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
|
|
@ -840,6 +840,8 @@ public sealed class GameWindow : IDisposable
|
|||
private AcDream.App.Combat.CombatAttackController? _combatAttackController;
|
||||
private AcDream.App.Combat.CombatTargetController? _combatTargetController;
|
||||
private AcDream.App.UI.ItemInteractionController? _itemInteractionController;
|
||||
private readonly AcDream.Core.Items.ExternalContainerState _externalContainers = new();
|
||||
private AcDream.App.World.ExternalContainerLifecycleController? _externalContainerLifecycle;
|
||||
private AcDream.App.Spells.MagicRuntime? _magicRuntime;
|
||||
private AcDream.App.Spells.MagicCatalog? _magicCatalog;
|
||||
private readonly AcDream.Core.Items.StackSplitQuantityState _stackSplitQuantity = new();
|
||||
|
|
@ -2110,6 +2112,10 @@ public sealed class GameWindow : IDisposable
|
|||
_selection,
|
||||
autoTarget: () => _persistedGameplay.AutoTarget,
|
||||
selectClosestTarget: () => SelectClosestCombatTarget(showToast: false));
|
||||
_externalContainerLifecycle = new AcDream.App.World.ExternalContainerLifecycleController(
|
||||
_externalContainers,
|
||||
Objects,
|
||||
guid => _liveSession?.SendNoLongerViewingContents(guid));
|
||||
|
||||
// Phase D.2b retail-look retained UI (ACDREAM_RETAIL_UI=1).
|
||||
if (_options.RetailUi)
|
||||
|
|
@ -2146,7 +2152,11 @@ public sealed class GameWindow : IDisposable
|
|||
stackSplitQuantity: _stackSplitQuantity,
|
||||
systemMessage: text => Chat.OnSystemMessage(text, 0x1Au),
|
||||
sendPutItemInContainer: (item, container, placement) =>
|
||||
_liveSession?.SendPutItemInContainer(item, container, placement));
|
||||
_liveSession?.SendPutItemInContainer(item, container, placement),
|
||||
sendSplitToContainer: (item, container, placement, amount) =>
|
||||
_liveSession?.SendStackableSplitToContainer(
|
||||
item, container, placement, amount),
|
||||
requestExternalContainer: guid => _externalContainers.RequestOpen(guid));
|
||||
var cursorFeedbackController = new AcDream.App.UI.CursorFeedbackController(
|
||||
_itemInteractionController,
|
||||
// Retail UpdateCursorState (0x00564630) keys target-mode
|
||||
|
|
@ -2358,7 +2368,6 @@ public sealed class GameWindow : IDisposable
|
|||
AcDream.Core.Player.LocalPlayerState.AttributeKind.Strength)
|
||||
is { } strength ? (int?)strength.Current : null,
|
||||
guid => _liveSession?.SendUse(guid),
|
||||
guid => _liveSession?.SendNoLongerViewingContents(guid),
|
||||
(item, container, placement) =>
|
||||
_liveSession?.SendPutItemInContainer(item, container, placement),
|
||||
(item, container, placement, amount) =>
|
||||
|
|
@ -2368,6 +2377,22 @@ public sealed class GameWindow : IDisposable
|
|||
_liveSession?.SendStackableMerge(source, target, amount),
|
||||
_itemInteractionController,
|
||||
_selection),
|
||||
ExternalContainer: new AcDream.App.UI.ExternalContainerRuntimeBindings(
|
||||
_externalContainers,
|
||||
Objects,
|
||||
(type, icon, under, over, effects) =>
|
||||
iconComposer.GetIcon(type, icon, under, over, effects),
|
||||
(type, icon, under, over, effects) =>
|
||||
iconComposer.GetDragIcon(type, icon, under, over, effects),
|
||||
_itemInteractionController,
|
||||
_selection,
|
||||
guid => _liveSession?.SendUse(guid),
|
||||
(item, container, placement) =>
|
||||
_liveSession?.SendPutItemInContainer(item, container, placement),
|
||||
(item, container, placement, amount) =>
|
||||
_liveSession?.SendStackableSplitToContainer(
|
||||
item, container, placement, amount),
|
||||
IsWithinExternalContainerUseRange),
|
||||
Cursor: new AcDream.App.UI.RetailUiCursorBindings(
|
||||
cursorFeedbackController,
|
||||
retailCursorManager),
|
||||
|
|
@ -2863,6 +2888,7 @@ public sealed class GameWindow : IDisposable
|
|||
// Attachment projections own GL-backed world registrations, so tear
|
||||
// them down before dropping the canonical GUID/timestamp snapshots.
|
||||
_equippedChildRenderer?.Clear();
|
||||
_externalContainers.Reset();
|
||||
Objects.Clear();
|
||||
SpellBook.Clear();
|
||||
_magicRuntime?.Reset();
|
||||
|
|
@ -3025,7 +3051,11 @@ public sealed class GameWindow : IDisposable
|
|||
},
|
||||
onShortcuts: list => Shortcuts = list,
|
||||
playerGuid: () => _playerServerGuid,
|
||||
onUseDone: error => _itemInteractionController?.CompleteUse(error),
|
||||
onUseDone: error =>
|
||||
{
|
||||
_externalContainers.ApplyUseDone(error);
|
||||
_itemInteractionController?.CompleteUse(error);
|
||||
},
|
||||
itemMana: ItemMana,
|
||||
onConfirmationRequest: request =>
|
||||
_retailUiRuntime?.HandleConfirmationRequest(request),
|
||||
|
|
@ -3038,7 +3068,8 @@ public sealed class GameWindow : IDisposable
|
|||
_characterOptions1 =
|
||||
(AcDream.Core.Net.Messages.PlayerDescriptionParser.CharacterOptions1)
|
||||
options1,
|
||||
clientTime: ClientTimerNow);
|
||||
clientTime: ClientTimerNow,
|
||||
externalContainers: _externalContainers);
|
||||
|
||||
// Phase I.7: subscribe to CombatState events and emit
|
||||
// retail-faithful "You hit X for Y damage" chat lines into
|
||||
|
|
@ -13266,6 +13297,9 @@ public sealed class GameWindow : IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
if (IsExternalContainerTarget(guid))
|
||||
_externalContainers.RequestOpen(guid);
|
||||
|
||||
// B.6/R4-V5: install a speculative local TurnToObject/MoveToObject
|
||||
// through the player's MoveToManager so close-range Use rotates the
|
||||
// body to face before the action fires. For FAR targets, ACE's
|
||||
|
|
@ -13482,6 +13516,40 @@ public sealed class GameWindow : IDisposable
|
|||
return distSq <= useRadius * useRadius;
|
||||
}
|
||||
|
||||
private bool IsWithinExternalContainerUseRange(uint targetGuid)
|
||||
{
|
||||
if (_playerController is null
|
||||
|| !_visibleEntitiesByServerGuid.TryGetValue(targetGuid, out var entity))
|
||||
{
|
||||
// The server remains authoritative for forced close. An entity can be
|
||||
// temporarily absent from the visible-cell projection while its external
|
||||
// view is still valid; do not manufacture a range exit from missing render state.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!LastSpawns.TryGetValue(targetGuid, out var spawn)
|
||||
|| spawn.UseRadius is not > 0f)
|
||||
return true;
|
||||
float useRadius = spawn.UseRadius.Value;
|
||||
float dx = entity.Position.X - _playerController.Position.X;
|
||||
float dy = entity.Position.Y - _playerController.Position.Y;
|
||||
return dx * dx + dy * dy <= useRadius * useRadius;
|
||||
}
|
||||
|
||||
private bool IsExternalContainerTarget(uint guid)
|
||||
{
|
||||
if (Objects.Get(guid) is { } item
|
||||
&& (item.ContainerTypeHint != 0u
|
||||
|| item.Type.HasFlag(AcDream.Core.Items.ItemType.Container)
|
||||
|| item.ItemsCapacity > 0))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return LastSpawns.TryGetValue(guid, out var spawn)
|
||||
&& (spawn.ObjectDescriptionFlags.GetValueOrDefault() & 0x2000u) != 0u;
|
||||
}
|
||||
|
||||
private void InstallSpeculativeTurnToTarget(uint targetGuid)
|
||||
{
|
||||
if (_playerController is not { } pc || pc.MoveTo is null) return;
|
||||
|
|
@ -14784,6 +14852,8 @@ public sealed class GameWindow : IDisposable
|
|||
_uiHost = null;
|
||||
_itemInteractionController?.Dispose();
|
||||
_itemInteractionController = null;
|
||||
_externalContainerLifecycle?.Dispose();
|
||||
_externalContainerLifecycle = null;
|
||||
_magicRuntime = null;
|
||||
_magicCatalog = null;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue