feat(items): port retail external-container looting
Add the ClientUISystem ground-object lifecycle, authoritative root and nested ViewContents projections, replacement and close semantics, and the DAT-authored gmExternalContainerUI strip for chests and corpses. Route double-click loot and full or partial drag transfers through the shared retail item policy without optimistic external ownership. Remove the incorrect NoLongerViewingContents behavior from owned side packs and retire AP-106/#196. Release build succeeds and all 5,875 tests pass with five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
48a118db91
commit
20ce67b625
27 changed files with 1750 additions and 45 deletions
|
|
@ -1026,6 +1026,56 @@ public sealed class ClientObjectTable
|
|||
ContainerContentsReplaced?.Invoke(containerId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retires a viewed container's temporary ordered projection without inventing
|
||||
/// item movement or deleting its child objects. Retail
|
||||
/// <c>ACCObjectMaint::StopViewingObjectContents</c> is driven by server event
|
||||
/// CloseGroundContainer; canonical ownership remains owned by move/delete wire.
|
||||
/// </summary>
|
||||
public bool StopViewingContents(uint containerId)
|
||||
{
|
||||
if (containerId == 0u || !_containerIndex.Remove(containerId))
|
||||
return false;
|
||||
ContainerContentsReplaced?.Invoke(containerId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retires the root and every nested container projection reachable through
|
||||
/// the last ViewContents snapshots. ACE sends a snapshot for each nested
|
||||
/// container; retail queues the external root's contents tree for destruction
|
||||
/// when the view closes.
|
||||
/// </summary>
|
||||
public int StopViewingContentsTree(uint rootContainerId)
|
||||
{
|
||||
if (rootContainerId == 0u)
|
||||
return 0;
|
||||
|
||||
var pending = new Stack<uint>();
|
||||
var visited = new HashSet<uint>();
|
||||
var removed = new List<uint>();
|
||||
pending.Push(rootContainerId);
|
||||
while (pending.Count != 0)
|
||||
{
|
||||
uint containerId = pending.Pop();
|
||||
if (!visited.Add(containerId)
|
||||
|| !_containerIndex.TryGetValue(containerId, out List<uint>? contents))
|
||||
continue;
|
||||
|
||||
foreach (uint childId in contents)
|
||||
{
|
||||
if (_containerIndex.ContainsKey(childId))
|
||||
pending.Push(childId);
|
||||
}
|
||||
_containerIndex.Remove(containerId);
|
||||
removed.Add(containerId);
|
||||
}
|
||||
|
||||
foreach (uint containerId in removed)
|
||||
ContainerContentsReplaced?.Invoke(containerId);
|
||||
return removed.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize canonical inventory ownership from PlayerDescription. This
|
||||
/// login manifest is object-state initialization, not a move transition.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue