feat(D.2b): ClientObjectTable.ReplaceContents — authoritative full-replace for ViewContents
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
24a0b84162
commit
48bf752cf1
2 changed files with 85 additions and 0 deletions
|
|
@ -314,6 +314,52 @@ public sealed class ClientObjectTable
|
|||
_containerIndex.TryGetValue(containerId, out var l)
|
||||
? l.ToArray() : System.Array.Empty<uint>();
|
||||
|
||||
/// <summary>
|
||||
/// Replace a container's entire membership with <paramref name="guids"/> (in order) — the
|
||||
/// authoritative full snapshot the server sends in ViewContents (0x0196) when you open a
|
||||
/// container. Members no longer present are detached (ContainerId → 0); new members are
|
||||
/// recorded with ContainerSlot = list index. ACE writes ViewContents entries
|
||||
/// OrderBy(PlacementPosition) with NO explicit slot field (GameEventViewContents.cs), so the
|
||||
/// list order IS the slot order. Fires ObjectAdded/ObjectMoved so bound UI repaints.
|
||||
/// Retail consumer: ClientUISystem::OnViewContents.
|
||||
/// </summary>
|
||||
public void ReplaceContents(uint containerId, IReadOnlyList<uint> guids)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(guids);
|
||||
if (containerId == 0) return;
|
||||
|
||||
var keep = new HashSet<uint>(guids);
|
||||
|
||||
// Detach prior members no longer present (they left the container server-side). GetContents
|
||||
// returns a snapshot, so mutating the index inside the loop is safe.
|
||||
foreach (var old in GetContents(containerId))
|
||||
{
|
||||
if (keep.Contains(old)) continue;
|
||||
if (!_objects.TryGetValue(old, out var o) || o.ContainerId != containerId) continue;
|
||||
o.ContainerId = 0;
|
||||
Reindex(o, containerId);
|
||||
ObjectMoved?.Invoke(o, containerId, 0);
|
||||
}
|
||||
|
||||
// Record new members in order; ContainerSlot = index reconstructs the server PlacementPosition.
|
||||
for (int i = 0; i < guids.Count; i++)
|
||||
{
|
||||
uint g = guids[i];
|
||||
bool existed = _objects.TryGetValue(g, out var obj);
|
||||
if (!existed || obj is null)
|
||||
{
|
||||
obj = new ClientObject { ObjectId = g };
|
||||
_objects[g] = obj;
|
||||
}
|
||||
uint oldContainer = obj.ContainerId;
|
||||
obj.ContainerId = containerId;
|
||||
obj.ContainerSlot = i;
|
||||
Reindex(obj, oldContainer);
|
||||
if (!existed) ObjectAdded?.Invoke(obj);
|
||||
else ObjectMoved?.Invoke(obj, oldContainer, containerId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Σ Burden over every object carried by <paramref name="ownerGuid"/>: items
|
||||
/// whose container chain roots at the owner (pack + side-bag contents, retail
|
||||
|
|
|
|||
|
|
@ -319,6 +319,45 @@ public sealed class ClientObjectTableTests
|
|||
Assert.True((o.Type & ItemType.Creature) != 0); // LiveItemType(guid) & Creature drives creature targeting
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceContents_RecordsAllInListOrder()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.ReplaceContents(0xC9u, new uint[] { 0xA01u, 0xA02u, 0xA03u });
|
||||
Assert.Equal(new[] { 0xA01u, 0xA02u, 0xA03u }, table.GetContents(0xC9u));
|
||||
Assert.Equal(0xC9u, table.Get(0xA01u)!.ContainerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceContents_SecondSmallerList_DetachesDropped() // the full-REPLACE semantics (vs the old additive merge)
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.ReplaceContents(0xC9u, new uint[] { 0xA01u, 0xA02u, 0xA03u });
|
||||
table.ReplaceContents(0xC9u, new uint[] { 0xA02u }); // A01 + A03 removed server-side
|
||||
Assert.Equal(new[] { 0xA02u }, table.GetContents(0xC9u));
|
||||
Assert.Equal(0u, table.Get(0xA01u)!.ContainerId); // detached, not lingering
|
||||
Assert.Equal(0u, table.Get(0xA03u)!.ContainerId);
|
||||
Assert.Equal(0xC9u, table.Get(0xA02u)!.ContainerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceContents_ReordersToNewListOrder()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.ReplaceContents(0xC9u, new uint[] { 0xA01u, 0xA02u });
|
||||
table.ReplaceContents(0xC9u, new uint[] { 0xA02u, 0xA01u });
|
||||
Assert.Equal(new[] { 0xA02u, 0xA01u }, table.GetContents(0xC9u));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceContents_ZeroContainer_NoOp()
|
||||
{
|
||||
var table = new ClientObjectTable();
|
||||
table.ReplaceContents(0u, new uint[] { 0xA01u });
|
||||
Assert.Empty(table.GetContents(0u));
|
||||
Assert.Null(table.Get(0xA01u));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainerIndex_SlotChange_ResortsInPlace() // guards the Reindex same-container early-out
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue