fix(B.6+B.7): re-send action on local arrival; scale indicator box by entity Scale

User report: 'It still however just approach it and does not use it.'
Root cause: local auto-walk arrives at the target visually, but ACE's
server-side MoveToChain may have timed out before our position was
recognised as in-range (we don't echo authoritative position back to
ACE during the walk yet). The action never fires.

Fix (re-send on arrival):
  * PlayerMovementController.AutoWalkArrived event fires once when
    EndServerAutoWalk(reason='arrived') is called.
  * GameWindow tracks _pendingPostArrivalAction = (guid, isPickup)
    on each SendUse / SendPickUp.
  * OnAutoWalkArrivedReSendAction (subscribed at EnterPlayerModeNow)
    re-sends the action with isRetryAfterArrival=true. The retry
    flag prevents the re-sent action from itself setting a new
    pending action — breaks any potential re-fire loop.
  * The re-sent action is close-range from the local body's
    perspective, so ACE's CreateMoveToChain hits the WithinUseRadius
    shortcut (Player_Move.cs:66) and completes immediately —
    dialogue opens, item picks up.

User report: 'items dropped on the ground now have a smaller triangle
box, perhaps too small. Also now other stuff like signs also have a
very small triangle box, should not have it should scale to the size
of the object.'

Fix (scale-aware indicator height):
  * TargetIndicatorPanel.TargetInfo now carries entity Scale.
  * EntityHeightFor multiplies the per-type base by Scale so an
    upscaled NPC / sign / lifestone gets a proportionally larger box.
  * Per-type table refined:
      Creature                    : 1.8 m * scale
      Door/Lifestone/Portal       : 2.4 m * scale
      Small carry items (weapon/armor/clothing/jewelry/food/money/
        misc/missile-weapon/container/gem/spellcomp/writable/key/
        caster — most pickup-able): 0.8 m * scale  (up from 0.5 m)
      Everything else (signs / scenery interactables / untyped):
        1.5 m * scale  (up from 0.5 m default)

Deferred to follow-up: exact mesh-AABB-derived box (need to read
each entity's actual rendered bounds at registration time).
This commit is contained in:
Erik 2026-05-15 07:45:27 +02:00
parent 5f83766de5
commit 2dc28bb61f
3 changed files with 132 additions and 15 deletions

View file

@ -38,11 +38,15 @@ public sealed class TargetIndicatorPanel
/// What the panel needs to know about the selected entity per frame.
/// <c>ItemType</c> + <c>ObjectDescriptionFlags</c> feed
/// <see cref="RadarBlipColors.For"/> for colour selection.
/// <c>Scale</c> multiplies the per-type base height in
/// <see cref="EntityHeightFor"/> — a scaled-up sign or oversized NPC
/// gets a proportionally bigger box.
/// </summary>
public readonly record struct TargetInfo(
Vector3 WorldPosition,
uint ItemType,
uint ObjectDescriptionFlags);
uint ObjectDescriptionFlags,
float Scale);
private readonly Func<uint?> _selectedGuidProvider;
private readonly Func<uint, TargetInfo?> _entityResolver;
@ -64,24 +68,68 @@ public sealed class TargetIndicatorPanel
/// <summary>
/// Resolve the world-space height to use for a given entity's
/// indicator box. Humanoids (Creature flag) use 1.8 m; doors /
/// lifestones / portals use 2.4 m (door-frame tall); ground items
/// use 0.5 m so the box hugs the item rather than ballooning out
/// to humanoid height. Falls back to <see cref="EntityHeight"/>
/// for entities without a recognisable type tag.
/// indicator box. The base height per type is multiplied by the
/// entity's <paramref name="scale"/> so an upscaled sign or NPC
/// gets a proportionally bigger box.
///
/// <para>Per-type base height:</para>
/// <list type="bullet">
/// <item>Creature (NPC / monster / player): 1.8 m (humanoid)</item>
/// <item>Door / Lifestone / Portal: 2.4 m (door-frame tall)</item>
/// <item>Small carry items (Money, Food, Gem, SpellComponents,
/// Misc, Weapons, Armour, Clothing, Jewelry, Container):
/// 0.8 m (item dropped on the ground)</item>
/// <item>Everything else (signs, generic objects, untyped
/// scenery interactables): 1.5 m (mid-sized object
/// default; without mesh AABB this is a best guess)</item>
/// </list>
///
/// <para>
/// Future refinement (deferred): read the entity's actual mesh
/// AABB at registration time and use the projected silhouette
/// for an exact-fit box. Issue #66-ish.
/// </para>
/// </summary>
public float EntityHeightFor(uint itemType, uint pwdBitfield)
public float EntityHeightFor(uint itemType, uint pwdBitfield, float scale)
{
if (scale <= 0f) scale = 1f; // defensive
bool isCreature = (itemType & (uint)AcDream.Core.Items.ItemType.Creature) != 0;
if (isCreature) return 1.8f;
if (isCreature) return 1.8f * scale;
// BF_DOOR = 0x1000, BF_LIFESTONE = 0x4000, BF_PORTAL = 0x40000
// (acclient.h:6431-6463)
const uint TallStructureMask = 0x1000u | 0x4000u | 0x40000u;
if ((pwdBitfield & TallStructureMask) != 0) return 2.4f;
if ((pwdBitfield & TallStructureMask) != 0) return 2.4f * scale;
// Default: small ground item / object.
return 0.5f;
// Small carry items: weapons / armour / clothing / jewellery /
// money / food / misc / weapons / containers / gems / spell
// components / writable / keys / casters / lockables / promissory
// notes / mana stones / craft bases / craft intermediates /
// tinkering tools / tinkering materials / gameboard.
// Per AcDream.Core.Items.ItemType — most non-zero values in
// the lower 28 bits map to carryable items.
const uint SmallItemMask =
(uint)(AcDream.Core.Items.ItemType.MeleeWeapon
| AcDream.Core.Items.ItemType.Armor
| AcDream.Core.Items.ItemType.Clothing
| AcDream.Core.Items.ItemType.Jewelry
| AcDream.Core.Items.ItemType.Food
| AcDream.Core.Items.ItemType.Money
| AcDream.Core.Items.ItemType.Misc
| AcDream.Core.Items.ItemType.MissileWeapon
| AcDream.Core.Items.ItemType.Container
| AcDream.Core.Items.ItemType.Gem
| AcDream.Core.Items.ItemType.SpellComponents
| AcDream.Core.Items.ItemType.Writable
| AcDream.Core.Items.ItemType.Key
| AcDream.Core.Items.ItemType.Caster);
if ((itemType & SmallItemMask) != 0) return 0.8f * scale;
// Everything else (signs, scenery interactables, untyped objects):
// 1.5 m default — bigger than a small item but smaller than a
// humanoid, splitting the difference until we have real mesh
// bounds to project.
return 1.5f * scale;
}
/// <summary>
@ -132,7 +180,7 @@ public sealed class TargetIndicatorPanel
// projection.
if (!TryProjectToScreen(info.WorldPosition, viewProj, viewport, out var feetScreen))
return;
float entityHeight = EntityHeightFor(info.ItemType, info.ObjectDescriptionFlags);
float entityHeight = EntityHeightFor(info.ItemType, info.ObjectDescriptionFlags, info.Scale);
var headWorld = new Vector3(
info.WorldPosition.X,
info.WorldPosition.Y,