fix(items): finish corpse pickup and combat switching

Port retail's first-slot ShowPendingInPlayer path for double-click loot and carry the current owned-container destination through deferred pickup. Retire the previous ground-container view as soon as a replacement is requested so its range close cannot cancel ACE's active MoveTo chain.

Preserve active-combat weapon intent across ACE's authoritative wand-to-missile stance tail while leaving peace-mode switches unchanged.

Release build succeeds and all 5,885 tests pass with five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-17 18:35:29 +02:00
parent d51a0fc825
commit 0392c6d721
17 changed files with 631 additions and 81 deletions

View file

@ -1,4 +1,5 @@
using System;
using AcDream.Core.Combat;
using AcDream.Core.Items;
namespace AcDream.App.UI;
@ -40,7 +41,8 @@ public sealed class ItemInteractionController : IDisposable
private readonly Func<bool> _playerOnGround;
private readonly Func<bool> _inNonCombatMode;
private readonly Func<uint, bool> _isComponentPack;
private readonly Action<uint>? _placeInBackpack;
private readonly Action<uint, uint, int>? _placeInBackpack;
private readonly Func<uint> _backpackContainerId;
private readonly Action<uint>? _requestExternalContainer;
private readonly Action<ItemPolicyAction>? _auxiliaryAction;
private readonly InteractionState _interactionState;
@ -73,7 +75,8 @@ public sealed class ItemInteractionController : IDisposable
Func<bool>? playerOnGround = null,
Func<bool>? inNonCombatMode = null,
Func<uint, bool>? isComponentPack = null,
Action<uint>? placeInBackpack = null,
Action<uint, uint, int>? placeInBackpack = null,
Func<uint>? backpackContainerId = null,
Action<ItemPolicyAction>? auxiliaryAction = null,
Action<uint, uint>? sendSplitToWorld = null,
Func<uint>? selectedObjectId = null,
@ -83,7 +86,9 @@ public sealed class ItemInteractionController : IDisposable
Func<bool>? dragOnPlayerOpensSecureTrade = null,
Action<string>? systemMessage = null,
Action<uint, uint, uint, uint>? sendSplitToContainer = null,
Action<uint>? requestExternalContainer = null)
Action<uint>? requestExternalContainer = null,
CombatState? combatState = null,
Action<CombatMode>? sendChangeCombatMode = null)
{
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
_playerGuid = playerGuid ?? throw new ArgumentNullException(nameof(playerGuid));
@ -105,6 +110,7 @@ public sealed class ItemInteractionController : IDisposable
_inNonCombatMode = inNonCombatMode ?? (() => false);
_isComponentPack = isComponentPack ?? (_ => false);
_placeInBackpack = placeInBackpack;
_backpackContainerId = backpackContainerId ?? _playerGuid;
_requestExternalContainer = requestExternalContainer;
_auxiliaryAction = auxiliaryAction;
_selectedObjectId = selectedObjectId ?? (() => 0u);
@ -119,11 +125,20 @@ public sealed class ItemInteractionController : IDisposable
_sendWield,
sendPutItemInContainer,
_toast,
_systemMessage);
_systemMessage,
combatState,
sendChangeCombatMode);
}
public event Action? StateChanged;
/// <summary>
/// Retail <c>CM_Item::SendNotice_ShowPendingInPlayer</c>: the inventory
/// panel inserts a waiting projection before the pickup request is sent.
/// The destination and placement are the same values sent on the wire.
/// </summary>
public event Action<uint, uint, int>? PendingBackpackPlacementRequested;
public uint PlayerGuid => _playerGuid();
public InteractionState InteractionState => _interactionState;
@ -399,8 +414,18 @@ public sealed class ItemInteractionController : IDisposable
switch (action.Kind)
{
case ItemPolicyActionKind.PlaceInBackpack:
_placeInBackpack?.Invoke(action.ObjectId);
acted |= _placeInBackpack is not null;
if (_placeInBackpack is null)
break;
uint containerId = _backpackContainerId();
if (containerId == 0u)
containerId = _playerGuid();
const int placement = 0;
PendingBackpackPlacementRequested?.Invoke(
action.ObjectId,
containerId,
placement);
_placeInBackpack(action.ObjectId, containerId, placement);
acted = true;
break;
case ItemPolicyActionKind.WieldRight:
case ItemPolicyActionKind.WieldLeft: