fix(anim): Phase L.1b route motion commands
This commit is contained in:
parent
1c69670392
commit
460f95cb42
6 changed files with 218 additions and 53 deletions
97
src/AcDream.Core/Physics/AnimationCommandRouter.cs
Normal file
97
src/AcDream.Core/Physics/AnimationCommandRouter.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
namespace AcDream.Core.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Central routing for full retail MotionCommand values after the wire's
|
||||
/// 16-bit command id has been reconstructed.
|
||||
///
|
||||
/// Retail/ACE split motion commands by class mask:
|
||||
/// - Action and ChatEmote commands play through link/action data.
|
||||
/// - Modifier commands play through modifier data.
|
||||
/// - SubState commands become the new cyclic state.
|
||||
/// - Style/UI/Toggle commands do not directly drive an animation overlay here.
|
||||
///
|
||||
/// References:
|
||||
/// CMotionTable::GetObjectSequence 0x00522860,
|
||||
/// CMotionInterp::DoInterpretedMotion 0x00528360,
|
||||
/// ACE MotionTable.GetObjectSequence, and
|
||||
/// docs/research/deepdives/r03-motion-animation.md section 3.
|
||||
/// </summary>
|
||||
public static class AnimationCommandRouter
|
||||
{
|
||||
private const uint ActionMask = 0x10000000u;
|
||||
private const uint ModifierMask = 0x20000000u;
|
||||
private const uint SubStateMask = 0x40000000u;
|
||||
private const uint ClassMask = 0xFF000000u;
|
||||
|
||||
/// <summary>
|
||||
/// Classifies a reconstructed full MotionCommand.
|
||||
/// </summary>
|
||||
public static AnimationCommandRouteKind Classify(uint fullCommand)
|
||||
{
|
||||
if (fullCommand == 0)
|
||||
return AnimationCommandRouteKind.None;
|
||||
|
||||
uint cls = fullCommand & ClassMask;
|
||||
if (cls == 0x12000000u || cls == 0x13000000u)
|
||||
return AnimationCommandRouteKind.ChatEmote;
|
||||
|
||||
if ((fullCommand & ModifierMask) != 0)
|
||||
return AnimationCommandRouteKind.Modifier;
|
||||
|
||||
if ((fullCommand & ActionMask) != 0)
|
||||
return AnimationCommandRouteKind.Action;
|
||||
|
||||
if ((fullCommand & SubStateMask) != 0)
|
||||
return AnimationCommandRouteKind.SubState;
|
||||
|
||||
return AnimationCommandRouteKind.Ignored;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reconstructs and routes a 16-bit wire command.
|
||||
/// </summary>
|
||||
public static AnimationCommandRouteKind RouteWireCommand(
|
||||
AnimationSequencer sequencer,
|
||||
uint currentStyle,
|
||||
ushort wireCommand,
|
||||
float speedMod = 1f)
|
||||
{
|
||||
uint fullCommand = MotionCommandResolver.ReconstructFullCommand(wireCommand);
|
||||
return RouteFullCommand(sequencer, currentStyle, fullCommand, speedMod);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Routes a full MotionCommand to the matching sequencer API.
|
||||
/// </summary>
|
||||
public static AnimationCommandRouteKind RouteFullCommand(
|
||||
AnimationSequencer sequencer,
|
||||
uint currentStyle,
|
||||
uint fullCommand,
|
||||
float speedMod = 1f)
|
||||
{
|
||||
var route = Classify(fullCommand);
|
||||
switch (route)
|
||||
{
|
||||
case AnimationCommandRouteKind.Action:
|
||||
case AnimationCommandRouteKind.Modifier:
|
||||
case AnimationCommandRouteKind.ChatEmote:
|
||||
sequencer.PlayAction(fullCommand, speedMod);
|
||||
break;
|
||||
case AnimationCommandRouteKind.SubState:
|
||||
sequencer.SetCycle(currentStyle, fullCommand, speedMod);
|
||||
break;
|
||||
}
|
||||
|
||||
return route;
|
||||
}
|
||||
}
|
||||
|
||||
public enum AnimationCommandRouteKind
|
||||
{
|
||||
None = 0,
|
||||
Action,
|
||||
Modifier,
|
||||
ChatEmote,
|
||||
SubState,
|
||||
Ignored,
|
||||
}
|
||||
|
|
@ -72,12 +72,20 @@ public static class MotionCommand
|
|||
/// regular SetCycle transition.
|
||||
/// </summary>
|
||||
public const uint FallDown = 0x10000050u;
|
||||
/// <summary>0x10000057 — Dead.</summary>
|
||||
public const uint Dead = 0x10000057u;
|
||||
/// <summary>0x40000011 - persistent dead substate.</summary>
|
||||
public const uint Dead = 0x40000011u;
|
||||
/// <summary>0x10000057 - Sanctuary death-trigger action.</summary>
|
||||
public const uint Sanctuary = 0x10000057u;
|
||||
/// <summary>0x41000012 - crouching substate.</summary>
|
||||
public const uint Crouch = 0x41000012u;
|
||||
/// <summary>0x41000013 - sitting substate.</summary>
|
||||
public const uint Sitting = 0x41000013u;
|
||||
/// <summary>0x41000014 - sleeping substate.</summary>
|
||||
public const uint Sleeping = 0x41000014u;
|
||||
/// <summary>0x41000011 — Crouch lower bound for blocked-jump check.</summary>
|
||||
public const uint CrouchLowerBound = 0x41000011u;
|
||||
/// <summary>0x41000014 — upper bound of crouch/sit/sleep range.</summary>
|
||||
public const uint CrouchUpperBound = 0x41000014u;
|
||||
/// <summary>0x41000015 - exclusive upper bound of crouch/sit/sleep range.</summary>
|
||||
public const uint CrouchUpperExclusive = 0x41000015u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -819,7 +827,7 @@ public sealed class MotionInterpreter
|
|||
/// if WeenieObj != null AND WeenieObj.CanJump(JumpExtent) returns false:
|
||||
/// return 0x49
|
||||
/// uVar1 = InterpretedState.ForwardCommand
|
||||
/// if uVar1 == 0x40000008 (Fallen) OR uVar1 == 0x10000057 (Dead):
|
||||
/// if uVar1 == 0x40000008 (Fallen) OR uVar1 == 0x40000011 (Dead):
|
||||
/// return 0x48
|
||||
/// if 0x41000011 < uVar1 < 0x41000015 (crouch/sit/sleep range):
|
||||
/// return 0x48
|
||||
|
|
@ -850,7 +858,7 @@ public sealed class MotionInterpreter
|
|||
return false;
|
||||
|
||||
// Crouch / sit / sleep range (0x41000011 < fwd < 0x41000015).
|
||||
if (fwd > MotionCommand.CrouchLowerBound && fwd < MotionCommand.CrouchUpperBound)
|
||||
if (fwd > MotionCommand.CrouchLowerBound && fwd < MotionCommand.CrouchUpperExclusive)
|
||||
return false;
|
||||
|
||||
// Need Gravity flag + Contact + OnWalkable for ground-based motion.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue