10 Hz heartbeat (301281d) made ACE see us in-radius before its
MoveToChain timeout; user confirmed doors work now. Closing #67 — root
cause was 1 Hz position outbound on our side, not anything door-
specific. Same fix unblocked door + NPC.
Two visible refinements:
1. Turn-first gate. User report: 'when I use from far range, I should
face that object and then start moving. Now it starts running
before facing is complete.'
ApplyAutoWalkOverlay now suppresses Forward motion when the
heading delta to the target is > 30°. Body turns IN PLACE first,
then walks forward once roughly aligned. Within the 30° band the
body walks while finishing the residual turn. Matches the user-
observed retail rhythm.
2. Arrival margin shrunk 0.2 m → 0.05 m. User report: 'NPC dialogue
fires, but still a bit too close. In retail it fires from a longer
range.' With the 10 Hz heartbeat the server-side Player.Location
tracks us within ~100 ms, so the bigger safety margin is no longer
needed — only a tiny epsilon to absorb the sub-tick race between
local arrival fire and the next outbound packet.
Filed #68: remote players' running animation doesn't transition to
Ready on auto-walk arrival when observed from acdream. Separate
visual bug — server-side action completes correctly; just the cycle
on the dead-reckoned remote body doesn't flip back to idle.
This commit is contained in:
parent
301281d8d0
commit
32352af583
2 changed files with 62 additions and 37 deletions
|
|
@ -445,22 +445,17 @@ public sealed class PlayerMovementController
|
|||
float dy = _autoWalkDestination.Y - pos.Y;
|
||||
float dist = MathF.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Arrival predicate. CRITICAL: ACE's server-side WithinUseRadius
|
||||
// is strict (dist <= radius), so arriving exactly at the radius
|
||||
// boundary fails — ACE rejects the action and replies with
|
||||
// another MoveToObject. We walk slightly INSIDE the boundary so
|
||||
// the re-sent action lands safely in-range.
|
||||
//
|
||||
// The margin is small — user feedback says retail fires Use
|
||||
// from longer range, so we minimise the over-walk: 0.2 m at
|
||||
// typical NPC radii (3 m → arrive at 2.8 m), tapered for tight
|
||||
// pickup radii (0.6 m → arrive at 0.48 m) so the body stays
|
||||
// reachable but always inside ACE's strict check.
|
||||
// Arrival predicate. With the 10 Hz heartbeat from 301281d the
|
||||
// server-side Player.Location tracks our body within ~100 ms, so
|
||||
// the previous "subtract 0.2 m safety margin" workaround is no
|
||||
// longer needed. Tiny 0.05 m margin remains to absorb the
|
||||
// sub-tick race between local arrival-fire and the next
|
||||
// heartbeat's outbound packet.
|
||||
float arrivalThreshold = _autoWalkMoveTowards
|
||||
? _autoWalkDistanceToObject
|
||||
: _autoWalkMinDistance;
|
||||
float safetyMargin = MathF.Min(0.2f, arrivalThreshold * 0.2f);
|
||||
float effectiveArrival = MathF.Max(arrivalThreshold - safetyMargin, 0.1f);
|
||||
const float TinyMargin = 0.05f;
|
||||
float effectiveArrival = MathF.Max(arrivalThreshold - TinyMargin, 0.1f);
|
||||
bool arrived =
|
||||
(_autoWalkMoveTowards
|
||||
&& dist <= effectiveArrival)
|
||||
|
|
@ -476,6 +471,14 @@ public sealed class PlayerMovementController
|
|||
// _body.Orientation = Quaternion.CreateFromAxisAngle(Z, Yaw - π/2),
|
||||
// so local-forward (+Y) maps to world (cos Yaw, sin Yaw, 0).
|
||||
// Therefore Yaw that faces (dx,dy) is atan2(dy, dx).
|
||||
//
|
||||
// User feedback (2026-05-15): 'I should face that object and then
|
||||
// start moving. Now it starts running before facing is complete.'
|
||||
// Track the current heading delta — if we're more than the
|
||||
// walk-while-turning threshold off, suppress Forward this frame
|
||||
// so the body turns IN PLACE first. Once we're within the
|
||||
// threshold, the synthesised Forward+Run kicks in below.
|
||||
bool aligned = true;
|
||||
if (dist > 1e-4f)
|
||||
{
|
||||
float desiredYaw = MathF.Atan2(dy, dx);
|
||||
|
|
@ -493,6 +496,14 @@ public sealed class PlayerMovementController
|
|||
}
|
||||
while (Yaw > MathF.PI) Yaw -= 2f * MathF.PI;
|
||||
while (Yaw < -MathF.PI) Yaw += 2f * MathF.PI;
|
||||
|
||||
// 30° "walk-while-turning" threshold: outside this, body
|
||||
// turns in place. Inside, body walks forward while finishing
|
||||
// any remaining alignment. Matches retail-feel observation;
|
||||
// exact retail value is in MoveToManager but ~30° is a
|
||||
// sensible heuristic for now.
|
||||
const float WalkWhileTurningRad = 30f * MathF.PI / 180f;
|
||||
aligned = MathF.Abs(delta) <= WalkWhileTurningRad;
|
||||
}
|
||||
|
||||
// Walk vs run decided ONCE at BeginServerAutoWalk based on
|
||||
|
|
@ -503,6 +514,11 @@ public sealed class PlayerMovementController
|
|||
// all the way to the object and then stop").
|
||||
bool shouldRun = _autoWalkInitiallyRunning;
|
||||
|
||||
// Turn-first gate: if not yet aligned with the target, suppress
|
||||
// forward motion so the body turns in place rather than
|
||||
// walking an arc.
|
||||
bool moveForward = aligned;
|
||||
|
||||
// Synthesize "moving forward" input. The rest of Update reads
|
||||
// Yaw + input.Forward + input.Run to drive _motion + body
|
||||
// velocity exactly as it does for user-driven W (+ optional Shift).
|
||||
|
|
@ -510,8 +526,8 @@ public sealed class PlayerMovementController
|
|||
// steering.
|
||||
return input with
|
||||
{
|
||||
Forward = true,
|
||||
Run = shouldRun,
|
||||
Forward = moveForward,
|
||||
Run = moveForward && shouldRun,
|
||||
Backward = false,
|
||||
StrafeLeft = false,
|
||||
StrafeRight = false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue