fix: complete retail parity stability pass
All checks were successful
CI / linux-portable (push) Successful in 3m41s
CI / windows-gate (push) Successful in 6m49s
CI / release (push) Successful in 3m22s

This commit is contained in:
Erik 2026-08-28 20:01:39 +02:00
parent d3df4cb20a
commit f7aa8e0eb7
131 changed files with 7765 additions and 1190 deletions

View file

@ -156,6 +156,43 @@ public sealed class LiveSessionEventRouterTests
router.Dispose();
}
[Fact]
public void PlayerKilled_SuppressesLocalParticipantsAndKeepsBystanderLine()
{
const uint self = 0x50000001u;
const uint other = 0x50000002u;
const uint third = 0x50000003u;
using var session = NewSession();
var chat = new ChatLog();
var social = new LiveSocialSessionBindings(
chat,
new TurbineChatState(),
new FriendsState(),
new SquelchState(),
PlayerGuid: () => self);
var router = new LiveSessionEventRouter(
session,
NoOpEntitySink(),
NoOpEnvironmentSink(),
NewInventoryBindings(),
NewCharacterBindings(),
social);
router.Attach();
Action<PlayerKilled.Parsed> deliver =
EventDelegate<Action<PlayerKilled.Parsed>>(
session,
nameof(session.PlayerKilledReceived));
deliver(new PlayerKilled.Parsed("self died", self, other));
deliver(new PlayerKilled.Parsed("self killed", other, self));
deliver(new PlayerKilled.Parsed("bystander", other, third));
ChatEntry entry = Assert.Single(chat.Snapshot());
Assert.Equal("bystander", entry.Text);
Assert.Equal(other, entry.SenderGuid);
Assert.Equal(third, entry.ChannelId);
}
// ── Campaign FA slice FA2 fix-round SHOULD-FIX 4 (blast review) ────────
// The single production registration site (LiveSessionEventRouter.cs)
// was untested — both router-test factories default Fellowship/
@ -878,6 +915,71 @@ public sealed class LiveSessionEventRouterTests
PlayPhysicsScriptType: _ => { },
SoundEvent: _ => { });
[Fact]
public void HouseRentNotices_RouteIntoTheSharedRuntimeOwner()
{
const uint self = 0x50000001u;
using var session = NewSession();
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject { ObjectId = self, Type = ItemType.Creature });
var house = new RuntimeHouseState(objects);
house.ApplyHouseData(
new GameEvents.HouseData(
BuyTime: 0u,
RentTime: 1_700_000_000u,
Type: 1u,
MaintenanceFree: false,
Buy: [],
Rent: [new GameEvents.HousePayment(10, 10, 1u, "Pyreal", "Pyreals")],
Position: new CreateObject.ServerPosition()),
self);
var router = new LiveSessionEventRouter(
session,
NoOpEntitySink(),
NoOpEnvironmentSink(),
new LiveInventorySessionBindings(
objects,
PlayerGuid: () => self,
OnShortcuts: null,
OnUseDone: null,
ItemMana: new ItemManaState(),
ExternalContainers: new ExternalContainerState()),
NewCharacterBindings(),
new LiveSocialSessionBindings(
new ChatLog(),
new TurbineChatState(),
new FriendsState(),
new SquelchState(),
House: house));
router.Attach();
session.GameEvents.Dispatch(GameEventEnvelope.TryParse(
WrapGameEvent(
GameEventType.UpdateRentTime,
BitConverter.GetBytes(1_710_000_000u)))!.Value);
Assert.Equal("Rent:\n0/10 Pyreals", house.Lines[1]);
Assert.Equal(HousePanelTextColor.RentNotPaid, house.PanelLines[^2].Color);
using var payload = new MemoryStream();
using (var writer = new BinaryWriter(
payload, System.Text.Encoding.UTF8, leaveOpen: true))
{
writer.Write(1u);
writer.Write(10);
writer.Write(10);
writer.Write(1u);
WriteString16L(writer, "Pyreal");
WriteString16L(writer, "Pyreals");
}
session.GameEvents.Dispatch(GameEventEnvelope.TryParse(
WrapGameEvent(GameEventType.UpdateRentPayment, payload.ToArray()))!.Value);
Assert.Equal("Rent:\n10/10 Pyreals", house.Lines[1]);
Assert.Equal(HousePanelTextColor.RentPaid, house.PanelLines[^2].Color);
router.Dispose();
}
private static LiveEnvironmentSessionSink NoOpEnvironmentSink() => new(
EnvironChanged: _ => { },
ServerTimeUpdated: _ => { });
@ -1017,6 +1119,24 @@ public sealed class LiveSessionEventRouterTests
return body;
}
private static byte[] WrapGameEvent(GameEventType type, byte[] payload)
{
byte[] body = new byte[GameEventEnvelope.HeaderSize + payload.Length];
BinaryPrimitives.WriteUInt32LittleEndian(body, GameEventEnvelope.Opcode);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), 0u);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), 0u);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), (uint)type);
payload.CopyTo(body, GameEventEnvelope.HeaderSize);
return body;
}
private static void WriteString16L(BinaryWriter writer, string value)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(value);
writer.Write((ushort)bytes.Length);
writer.Write(bytes);
}
private static WorldSession NewSession() =>
new(new IPEndPoint(IPAddress.Loopback, 9));