feat(R3-W6): LOCAL PLAYER UNIFICATION — edge-driven retail input; UpdatePlayerAnimation + the synthesis layer DELETED (closes J15)
The local player now drives the SAME pipeline remotes use. The controller's per-frame RawMotionState rebuild (level-triggered D6.2) is replaced by retail's CommandInterpreter altitude: key EDGES fire DoMotion/StopMotion (0x00528d20/0x00528530) and the Shift edge fires set_hold_run (0x00528b70, caller-0x006b33ca shape) — the interpreter's own RawState mutates via ApplyMotion/RemoveMotion and every dispatch routes the funnel + DefaultSink into the sequencer. GameWindow's UpdatePlayerAnimation (169 lines) is DELETED with both LocalAnimationCommand/LocalAnimationSpeed MovementResult fields and all three synthesis sites (K-fix5 pacing, Walk→Run cycle selection, the auto-walk overrides): run pacing now comes from apply_run_to_command's my_run_rate promotion — the same source remotes use; airborne-Falling falls out of contact_allows_move (the funnel's second dispatch). Auto-walk's #69 turn-phase legs re-expressed as DoMotion(Turn*) edges; teleport idle = StopCompletely (retail's full stop) + an edge-tracker reset so held keys walk straight out of a portal. Map discovery R1 fixed: ChargeJump() now fires at charge START (the 0x0056afac input-boundary site) — StandingLongJump had NEVER armed in production despite the W3 port. TWO root-cause fixes the cutover surfaced (each would have been invisible under the old synthesis layer): - CurrentHoldKey was a shadow FIELD synced only on the raw dispatch branch; set_hold_run writes RawState.CurrentHoldKey, so an edge-driven Shift toggle followed by DoMotion read a stale None and lost the run promotion. Now an alias of RawState.CurrentHoldKey (retail's adjust_motion reads raw_state.current_holdkey directly). - The controller's grounded velocity + jump-launch writes used set_local_velocity's default autonomous:false, silently clearing LastMoveWasAutonomous and flipping the A3 dual dispatch to the interpreted branch for the local player. Both marked autonomous. EXPECTED-DIFFS (map §6, for the R3 visual pass): #45's 1.248x sidestep factor + ACDREAM_ANIM_SPEED_SCALE died with UpdatePlayerAnimation — local sidestep pacing now matches how remotes have always played; auto-walk-at-run plays walk-pace legs until R4; ApplyServerRunRate's apply_current_movement goes live (Branch-2 fast re-speed absorbs the echo — retail's own mid-run speed-change mechanism). Wire: outbound values stay input-derived (the L.2b-verified byte stream untouched; RawState-sourcing needs the map §2b cdb TODO and R7 owns outbound). MotionStateChanged = the old comparison minus the dead localAnimCmd leg, OR any edge fired. Full suite green: 3,731 passed. Live smoke: in-world, user-driven input through the new path (walk/turn/strafe), 12k entities, zero exceptions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
fc5a2cda28
commit
fb7beb706b
4 changed files with 177 additions and 287 deletions
|
|
@ -52,14 +52,12 @@ public readonly record struct MovementResult(
|
|||
float? SidestepSpeed,
|
||||
float? TurnSpeed,
|
||||
bool IsRunning = false,
|
||||
uint? LocalAnimationCommand = null, // which cycle to play on the local player (RunForward when running)
|
||||
// K-fix5 (2026-04-26): cycle-pace multiplier for the LOCAL animation
|
||||
// sequencer. Decoupled from ForwardSpeed so the wire can keep sending
|
||||
// 1.0 for WalkBackward (ACE-compatible) while the animation plays at
|
||||
// runRate × so the cycle visually matches the run-speed velocity.
|
||||
// Forward+Run = runRate (same as ForwardSpeed); Backward+Run, Strafe+Run
|
||||
// = runRate (where ForwardSpeed is 1.0 / null); everything else = 1.0.
|
||||
float LocalAnimationSpeed = 1f,
|
||||
bool JustLanded = false, // true on the single frame we transitioned airborne → grounded
|
||||
float? JumpExtent = null, // non-null when a jump was triggered this frame
|
||||
Vector3? JumpVelocity = null); // BODY-LOCAL launch velocity (forward/right/up relative to facing) — see PlayerMovementController jump path for the inverse-yaw conversion. Server rotates body→world on broadcast.
|
||||
|
|
@ -182,13 +180,25 @@ public sealed class PlayerMovementController
|
|||
// to drive the landing animation cycle.
|
||||
private bool _wasAirborneLastFrame;
|
||||
|
||||
// Previous frame's motion commands for change detection.
|
||||
// Previous frame's motion commands for change detection (wire cadence).
|
||||
private uint? _prevForwardCmd;
|
||||
private uint? _prevSidestepCmd;
|
||||
private uint? _prevTurnCmd;
|
||||
private float? _prevForwardSpeed;
|
||||
private bool _prevRunHold;
|
||||
private uint? _prevLocalAnimCmd;
|
||||
|
||||
// R3-W6: previous frame's HELD-KEY state — the edge detector feeding
|
||||
// retail's DoMotion/StopMotion/set_hold_run calls (retail's
|
||||
// CommandInterpreter altitude: motion dispatch happens on key EDGES,
|
||||
// never level-triggered per-frame).
|
||||
private bool _prevForwardHeld;
|
||||
private bool _prevBackwardHeld;
|
||||
private bool _prevStrafeLeftHeld;
|
||||
private bool _prevStrafeRightHeld;
|
||||
private bool _prevTurnLeftHeld;
|
||||
private bool _prevTurnRightHeld;
|
||||
private bool _prevRunHeld;
|
||||
private int _prevAutoWalkTurnDir;
|
||||
|
||||
// Heartbeat timer.
|
||||
// Cadence is 1.0 sec to match holtburger's
|
||||
|
|
@ -302,7 +312,7 @@ public sealed class PlayerMovementController
|
|||
// turn-first phase (rotating in place toward target) and after
|
||||
// arrival. Drives the animation cycle override: walking animation
|
||||
// only plays when the body is actually moving forward.
|
||||
private bool _autoWalkMovingForwardThisFrame;
|
||||
// R3-W6: _autoWalkMovingForwardThisFrame deleted with the LocalAnimationCommand override (forward legs come from DriveServerAutoWalk's own DoMotion).
|
||||
|
||||
// 2026-05-16 (issue #69 fix) — turn direction this frame.
|
||||
// +1 = rotating counter-clockwise (Yaw increasing) → TurnLeft cycle
|
||||
|
|
@ -556,7 +566,6 @@ public sealed class PlayerMovementController
|
|||
/// </summary>
|
||||
private bool DriveServerAutoWalk(float dt, MovementInput input)
|
||||
{
|
||||
_autoWalkMovingForwardThisFrame = false;
|
||||
_autoWalkTurnDirectionThisFrame = 0;
|
||||
if (!_autoWalkActive) return false;
|
||||
|
||||
|
|
@ -729,6 +738,8 @@ public sealed class PlayerMovementController
|
|||
// The motion-interpreter state also has to step out of
|
||||
// WalkForward so get_state_velocity (used downstream) reports
|
||||
// standing-velocity, not the prior frame's run-speed.
|
||||
// R3-W6 note: deliberately NOT StopCompletely — auto-walk is
|
||||
// KEEP-LIST until R4 (w6-cutover-map.md R4 risk note).
|
||||
_motion.DoMotion(MotionCommand.Ready, 1.0f);
|
||||
if (_body.OnWalkable)
|
||||
{
|
||||
|
|
@ -759,7 +770,6 @@ public sealed class PlayerMovementController
|
|||
forwardCmdSpeed = 1.0f;
|
||||
}
|
||||
|
||||
_autoWalkMovingForwardThisFrame = true;
|
||||
|
||||
// Update interpreted motion state — drives the animation cycle
|
||||
// via UpdatePlayerAnimation downstream + the MotionInterpreter's
|
||||
|
|
@ -836,7 +846,22 @@ public sealed class PlayerMovementController
|
|||
// reconstruct that run vector via get_state_velocity and the player would sprint
|
||||
// off in the old direction the instant input resumes. Resetting the forward
|
||||
// command to Ready makes the player arrive at rest.
|
||||
_motion.DoMotion(MotionCommand.Ready, 1.0f);
|
||||
// R3-W6: retail's teleport idle is a FULL stop (StopCompletely
|
||||
// 0x00527e40: resets fwd/sidestep/turn COMMANDS, zeroes velocity,
|
||||
// enqueues the A9 jump-snapshot node) — not a bare DoMotion(Ready).
|
||||
_motion.StopCompletely();
|
||||
// Reset the edge tracker: the stop wiped the motion state, so keys
|
||||
// still physically held must re-fire as press edges on the next
|
||||
// Update (matches the pre-W6 level-triggered behavior of walking
|
||||
// straight out of a teleport while W stays held).
|
||||
_prevForwardHeld = false;
|
||||
_prevBackwardHeld = false;
|
||||
_prevStrafeLeftHeld = false;
|
||||
_prevStrafeRightHeld = false;
|
||||
_prevTurnLeftHeld = false;
|
||||
_prevTurnRightHeld = false;
|
||||
_prevRunHeld = false;
|
||||
_prevAutoWalkTurnDir = 0;
|
||||
|
||||
// Reset physics clock so any subsequent update_object calls start fresh.
|
||||
_body.LastUpdateTime = 0.0;
|
||||
|
|
@ -889,40 +914,80 @@ public sealed class PlayerMovementController
|
|||
TurnSpeed: null);
|
||||
}
|
||||
|
||||
// ── D6.2: normalize input into the interpreted motion state ───────────
|
||||
// ONE RawMotionState from MovementInput drives the local velocity + turn
|
||||
// (retail CMotionInterp::apply_raw_movement 0x005287e0). RAW speeds are
|
||||
// 1.0 — apply_run_to_command applies the run rate, so passing a pre-scaled
|
||||
// speed would double-scale. adjust_motion normalizes backward →
|
||||
// WalkForward (×-0.65) and strafe-left → SideStepRight (×-1×1.248), so
|
||||
// get_state_velocity below is correct for ALL directions (retires the
|
||||
// hand-mirrored formulas; register TS-22). Skipped during server
|
||||
// auto-walk, which drives the body itself.
|
||||
// ── R3-W6: EDGE-DRIVEN retail input (replaces the D6.2 per-frame
|
||||
// RawMotionState rebuild — the level-triggered substitute for
|
||||
// retail's edge-triggered CommandInterpreter). Each key EDGE fires
|
||||
// DoMotion/StopMotion (0x00528d20/0x00528530) which mutate the
|
||||
// interpreter's OWN RawState via ApplyMotion/RemoveMotion and
|
||||
// dispatch through the funnel + DefaultSink — the SAME pipeline
|
||||
// remotes use. The Shift edge is retail's set_hold_run
|
||||
// (0x00528b70, caller 0x006b33ca shape: interrupt=true). RAW speeds
|
||||
// stay 1.0 (apply_run_to_command applies the run rate — pre-scaling
|
||||
// would double-scale; TS-22 unchanged). Skipped during server
|
||||
// auto-walk, which drives the body itself (edge state still
|
||||
// tracked so release edges fire correctly when auto-walk ends).
|
||||
bool motionEdgeFired = false;
|
||||
if (!autoWalkConsumedMotion)
|
||||
{
|
||||
var axisHold = input.Run ? HoldKey.Run : HoldKey.None;
|
||||
var raw = new RawMotionState
|
||||
var p = new AcDream.Core.Physics.Motion.MovementParameters();
|
||||
|
||||
// Shift/run edge FIRST — retail's set_hold_run re-applies
|
||||
// movement, so the hold-key state is current before any
|
||||
// same-frame directional dispatch.
|
||||
if (input.Run != _prevRunHeld)
|
||||
{
|
||||
CurrentHoldKey = axisHold,
|
||||
ForwardCommand = input.Forward ? MotionCommand.WalkForward
|
||||
: input.Backward ? MotionCommand.WalkBackward
|
||||
: MotionCommand.Ready,
|
||||
ForwardHoldKey = (input.Forward || input.Backward) ? axisHold : HoldKey.Invalid,
|
||||
ForwardSpeed = 1.0f,
|
||||
SidestepCommand = input.StrafeRight ? MotionCommand.SideStepRight
|
||||
: input.StrafeLeft ? MotionCommand.SideStepLeft
|
||||
: 0u,
|
||||
SidestepHoldKey = (input.StrafeRight || input.StrafeLeft) ? axisHold : HoldKey.Invalid,
|
||||
SidestepSpeed = 1.0f,
|
||||
TurnCommand = input.TurnRight ? MotionCommand.TurnRight
|
||||
: input.TurnLeft ? MotionCommand.TurnLeft
|
||||
: 0u,
|
||||
TurnHoldKey = (input.TurnRight || input.TurnLeft) ? axisHold : HoldKey.Invalid,
|
||||
TurnSpeed = 1.0f,
|
||||
};
|
||||
_motion.apply_raw_movement(raw);
|
||||
_motion.set_hold_run(input.Run, interrupt: true);
|
||||
motionEdgeFired = true;
|
||||
}
|
||||
|
||||
// Forward channel (W / S share one raw channel — retail
|
||||
// RawMotionState.ApplyMotion's forward-class switch). W wins on
|
||||
// a same-frame double-press; releasing one key while the
|
||||
// opposite is still held re-issues the survivor (equivalent to
|
||||
// the old level-triggered build, which always reflected the
|
||||
// currently-held set).
|
||||
if (input.Forward && !_prevForwardHeld)
|
||||
{ _motion.DoMotion(MotionCommand.WalkForward, p); motionEdgeFired = true; }
|
||||
else if (input.Backward && !_prevBackwardHeld && !input.Forward)
|
||||
{ _motion.DoMotion(MotionCommand.WalkBackward, p); motionEdgeFired = true; }
|
||||
if (!input.Forward && _prevForwardHeld)
|
||||
{
|
||||
if (input.Backward)
|
||||
_motion.DoMotion(MotionCommand.WalkBackward, p);
|
||||
else
|
||||
_motion.StopMotion(MotionCommand.WalkForward, p);
|
||||
motionEdgeFired = true;
|
||||
}
|
||||
else if (!input.Backward && _prevBackwardHeld && !input.Forward)
|
||||
{ _motion.StopMotion(MotionCommand.WalkBackward, p); motionEdgeFired = true; }
|
||||
|
||||
// Sidestep channel.
|
||||
if (input.StrafeRight && !_prevStrafeRightHeld)
|
||||
{ _motion.DoMotion(MotionCommand.SideStepRight, p); motionEdgeFired = true; }
|
||||
else if (input.StrafeLeft && !_prevStrafeLeftHeld)
|
||||
{ _motion.DoMotion(MotionCommand.SideStepLeft, p); motionEdgeFired = true; }
|
||||
else if (!input.StrafeRight && !input.StrafeLeft
|
||||
&& (_prevStrafeRightHeld || _prevStrafeLeftHeld))
|
||||
{ _motion.StopMotion(MotionCommand.SideStepRight, p); motionEdgeFired = true; }
|
||||
|
||||
// Turn channel.
|
||||
if (input.TurnRight && !_prevTurnRightHeld)
|
||||
{ _motion.DoMotion(MotionCommand.TurnRight, p); motionEdgeFired = true; }
|
||||
else if (input.TurnLeft && !_prevTurnLeftHeld)
|
||||
{ _motion.DoMotion(MotionCommand.TurnLeft, p); motionEdgeFired = true; }
|
||||
else if (!input.TurnRight && !input.TurnLeft
|
||||
&& (_prevTurnRightHeld || _prevTurnLeftHeld))
|
||||
{ _motion.StopMotion(MotionCommand.TurnRight, p); motionEdgeFired = true; }
|
||||
}
|
||||
|
||||
_prevForwardHeld = input.Forward;
|
||||
_prevBackwardHeld = input.Backward;
|
||||
_prevStrafeLeftHeld = input.StrafeLeft;
|
||||
_prevStrafeRightHeld = input.StrafeRight;
|
||||
_prevTurnLeftHeld = input.TurnLeft;
|
||||
_prevTurnRightHeld = input.TurnRight;
|
||||
_prevRunHeld = input.Run;
|
||||
|
||||
// ── 1. Apply turning from keyboard + mouse ────────────────────────────
|
||||
// 2026-05-16 — retail-faithful turn rate.
|
||||
// Anchor: docs/research/named-retail/acclient_2013_pseudo_c.txt
|
||||
|
|
@ -986,7 +1051,10 @@ public sealed class PlayerMovementController
|
|||
// that adjust_motion ran above. The hand-mirrored formulas are gone
|
||||
// (register TS-22 retired).
|
||||
var stateVel = _motion.get_state_velocity();
|
||||
_body.set_local_velocity(new Vector3(stateVel.X, stateVel.Y, savedWorldVz));
|
||||
// R3-W6: autonomous — this is local input-driven motion; the
|
||||
// default (false) silently cleared LastMoveWasAutonomous and
|
||||
// flipped the A3 dual dispatch to the interpreted branch.
|
||||
_body.set_local_velocity(new Vector3(stateVel.X, stateVel.Y, savedWorldVz), autonomous: true);
|
||||
}
|
||||
} // end of `if (!autoWalkConsumedMotion)` — section 2
|
||||
|
||||
|
|
@ -1004,6 +1072,12 @@ public sealed class PlayerMovementController
|
|||
{
|
||||
_jumpCharging = true;
|
||||
_jumpExtent = 0f;
|
||||
// R3-W6 (map R1): retail's charge_jump fires at charge
|
||||
// START (SmartBox/input boundary 0x0056afac) — the ONLY
|
||||
// place StandingLongJump arms (grounded + Ready + no
|
||||
// sidestep/turn). Never called by production code before
|
||||
// this line despite the W3 port.
|
||||
_motion.ChargeJump();
|
||||
}
|
||||
_jumpExtent = MathF.Min(_jumpExtent + dt * JumpChargeRate, 1.0f);
|
||||
}
|
||||
|
|
@ -1036,7 +1110,7 @@ public sealed class PlayerMovementController
|
|||
// client renders the jump in the same world direction the
|
||||
// server is broadcasting to observers. Same vector we just
|
||||
// sent in JumpAction — local + remote stay in sync.
|
||||
_body.set_local_velocity(outJumpVelocity.Value);
|
||||
_body.set_local_velocity(outJumpVelocity.Value, autonomous: true);
|
||||
}
|
||||
_jumpCharging = false;
|
||||
_jumpExtent = 0f;
|
||||
|
|
@ -1301,24 +1375,25 @@ public sealed class PlayerMovementController
|
|||
// from the character's run skill and auto-upgrades WalkForward+HoldKey.Run
|
||||
// → RunForward for observers. Sending the raw forward_speed=1.0 (omitted by
|
||||
// default-difference packing) still broadcasts RunForward @ runRate — a
|
||||
// retail observer saw +Acdream run at full pace. The earlier "wire must
|
||||
// send run_rate because ACE relays it" comment (citing MovementData.cs)
|
||||
// was WRONG. Our own local animation still wants the actual RunForward
|
||||
// cycle — carried separately in LocalAnimationCommand below.
|
||||
uint? localAnimCmd = null;
|
||||
// retail observer saw +Acdream run at full pace.
|
||||
// R3-W6: the LOCAL animation no longer needs a separate
|
||||
// LocalAnimationCommand — the walk→run promotion happens inside the
|
||||
// ported machinery (apply_raw_movement → apply_run_to_command
|
||||
// promotes WalkForward+HoldKey.Run → RunForward @ my_run_rate on the
|
||||
// interpreted side, which the DefaultSink dispatch plays).
|
||||
// NOTE (R7 scope): the wire values below stay derived from input —
|
||||
// the L.2b-verified byte stream is untouched; sourcing them from
|
||||
// _motion.RawState needs the cdb CommandInterpreter-boundary capture
|
||||
// the W6 map's §2b TODO flags, and R7 owns outbound anyway.
|
||||
if (input.Forward)
|
||||
{
|
||||
outForwardCmd = MotionCommand.WalkForward;
|
||||
outForwardSpeed = 1.0f; // RAW — ACE recomputes the broadcast speed
|
||||
localAnimCmd = (input.Run && _weenie.InqRunRate(out _))
|
||||
? MotionCommand.RunForward // local cycle is RunForward
|
||||
: MotionCommand.WalkForward;
|
||||
}
|
||||
else if (input.Backward)
|
||||
{
|
||||
outForwardCmd = MotionCommand.WalkBackward;
|
||||
outForwardSpeed = 1.0f;
|
||||
localAnimCmd = MotionCommand.WalkBackward;
|
||||
}
|
||||
|
||||
// Strafe: retail uses speed=1.0 for SideStep (see holtburger
|
||||
|
|
@ -1363,18 +1438,22 @@ public sealed class PlayerMovementController
|
|||
// observers. The forward_speed comparison below is retained (harmless — it
|
||||
// never fires now that forward_speed is constant) for defensiveness.
|
||||
bool runHold = input.Run;
|
||||
// R3-W6: the localAnimCmd leg is deleted with the synthesis layer;
|
||||
// motionEdgeFired (any DoMotion/StopMotion/set_hold_run edge this
|
||||
// frame) is OR-ed in — by construction an edge IS a state change
|
||||
// (retail dispatches only on edges), keeping the wire cadence
|
||||
// identical to the old output-comparison.
|
||||
bool changed = outForwardCmd != _prevForwardCmd
|
||||
|| outSidestepCmd != _prevSidestepCmd
|
||||
|| outTurnCmd != _prevTurnCmd
|
||||
|| !FloatsEqual(outForwardSpeed, _prevForwardSpeed)
|
||||
|| runHold != _prevRunHold
|
||||
|| localAnimCmd != _prevLocalAnimCmd;
|
||||
|| motionEdgeFired;
|
||||
_prevForwardCmd = outForwardCmd;
|
||||
_prevSidestepCmd = outSidestepCmd;
|
||||
_prevTurnCmd = outTurnCmd;
|
||||
_prevForwardSpeed = outForwardSpeed;
|
||||
_prevRunHold = runHold;
|
||||
_prevLocalAnimCmd = localAnimCmd;
|
||||
|
||||
static bool FloatsEqual(float? a, float? b)
|
||||
{
|
||||
|
|
@ -1434,50 +1513,31 @@ public sealed class PlayerMovementController
|
|||
|
||||
HeartbeatDue = groundedOnWalkable && sendThisFrame;
|
||||
|
||||
// K-fix5 (2026-04-26): local-animation-cycle pacing. Visual rate
|
||||
// should match the actual movement speed. For Forward+Run this is
|
||||
// already runRate (it equals ForwardSpeed). For Backward+Run and
|
||||
// Strafe+Run it must be runRate too even though the wire keeps
|
||||
// those at 1.0. Picking runMul (already computed above) keeps the
|
||||
// math in one place.
|
||||
// R3-W6: the K-fix5 LocalAnimationSpeed synthesis is DELETED — the
|
||||
// run pacing now comes from the ported machinery itself
|
||||
// (apply_run_to_command scales the interpreted speed by my_run_rate;
|
||||
// the DefaultSink dispatch plays the cycle at that speed — the same
|
||||
// source remotes use).
|
||||
bool anyDirectional = input.Forward || input.Backward
|
||||
|| input.StrafeLeft || input.StrafeRight;
|
||||
float localAnimSpeed = (input.Run && anyDirectional)
|
||||
? (_weenie.InqRunRate(out float vrrAnim) ? vrrAnim : 1f)
|
||||
: 1f;
|
||||
|
||||
// 2026-05-16 (issue #75) — server-initiated auto-walk drives
|
||||
// the local animation cycle directly:
|
||||
// - moving forward → WalkForward / RunForward (legs animate)
|
||||
// - turn-first phase → TurnLeft / TurnRight (issue #69 fix)
|
||||
// - aligned but pre-step / arrival → no override, falls to
|
||||
// the user-input section's default (idle)
|
||||
// UpdatePlayerAnimation reads LocalAnimationCommand +
|
||||
// LocalAnimationSpeed; without these overrides the body
|
||||
// translates/rotates without leg/arm animation. The motion
|
||||
// cycle commands here flow into the animation sequencer
|
||||
// ONLY — the wire-layer guard at GameWindow.cs:6419 prevents
|
||||
// them from leaking to a user-MoveToState packet during
|
||||
// auto-walk.
|
||||
if (_autoWalkMovingForwardThisFrame)
|
||||
// R3-W6 (#69 kept alive through the retail pipeline): auto-walk's
|
||||
// turn-first phase now dispatches the turn cycle as an EDGE through
|
||||
// DoMotion/StopMotion instead of the deleted LocalAnimationCommand
|
||||
// override. Forward legs during auto-walk come from
|
||||
// DriveServerAutoWalk's own DoMotion(WalkForward) (KEEP-LIST, R4
|
||||
// replaces). Expected-diff: auto-walk-at-run plays walk-pace legs
|
||||
// until R4's MoveToManager drives CanCharge walk/run selection.
|
||||
if (_autoWalkTurnDirectionThisFrame != _prevAutoWalkTurnDir)
|
||||
{
|
||||
if (_autoWalkInitiallyRunning && _weenie.InqRunRate(out float autoWalkRunRate))
|
||||
{
|
||||
localAnimCmd = MotionCommand.RunForward;
|
||||
localAnimSpeed = autoWalkRunRate;
|
||||
}
|
||||
var pTurn = new AcDream.Core.Physics.Motion.MovementParameters();
|
||||
if (_autoWalkTurnDirectionThisFrame > 0)
|
||||
_motion.DoMotion(MotionCommand.TurnLeft, pTurn);
|
||||
else if (_autoWalkTurnDirectionThisFrame < 0)
|
||||
_motion.DoMotion(MotionCommand.TurnRight, pTurn);
|
||||
else
|
||||
{
|
||||
localAnimCmd = MotionCommand.WalkForward;
|
||||
localAnimSpeed = 1f;
|
||||
}
|
||||
}
|
||||
else if (_autoWalkTurnDirectionThisFrame != 0)
|
||||
{
|
||||
localAnimCmd = _autoWalkTurnDirectionThisFrame > 0
|
||||
? MotionCommand.TurnLeft
|
||||
: MotionCommand.TurnRight;
|
||||
localAnimSpeed = 1f;
|
||||
_motion.StopMotion(MotionCommand.TurnRight, pTurn);
|
||||
_prevAutoWalkTurnDir = _autoWalkTurnDirectionThisFrame;
|
||||
}
|
||||
|
||||
return new MovementResult(
|
||||
|
|
@ -1500,8 +1560,6 @@ public sealed class PlayerMovementController
|
|||
// who then animated walk + dead-reckoned at walk speed while the
|
||||
// server position moved at run speed — visible as observer lag.
|
||||
IsRunning: input.Run && anyDirectional,
|
||||
LocalAnimationCommand: localAnimCmd,
|
||||
LocalAnimationSpeed: localAnimSpeed,
|
||||
JustLanded: justLanded,
|
||||
JumpExtent: outJumpExtent,
|
||||
JumpVelocity: outJumpVelocity);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue