fix(interaction): restore retail loot placement and world-drop projection

This commit is contained in:
Erik 2026-07-27 00:03:15 +02:00
parent 4d095be286
commit 921712f412
24 changed files with 1581 additions and 34 deletions

View file

@ -179,6 +179,14 @@ public sealed class ItemInteractionController : IDisposable
/// </summary>
public event Action<PendingBackpackPlacement>? PendingBackpackPlacementResolved;
/// <summary>
/// Publishes the exact split-to-world request after its wire send has
/// succeeded. Retail <c>ACCWeenieObject::UIAttemptSplitTo3D @
/// 0x0058D850</c> retains the source class, selected amount, and request
/// time so the subsequently created ground stack can be recognized.
/// </summary>
public event Action<WorldDropDispatch>? WorldDropDispatched;
public uint PlayerGuid => _playerGuid();
public InteractionState InteractionState => _interactionState;
@ -302,14 +310,10 @@ public sealed class ItemInteractionController : IDisposable
/// <summary>
/// Retail <c>ACCWeenieObject::IsOwnedByPlayer</c> projection shared with
/// toolbar shortcut creation. Ownership includes self, equipped items, and
/// arbitrarily nested carried containers.
/// toolbar shortcut creation.
/// </summary>
public bool IsOwnedByPlayer(uint itemGuid)
=> _objects.Get(itemGuid) is { } item
&& (item.ObjectId == _playerGuid()
|| IsCarriedByPlayer(item)
|| IsEquippedByPlayer(item));
=> _objects.IsOwnedByObject(itemGuid, _playerGuid());
public bool IsCurrentTargetCompatible(uint targetGuid)
{
@ -721,6 +725,21 @@ public sealed class ItemInteractionController : IDisposable
return item is not null;
}
/// <summary>
/// Returns whether the object is a member of retail's current
/// <c>ClientUISystem::groundObject</c>. Such objects are container
/// contents, not independently projected world objects, so
/// <c>CPlayerSystem::PlaceInBackpack @ 0x0055D8C0</c> sends their
/// container transfer directly without a 3-D approach lookup.
/// </summary>
internal bool IsInCurrentGroundObject(uint objectId)
{
uint groundObjectId = _groundObjectId();
return groundObjectId != 0u
&& _objects.Get(objectId) is { } item
&& item.ContainerId == groundObjectId;
}
internal bool IsCurrentObjectIdentity(uint objectId, ClientObject item)
=> ReferenceEquals(_objects.Get(objectId), item);
@ -983,6 +1002,14 @@ public sealed class ItemInteractionController : IDisposable
if (_sendSplitToWorld is null)
return false;
_sendSplitToWorld(action.ObjectId, (uint)action.Amount);
if (_transactions.TryGetPending(out PendingInventoryRequest pending)
&& pending.Kind is InventoryRequestKind.SplitToWorld
&& pending.ItemId == action.ObjectId)
{
WorldDropDispatched?.Invoke(new WorldDropDispatch(
pending,
(uint)action.Amount));
}
return true;
});
break;
@ -1137,6 +1164,7 @@ public sealed class ItemInteractionController : IDisposable
_transactions.ObjectTableCleared -= OnInventoryObjectsCleared;
_transactions.RequestCompleted -= OnInventoryRequestCompleted;
_transactions.StateChanged -= OnTransactionStateChanged;
WorldDropDispatched = null;
_autoWield.Dispose();
}