fix(anim): jump animation via Falling SubState + kept PlayAction infra for emotes
The actual retail behavior for jump animations is a plain SubState swap —
NOT an Action overlay as I'd initially guessed. MotionCommand.Falling
(0x40000015) is a SubState cycle whose motion-table entries handle the
whole jump lifecycle:
- Links[(stance, RunForward)][Falling] = leap-into-air link
- Cycles[(stance, Falling)] = airborne cycle (loops)
- Links[(stance, Falling)][Ready/...] = landing link back to normal
Empirical verification from the diagnostic dump:
Links[0x003D0007] has 3 inner entries:
inner key: 0x41000003 (Ready)
inner key: 0x45000005 (WalkForward)
inner key: 0x40000015 (Falling) ← jackpot
SetCycle() already handles SubState + Links + Cycles resolution correctly,
so the whole fix is three lines:
if (!result.IsOnGround)
animCommand = MotionCommand.Falling;
What's in this commit:
- Added MotionCommand.Falling (0x40000015) constant + comments explaining
the retail jump-is-a-SubState flow
- GameWindow.UpdatePlayerAnimation swaps to Falling when airborne (the
cleanest possible implementation — motion table does all the work)
- Kept AnimationSequencer.PlayAction infrastructure (ported via Links
fallback + Modifiers fallback). Not needed for jump, but perfectly
valid for emotes like /wave, /bow (found in the same Links dict as
inner keys 0x13000080-0x13000083) and eventual combat attacks
- Kept MotionCommand.Jump / Jumpup / FallDown constants (unused for now
but useful reference if non-humanoid motion tables use them)
- Removed all diagnostic logging
What was learned (for future motion work):
- Retail's MotionTable.Cycles dict holds SubState loops (Ready, Walk,
Run, Falling, Crouch, etc.) by (style<<16) | (motion & 0xFFFFFF)
- MotionTable.Links dict holds TRANSITIONS between motions: the OUTER
key is the (style, fromMotion) combo; the INNER key is the TARGET
motion. The stored MotionData IS the link animation played during
the transition. This is what ACE's get_link traverses.
- MotionTable.Modifiers dict holds overlay motions (mask 0x20) — rare
for humanoids, only 8 TurnRight/SideStepRight stance variants
- Actions (mask 0x10) in retail ALSO go through Links — they're
transition animations FROM current substate, not overlays. Use
PlayAction (now correctly routed to Links dict) for them.
Jump animation now works retail-faithfully for running + jumping off.
Standing-jump behavior depends on whether the player's motion table
has a Ready→Falling link; SetCycle's fallback chain should handle it
via the style-level catch-all if the direct link is absent.
470 tests pass. Build clean.
This commit is contained in:
parent
08ea2c0af8
commit
6bce9b8019
3 changed files with 70 additions and 48 deletions
|
|
@ -3037,46 +3037,30 @@ public sealed class GameWindow : IDisposable
|
|||
{
|
||||
if (_dats is null) return;
|
||||
|
||||
// ── Action-motion events (jump / land) ─────────────────────────────
|
||||
// ── Airborne SubState (Falling) ────────────────────────────────────
|
||||
//
|
||||
// Retail does NOT animate jumps — confirmed via ACE's HandleActionJump
|
||||
// (Player.cs:914-915) which explicitly clears PendingMotions and
|
||||
// sets IsAnimating=false during the jump. The character keeps
|
||||
// whatever cycle it was on and the physics body arcs through the air.
|
||||
// Humanoid Setup MotionTables have NO entry for Jump (0x2500003B)
|
||||
// or FallDown (0x10000050) in the Modifiers dict — verified empirically
|
||||
// (only 8 TurnRight stance-variants + SideStepRight).
|
||||
// Retail models the jump-animation as a SubState swap to
|
||||
// MotionCommand.Falling (0x40000015) while airborne, NOT as an
|
||||
// Action overlay. Empirically verified: Links[(NonCombat,RunForward)]
|
||||
// has 3 transitions including 0x40000015 Falling. The SubState cycle
|
||||
// for Falling lives in Cycles[(style, Falling)] and loops while
|
||||
// airborne. On land, we transition back to whatever SubState the
|
||||
// motion input implies (Ready / WalkForward / RunForward).
|
||||
//
|
||||
// We still call PlayAction here as a no-op safety hatch: if a future
|
||||
// Setup / creature DOES carry a jump/fall modifier in its MotionTable
|
||||
// (e.g. a leaping-monster) the sequencer will pick it up for free.
|
||||
// For player humanoids, the lookup silently misses and nothing changes.
|
||||
if (result.JumpExtent.HasValue || result.JustLanded)
|
||||
{
|
||||
if (_entitiesByServerGuid.TryGetValue(_playerServerGuid, out var actionPe)
|
||||
&& _animatedEntities.TryGetValue(actionPe.Id, out var actionAe)
|
||||
&& actionAe.Sequencer is not null)
|
||||
{
|
||||
if (result.JumpExtent.HasValue)
|
||||
actionAe.Sequencer.PlayAction(AcDream.Core.Physics.MotionCommand.Jump);
|
||||
if (result.JustLanded)
|
||||
actionAe.Sequencer.PlayAction(AcDream.Core.Physics.MotionCommand.FallDown);
|
||||
}
|
||||
}
|
||||
// Implementation: force animCommand = Falling when airborne; the
|
||||
// existing SetCycle pathway resolves the link + cycle correctly and
|
||||
// the transition back happens naturally when airborne becomes false.
|
||||
|
||||
// Determine the animation command: forward takes priority, then sidestep,
|
||||
// then turn, then idle (Ready 0x41000003).
|
||||
// Determine the animation command: airborne takes priority (Falling
|
||||
// SubState), then forward, sidestep, turn, then idle (Ready 0x41000003).
|
||||
//
|
||||
// Note: AC's Jump (0x2500003b) is an Action motion (mask 0x25000000),
|
||||
// NOT a SubState cycle. Feeding it to the MotionTable resolver via
|
||||
// SetCycle produces a failed cycle lookup — which mis-renders the
|
||||
// character. Proper action playback needs a separate sequencer path
|
||||
// that honors the motion table's action queue; that's deferred.
|
||||
// For now the player stays in whatever cycle was active when they
|
||||
// jumped (usually walk/run or Ready) — animation wise it's wrong but
|
||||
// at least the character doesn't implode.
|
||||
// Airborne → Falling (retail behavior; see airborne note above).
|
||||
// Otherwise: LocalAnimationCommand (RunForward when running) preferred,
|
||||
// falling back to wire ForwardCommand (WalkForward / WalkBackward).
|
||||
uint animCommand;
|
||||
if (result.LocalAnimationCommand is { } localCmd)
|
||||
if (!result.IsOnGround)
|
||||
animCommand = AcDream.Core.Physics.MotionCommand.Falling;
|
||||
else if (result.LocalAnimationCommand is { } localCmd)
|
||||
animCommand = localCmd;
|
||||
else if (result.ForwardCommand is { } fwd)
|
||||
animCommand = fwd;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue