fix(B.6+B.7): run-all-the-way auto-walk, per-type indicator height, R = smart interact
Three user-reported fixes:
1. (B.6) Run-vs-walk decision lifted out of the per-frame overlay
into BeginServerAutoWalk. Once set at auto-walk start, the
character runs (or walks) the full way to the target instead of
transitioning. Matches user-observed retail behaviour:
'if its far away it should run all the way to the object and
then stop'.
_autoWalkWalkRunThreshold → _autoWalkInitiallyRunning (bool,
sampled once from initial distance vs the wire's WalkRunThreshold).
2. (B.7) TargetIndicatorPanel now picks EntityHeight per-type:
Creature (NPC/player) → 1.8 m
Door / Lifestone / Portal (tall structures) → 2.4 m
Default (small ground item) → 0.5 m
Items now get a small box hugging the silhouette instead of a
humanoid-tall rectangle floating around them.
3. (Interact) R-key (UseCurrentSelection) now dispatches by target
type:
Item (no Creature flag, no BF_DOOR|LIFESTONE|PORTAL|CORPSE)
→ SendPickUp (PutItemInContainer 0x0019)
Everything else → SendUse (0x0036)
Single hotkey to interact with whatever's selected.
Deferred (separate phase): turn-to-face on close-range use. ACE
server-side does Rotate(target) before the close-range pickup
callback (Player_Move.cs:71), but our local body doesn't echo
the turn yet — needs a synthesized client-side rotation or
MovementType=8 TurnToObject handling. Filing as follow-up.
This commit is contained in:
parent
1a0656a3ce
commit
211fe240b8
3 changed files with 82 additions and 24 deletions
|
|
@ -234,12 +234,13 @@ public sealed class PlayerMovementController
|
|||
private float _autoWalkMinDistance;
|
||||
private float _autoWalkDistanceToObject;
|
||||
private bool _autoWalkMoveTowards;
|
||||
// Wire's WalkRunThreshold — retail semantic: locomotion runs while
|
||||
// remaining distance > threshold, walks once inside threshold. ACE's
|
||||
// Use/PickUp path uses MoveToParameters.SetDefaults() = 15.0f, so
|
||||
// most pickup targets walk the entire way. ACE's charge path sets
|
||||
// it to 1.0f so combat chase runs almost the whole way.
|
||||
private float _autoWalkWalkRunThreshold;
|
||||
// Walk-vs-run decision is made ONCE at BeginServerAutoWalk based on
|
||||
// initial distance vs the wire's WalkRunThreshold, then held for the
|
||||
// duration of the auto-walk. Earlier per-frame evaluation produced
|
||||
// "runs partway then walks the rest" which the user reported as
|
||||
// wrong: the character should run all the way to the target then
|
||||
// stop, not transition to a walk near the end.
|
||||
private bool _autoWalkInitiallyRunning;
|
||||
|
||||
/// <summary>
|
||||
/// True while a server-initiated auto-walk (MoveToObject inbound) is
|
||||
|
|
@ -357,7 +358,18 @@ public sealed class PlayerMovementController
|
|||
_autoWalkMinDistance = minDistance;
|
||||
_autoWalkDistanceToObject = distanceToObject;
|
||||
_autoWalkMoveTowards = moveTowards;
|
||||
_autoWalkWalkRunThreshold = walkRunThreshold;
|
||||
|
||||
// Decide run vs walk ONCE based on the initial horizontal
|
||||
// distance from the player to the destination. Run-all-the-way
|
||||
// is the retail-faithful behaviour the user observed: pick a
|
||||
// distant target → character runs the whole way, decelerates
|
||||
// to a stop at use radius. Earlier per-frame evaluation made
|
||||
// the body transition to a walk inside threshold and felt
|
||||
// wrong (the user reported "runs partway then walks").
|
||||
float dx = destinationWorld.X - _body.Position.X;
|
||||
float dy = destinationWorld.Y - _body.Position.Y;
|
||||
float initialDist = MathF.Sqrt(dx * dx + dy * dy);
|
||||
_autoWalkInitiallyRunning = initialDist > walkRunThreshold;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -452,15 +464,13 @@ public sealed class PlayerMovementController
|
|||
while (Yaw < -MathF.PI) Yaw += 2f * MathF.PI;
|
||||
}
|
||||
|
||||
// Walk vs run per the wire's WalkRunThreshold. Retail semantics:
|
||||
// dist > threshold → RUN, dist ≤ threshold → WALK. ACE's default
|
||||
// for Use/PickUp is 15.0 m (so close targets walk the whole way);
|
||||
// ACE's combat charge path sets it to 1.0 m (so chase runs to the
|
||||
// last metre then walks the final approach). Matches the user's
|
||||
// observed retail behaviour: "When at a distance X it should
|
||||
// start running towards the double clicked target… When at a
|
||||
// shorter distance it should walk to it."
|
||||
bool shouldRun = dist > _autoWalkWalkRunThreshold;
|
||||
// Walk vs run decided ONCE at BeginServerAutoWalk based on
|
||||
// initial distance — held for the rest of the auto-walk so the
|
||||
// character keeps running all the way to the target instead of
|
||||
// transitioning to a walk inside the threshold. Matches user-
|
||||
// observed retail behaviour ("if its far away it should run
|
||||
// all the way to the object and then stop").
|
||||
bool shouldRun = _autoWalkInitiallyRunning;
|
||||
|
||||
// Synthesize "moving forward" input. The rest of Update reads
|
||||
// Yaw + input.Forward + input.Run to drive _motion + body
|
||||
|
|
|
|||
|
|
@ -9064,10 +9064,36 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
private void UseCurrentSelection()
|
||||
{
|
||||
if (_selectedGuid is uint sel)
|
||||
SendUse(sel);
|
||||
else
|
||||
if (_selectedGuid is not uint sel)
|
||||
{
|
||||
_debugVm?.AddToast("Nothing selected");
|
||||
return;
|
||||
}
|
||||
|
||||
// B.7 (2026-05-15): the user requested R behave as a universal
|
||||
// interact key — pickup for items, use for NPCs / doors /
|
||||
// lifestones / portals / corpses. Matches retail's "use"
|
||||
// behaviour where the action picked depends on the target's
|
||||
// type rather than forcing the player to remember a different
|
||||
// hotkey per target type.
|
||||
bool isPickupableItem = true;
|
||||
if (_liveEntityInfoByGuid.TryGetValue(sel, out var info)
|
||||
&& (info.ItemType & AcDream.Core.Items.ItemType.Creature) != 0)
|
||||
{
|
||||
// NPCs / monsters / players are Use targets, never PickUp.
|
||||
isPickupableItem = false;
|
||||
}
|
||||
if (_lastSpawnByGuid.TryGetValue(sel, out var spawn)
|
||||
&& spawn.ObjectDescriptionFlags is { } odf)
|
||||
{
|
||||
// BF_DOOR | BF_LIFESTONE | BF_PORTAL | BF_CORPSE → Use, not PickUp.
|
||||
// (acclient.h:6431-6463)
|
||||
const uint NonPickupMask = 0x1000u | 0x4000u | 0x40000u | 0x2000u;
|
||||
if ((odf & NonPickupMask) != 0) isPickupableItem = false;
|
||||
}
|
||||
|
||||
if (isPickupableItem) SendPickUp(sel);
|
||||
else SendUse(sel);
|
||||
}
|
||||
|
||||
private void SendUse(uint guid)
|
||||
|
|
|
|||
|
|
@ -54,15 +54,36 @@ public sealed class TargetIndicatorPanel
|
|||
public float TriangleSize { get; set; } = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// World-space height of the indicator box. The panel projects feet
|
||||
/// (at <c>WorldPosition</c>) and head (at <c>WorldPosition.Z +
|
||||
/// EntityHeight</c>) to screen space and uses the projected pixel
|
||||
/// distance as the box height. 1.8 m matches a standing humanoid;
|
||||
/// World-space height of the indicator box for entities that don't
|
||||
/// have a more specific type tag. Items use a smaller value (see
|
||||
/// <see cref="EntityHeightFor"/>). 1.8 m matches a standing humanoid;
|
||||
/// short items still get a small box because the projection
|
||||
/// preserves apparent size.
|
||||
/// </summary>
|
||||
public float EntityHeight { get; set; } = 1.8f;
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public float EntityHeightFor(uint itemType, uint pwdBitfield)
|
||||
{
|
||||
bool isCreature = (itemType & (uint)AcDream.Core.Items.ItemType.Creature) != 0;
|
||||
if (isCreature) return 1.8f;
|
||||
|
||||
// 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;
|
||||
|
||||
// Default: small ground item / object.
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Box width = <see cref="EntityHeight"/> projected height ×
|
||||
/// <see cref="WidthHeightRatio"/>. Retail's Vivid Target Indicator
|
||||
|
|
@ -111,10 +132,11 @@ public sealed class TargetIndicatorPanel
|
|||
// projection.
|
||||
if (!TryProjectToScreen(info.WorldPosition, viewProj, viewport, out var feetScreen))
|
||||
return;
|
||||
float entityHeight = EntityHeightFor(info.ItemType, info.ObjectDescriptionFlags);
|
||||
var headWorld = new Vector3(
|
||||
info.WorldPosition.X,
|
||||
info.WorldPosition.Y,
|
||||
info.WorldPosition.Z + EntityHeight);
|
||||
info.WorldPosition.Z + entityHeight);
|
||||
if (!TryProjectToScreen(headWorld, viewProj, viewport, out var headScreen))
|
||||
return;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue