fix(anim): implement adjust_motion — TurnLeft/SideStepLeft play backward
ROOT CAUSE FIX for missing left-side animations. The AC client's MotionTable has NO cycles for TurnLeft (0x000E), SideStepLeft (0x0010), or WalkBackward (0x0006). The real client calls adjust_motion() which remaps these to their right-side equivalents with NEGATIVE speed before looking up the cycle. Then multiply_framerate() swaps LowFrame↔HighFrame so the animation plays backward. Source: ACE MotionInterp.cs:394-428, decompiled FUN_005267E0. Changes: - AnimationSequencer.SetCycle: adds adjust_motion block that remaps left→right with speed *= -1 (TurnLeft, SideStepLeft) or speed *= -0.65 (WalkBackward = BackwardsFactor) - LoadAnimNode: when framerate < 0, swaps Low↔High (matching the decompiled multiply_framerate) - GameWindow.UpdatePlayerAnimation: passes original animCommand to SetCycle (sequencer handles remapping internally), keeps legacy fallback for non-sequencer entities Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0e66078e57
commit
67b51a3e6f
3 changed files with 339 additions and 35 deletions
|
|
@ -173,32 +173,55 @@ public sealed class AnimationSequencer
|
|||
/// <param name="speedMod">Speed multiplier applied to framerates (1.0 = normal).</param>
|
||||
public void SetCycle(uint style, uint motion, float speedMod = 1f)
|
||||
{
|
||||
// ── adjust_motion: remap left→right variants with negative speed ───
|
||||
// The AC client's MotionTable has NO cycles for TurnLeft, SideStepLeft,
|
||||
// or WalkBackward. These are played as their right-side / forward
|
||||
// equivalents with negative framerate (animation runs backward).
|
||||
// ACE: MotionInterp.cs:394-428
|
||||
uint adjustedMotion = motion;
|
||||
float adjustedSpeed = speedMod;
|
||||
switch (motion & 0xFFFFu)
|
||||
{
|
||||
case 0x000E: // TurnLeft → TurnRight
|
||||
adjustedMotion = (motion & 0xFFFF0000u) | 0x000Du;
|
||||
adjustedSpeed *= -1f;
|
||||
break;
|
||||
case 0x0010: // SideStepLeft → SideStepRight
|
||||
adjustedMotion = (motion & 0xFFFF0000u) | 0x000Fu;
|
||||
adjustedSpeed *= -1f;
|
||||
break;
|
||||
case 0x0006: // WalkBackward → WalkForward
|
||||
adjustedMotion = (motion & 0xFFFF0000u) | 0x0005u;
|
||||
adjustedSpeed *= -0.65f; // BackwardsFactor from ACE
|
||||
break;
|
||||
}
|
||||
|
||||
// Fast-path: already playing this exact motion at the same speed.
|
||||
if (CurrentStyle == style && CurrentMotion == motion
|
||||
&& _firstCyclic != null && _queue.Count > 0)
|
||||
return;
|
||||
|
||||
// Resolve transition link (currentSubstate → newMotion).
|
||||
// Resolve transition link (currentSubstate → adjustedMotion).
|
||||
MotionData? linkData = CurrentMotion != 0
|
||||
? GetLink(style, CurrentMotion, motion)
|
||||
? GetLink(style, CurrentMotion, adjustedMotion)
|
||||
: null;
|
||||
|
||||
// Resolve target cycle.
|
||||
int cycleKey = (int)(((style & 0xFFFFu) << 16) | (motion & 0xFFFFFFu));
|
||||
// Resolve target cycle using the ADJUSTED motion (TurnRight, not TurnLeft).
|
||||
int cycleKey = (int)(((style & 0xFFFFu) << 16) | (adjustedMotion & 0xFFFFFFu));
|
||||
_mtable.Cycles.TryGetValue(cycleKey, out var cycleData);
|
||||
|
||||
// Clear the old cyclic tail; keep any non-cyclic head that hasn't
|
||||
// been played yet (ACE behaviour: non-cyclic anims drain naturally).
|
||||
ClearCyclicTail();
|
||||
|
||||
// Enqueue link frames.
|
||||
// Enqueue link frames (with adjusted speed for left→right remapping).
|
||||
if (linkData is { Anims.Count: > 0 })
|
||||
EnqueueMotionData(linkData, speedMod, isLooping: false);
|
||||
EnqueueMotionData(linkData, adjustedSpeed, isLooping: false);
|
||||
|
||||
// Enqueue new cycle.
|
||||
if (cycleData is { Anims.Count: > 0 })
|
||||
{
|
||||
EnqueueMotionData(cycleData, speedMod, isLooping: true);
|
||||
EnqueueMotionData(cycleData, adjustedSpeed, isLooping: true);
|
||||
}
|
||||
else if (_queue.Count == 0)
|
||||
{
|
||||
|
|
@ -366,9 +389,23 @@ public sealed class AnimationSequencer
|
|||
if (low >= numFrames) low = numFrames - 1;
|
||||
if (high >= numFrames) high = numFrames - 1;
|
||||
if (low < 0) low = 0;
|
||||
if (low > high) high = low;
|
||||
|
||||
float fr = ad.Framerate * speedMod;
|
||||
|
||||
// multiply_framerate: when speed is negative (TurnLeft, SideStepLeft),
|
||||
// swap Low↔High so the animation plays backward. This is exactly what
|
||||
// the decompiled FUN_005267E0 does. ACE: AnimData.GetFramerate(speed).
|
||||
// After swap, LowFrame > HighFrame — the Advance loop handles this
|
||||
// by checking negative frametime against LowFrame (the higher value).
|
||||
if (fr < 0f)
|
||||
{
|
||||
(low, high) = (high, low);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (low > high) high = low; // only clamp for positive-speed case
|
||||
}
|
||||
|
||||
return new AnimNode(anim, fr, low, high, isLooping);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue