feat(D.2b): ViewContents wiring is a full REPLACE (consumes the GameEventWiring TODO)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
48bf752cf1
commit
ad9d2651c1
2 changed files with 37 additions and 9 deletions
|
|
@ -250,19 +250,18 @@ public static class GameEventWiring
|
|||
newSlot: (int)p.Value.Placement);
|
||||
});
|
||||
|
||||
// B-Wire: ViewContents (0x0196) — the server's full contents list for a
|
||||
// container you opened. Record membership for each entry so the table is
|
||||
// correct before the container-open UI mounts the second item list.
|
||||
// TODO(container-open): ViewContents is the AUTHORITATIVE full snapshot. When the
|
||||
// container-open phase consumes it, treat it as a full REPLACE (flush the container's
|
||||
// prior membership first), not the additive merge below — otherwise items removed
|
||||
// server-side since the last open would linger.
|
||||
// ViewContents (0x0196) — the server's AUTHORITATIVE full contents list for a container you
|
||||
// opened (Use 0x0036). Treat it as a full REPLACE: flush the container's prior membership and
|
||||
// record the new entries in order (ContainerSlot = index — ACE writes entries
|
||||
// OrderBy(PlacementPosition) with no slot field). The container-open UI (InventoryController)
|
||||
// repaints the grid off the resulting ObjectAdded/Moved events. Retail: ClientUISystem::OnViewContents.
|
||||
dispatcher.Register(GameEventType.ViewContents, e =>
|
||||
{
|
||||
var p = GameEvents.ParseViewContents(e.Payload.Span);
|
||||
if (p is null) return;
|
||||
foreach (var entry in p.Value.Items)
|
||||
items.RecordMembership(entry.Guid, containerId: p.Value.ContainerGuid);
|
||||
var guids = new uint[p.Value.Items.Count];
|
||||
for (int i = 0; i < guids.Length; i++) guids[i] = p.Value.Items[i].Guid;
|
||||
items.ReplaceContents(p.Value.ContainerGuid, guids);
|
||||
});
|
||||
|
||||
// B-Wire: InventoryPutObjectIn3D (0x019A) — server confirms an item dropped
|
||||
|
|
|
|||
|
|
@ -556,6 +556,35 @@ public sealed class GameEventWiringTests
|
|||
Assert.Equal(0x500000C9u, items.Get(0x50000A02u)!.ContainerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WireAll_ViewContents_IsFullReplace_DropsRemovedItems()
|
||||
{
|
||||
var (d, items, _, _, _) = MakeAll();
|
||||
|
||||
// First view of container 0xC9: two items.
|
||||
DispatchViewContents(d, 0x500000C9u, new uint[] { 0x50000A01u, 0x50000A02u });
|
||||
Assert.Equal(2, items.GetContents(0x500000C9u).Count);
|
||||
|
||||
// Second view: only one item — the other was removed server-side. Additive merge would keep both.
|
||||
DispatchViewContents(d, 0x500000C9u, new uint[] { 0x50000A02u });
|
||||
Assert.Equal(new[] { 0x50000A02u }, items.GetContents(0x500000C9u));
|
||||
Assert.Equal(0u, items.Get(0x50000A01u)!.ContainerId);
|
||||
}
|
||||
|
||||
private static void DispatchViewContents(GameEventDispatcher d, uint containerGuid, uint[] itemGuids)
|
||||
{
|
||||
byte[] payload = new byte[8 + itemGuids.Length * 8];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(0), containerGuid);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(4), (uint)itemGuids.Length);
|
||||
for (int i = 0; i < itemGuids.Length; i++)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(8 + i * 8), itemGuids[i]);
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(12 + i * 8), 0u); // containerType
|
||||
}
|
||||
var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.ViewContents, payload));
|
||||
d.Dispatch(env!.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WireAll_InventoryPutObjectIn3D_UnparentsFromContainer()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue