using AcDream.Core.Physics;
using Drw = DatReaderWriter.Enums.MotionCommand;
namespace AcDream.Core.Combat;
///
/// Retail-faithful combat animation planner for server-sent motion commands.
///
/// Retail evidence:
/// - ClientCombatSystem::ExecuteAttack (0x0056BB70) only sends the
/// targeted melee/missile GameAction and sets response state; it does not
/// locally choose or play a swing animation.
/// - ClientCombatSystem::HandleCommenceAttackEvent (0x0056AD20)
/// updates the power bar/busy state; it carries no MotionCommand.
/// - ACE Player_Melee.DoSwingMotion chooses a swing via
/// CombatManeuverTable.GetMotion and broadcasts that MotionCommand
/// in UpdateMotion.
///
/// So acdream treats combat GameEvents as state/UI signals and treats
/// UpdateMotion command IDs as the animation authority.
///
public static class CombatAnimationPlanner
{
public static CombatAnimationPlan PlanForEvent(CombatAnimationEvent combatEvent)
{
_ = combatEvent;
return CombatAnimationPlan.None;
}
public static CombatAnimationPlan PlanFromWireCommand(ushort wireCommand, float speedMod = 1f)
{
uint fullCommand = MotionCommandResolver.ReconstructFullCommand(wireCommand);
return PlanFromFullCommand(fullCommand, speedMod);
}
public static CombatAnimationPlan PlanFromFullCommand(uint fullCommand, float speedMod = 1f)
{
var kind = ClassifyMotionCommand(fullCommand);
if (kind == CombatAnimationKind.None)
return CombatAnimationPlan.None;
return new CombatAnimationPlan(
kind,
AnimationCommandRouter.Classify(fullCommand),
fullCommand,
speedMod);
}
public static CombatAnimationKind ClassifyMotionCommand(uint fullCommand)
{
return fullCommand switch
{
CombatAnimationMotionCommands.HandCombat
or CombatAnimationMotionCommands.SwordCombat
or CombatAnimationMotionCommands.SwordShieldCombat
or CombatAnimationMotionCommands.TwoHandedSwordCombat
or CombatAnimationMotionCommands.TwoHandedStaffCombat
or CombatAnimationMotionCommands.BowCombat
or CombatAnimationMotionCommands.CrossbowCombat
or CombatAnimationMotionCommands.SlingCombat
or CombatAnimationMotionCommands.DualWieldCombat
or CombatAnimationMotionCommands.ThrownWeaponCombat
or CombatAnimationMotionCommands.AtlatlCombat
or CombatAnimationMotionCommands.ThrownShieldCombat
or CombatAnimationMotionCommands.Magic => CombatAnimationKind.CombatStance,
CombatAnimationMotionCommands.ThrustMed
or CombatAnimationMotionCommands.ThrustLow
or CombatAnimationMotionCommands.ThrustHigh
or CombatAnimationMotionCommands.SlashHigh
or CombatAnimationMotionCommands.SlashMed
or CombatAnimationMotionCommands.SlashLow
or CombatAnimationMotionCommands.BackhandHigh
or CombatAnimationMotionCommands.BackhandMed
or CombatAnimationMotionCommands.BackhandLow
or CombatAnimationMotionCommands.DoubleSlashLow
or CombatAnimationMotionCommands.DoubleSlashMed
or CombatAnimationMotionCommands.DoubleSlashHigh
or CombatAnimationMotionCommands.TripleSlashLow
or CombatAnimationMotionCommands.TripleSlashMed
or CombatAnimationMotionCommands.TripleSlashHigh
or CombatAnimationMotionCommands.DoubleThrustLow
or CombatAnimationMotionCommands.DoubleThrustMed
or CombatAnimationMotionCommands.DoubleThrustHigh
or CombatAnimationMotionCommands.TripleThrustLow
or CombatAnimationMotionCommands.TripleThrustMed
or CombatAnimationMotionCommands.TripleThrustHigh
or CombatAnimationMotionCommands.OffhandSlashHigh
or CombatAnimationMotionCommands.OffhandSlashMed
or CombatAnimationMotionCommands.OffhandSlashLow
or CombatAnimationMotionCommands.OffhandThrustHigh
or CombatAnimationMotionCommands.OffhandThrustMed
or CombatAnimationMotionCommands.OffhandThrustLow
or CombatAnimationMotionCommands.OffhandDoubleSlashLow
or CombatAnimationMotionCommands.OffhandDoubleSlashMed
or CombatAnimationMotionCommands.OffhandDoubleSlashHigh
or CombatAnimationMotionCommands.OffhandTripleSlashLow
or CombatAnimationMotionCommands.OffhandTripleSlashMed
or CombatAnimationMotionCommands.OffhandTripleSlashHigh
or CombatAnimationMotionCommands.OffhandDoubleThrustLow
or CombatAnimationMotionCommands.OffhandDoubleThrustMed
or CombatAnimationMotionCommands.OffhandDoubleThrustHigh
or CombatAnimationMotionCommands.OffhandTripleThrustLow
or CombatAnimationMotionCommands.OffhandTripleThrustMed
or CombatAnimationMotionCommands.OffhandTripleThrustHigh
or CombatAnimationMotionCommands.OffhandKick
or CombatAnimationMotionCommands.PunchFastHigh
or CombatAnimationMotionCommands.PunchFastMed
or CombatAnimationMotionCommands.PunchFastLow
or CombatAnimationMotionCommands.PunchSlowHigh
or CombatAnimationMotionCommands.PunchSlowMed
or CombatAnimationMotionCommands.PunchSlowLow
or CombatAnimationMotionCommands.OffhandPunchFastHigh
or CombatAnimationMotionCommands.OffhandPunchFastMed
or CombatAnimationMotionCommands.OffhandPunchFastLow
or CombatAnimationMotionCommands.OffhandPunchSlowHigh
or CombatAnimationMotionCommands.OffhandPunchSlowMed
or CombatAnimationMotionCommands.OffhandPunchSlowLow => CombatAnimationKind.MeleeSwing,
CombatAnimationMotionCommands.Shoot
or CombatAnimationMotionCommands.MissileAttack1
or CombatAnimationMotionCommands.MissileAttack2
or CombatAnimationMotionCommands.MissileAttack3
or CombatAnimationMotionCommands.Reload => CombatAnimationKind.MissileAttack,
CombatAnimationMotionCommands.AttackHigh1
or CombatAnimationMotionCommands.AttackMed1
or CombatAnimationMotionCommands.AttackLow1
or CombatAnimationMotionCommands.AttackHigh2
or CombatAnimationMotionCommands.AttackMed2
or CombatAnimationMotionCommands.AttackLow2
or CombatAnimationMotionCommands.AttackHigh3
or CombatAnimationMotionCommands.AttackMed3
or CombatAnimationMotionCommands.AttackLow3
or CombatAnimationMotionCommands.AttackHigh4
or CombatAnimationMotionCommands.AttackMed4
or CombatAnimationMotionCommands.AttackLow4
or CombatAnimationMotionCommands.AttackHigh5
or CombatAnimationMotionCommands.AttackMed5
or CombatAnimationMotionCommands.AttackLow5
or CombatAnimationMotionCommands.AttackHigh6
or CombatAnimationMotionCommands.AttackMed6
or CombatAnimationMotionCommands.AttackLow6 => CombatAnimationKind.CreatureAttack,
CombatAnimationMotionCommands.CastSpell
or CombatAnimationMotionCommands.UseMagicStaff
or CombatAnimationMotionCommands.UseMagicWand => CombatAnimationKind.SpellCast,
CombatAnimationMotionCommands.FallDown
or CombatAnimationMotionCommands.Twitch1
or CombatAnimationMotionCommands.Twitch2
or CombatAnimationMotionCommands.Twitch3
or CombatAnimationMotionCommands.Twitch4
or CombatAnimationMotionCommands.StaggerBackward
or CombatAnimationMotionCommands.StaggerForward
or CombatAnimationMotionCommands.Sanctuary => CombatAnimationKind.HitReaction,
MotionCommand.Dead => CombatAnimationKind.Death,
_ => CombatAnimationKind.None,
};
}
}
public readonly record struct CombatAnimationPlan(
CombatAnimationKind Kind,
AnimationCommandRouteKind RouteKind,
uint MotionCommand,
float SpeedMod)
{
public static CombatAnimationPlan None { get; } = new(
CombatAnimationKind.None,
AnimationCommandRouteKind.None,
0u,
0f);
public bool HasMotion => Kind != CombatAnimationKind.None && MotionCommand != 0;
}
public enum CombatAnimationEvent
{
CombatCommenceAttack,
AttackDone,
AttackerNotification,
DefenderNotification,
EvasionAttackerNotification,
EvasionDefenderNotification,
VictimNotification,
KillerNotification,
}
public enum CombatAnimationKind
{
None = 0,
CombatStance,
MeleeSwing,
MissileAttack,
CreatureAttack,
SpellCast,
HitReaction,
Death,
}
///
/// The full 32-bit retail MotionCommand values the planner classifies, each
/// derived DIRECTLY from (Chorizite.DatReaderWriter — the
/// same enum ACE broadcasts and that the local DAT MotionTables key on) so
/// the values can never drift from the wire again.
///
///
/// History (ISSUES #159): these were previously hand-transcribed from the
/// Sept 2013 EoR decomp's command_ids table, which numbers the whole
/// late-combat block (Offhand* / Attack4-6 / Punch*)
/// THREE lower than ACE/DRW — a contiguous low-word "+3" shift that begins
/// around SnowAngelState (0x115). Against a live ACE server those
/// commands silently misclassified: the resolver returned the correct ACE
/// value (e.g. wire 0x0173 -> 0x10000173 OffhandSlashHigh),
/// but the planner's constant held the 2013 value (0x10000170), so no
/// match. Reload was worse — hardcoded 0x100000D4, a value
/// absent from DRW entirely; the real ACE/DRW Reload is the
/// 0x40000016 SubState. Deriving from the enum by name fixes the whole
/// block at once and drops ~80 magic numbers.
/// See docs/research/2026-06-26-ace-vs-2013-motion-command-gap.md.
///
///
internal static class CombatAnimationMotionCommands
{
// Combat stances (Style class 0x80). Already correct before #159.
public const uint HandCombat = (uint)Drw.HandCombat;
public const uint SwordCombat = (uint)Drw.SwordCombat;
public const uint BowCombat = (uint)Drw.BowCombat;
public const uint SwordShieldCombat = (uint)Drw.SwordShieldCombat;
public const uint CrossbowCombat = (uint)Drw.CrossbowCombat;
public const uint SlingCombat = (uint)Drw.SlingCombat;
public const uint TwoHandedSwordCombat = (uint)Drw.TwoHandedSwordCombat;
public const uint TwoHandedStaffCombat = (uint)Drw.TwoHandedStaffCombat;
public const uint DualWieldCombat = (uint)Drw.DualWieldCombat;
public const uint ThrownWeaponCombat = (uint)Drw.ThrownWeaponCombat;
public const uint Magic = (uint)Drw.Magic;
public const uint AtlatlCombat = (uint)Drw.AtlatlCombat;
public const uint ThrownShieldCombat = (uint)Drw.ThrownShieldCombat;
// Hit reactions / staggers (Action class 0x10, unshifted).
public const uint FallDown = (uint)Drw.FallDown;
public const uint Twitch1 = (uint)Drw.Twitch1;
public const uint Twitch2 = (uint)Drw.Twitch2;
public const uint Twitch3 = (uint)Drw.Twitch3;
public const uint Twitch4 = (uint)Drw.Twitch4;
public const uint StaggerBackward = (uint)Drw.StaggerBackward;
public const uint StaggerForward = (uint)Drw.StaggerForward;
public const uint Sanctuary = (uint)Drw.Sanctuary;
// Single melee attacks (Action class 0x10, unshifted).
public const uint ThrustMed = (uint)Drw.ThrustMed;
public const uint ThrustLow = (uint)Drw.ThrustLow;
public const uint ThrustHigh = (uint)Drw.ThrustHigh;
public const uint SlashHigh = (uint)Drw.SlashHigh;
public const uint SlashMed = (uint)Drw.SlashMed;
public const uint SlashLow = (uint)Drw.SlashLow;
public const uint BackhandHigh = (uint)Drw.BackhandHigh;
public const uint BackhandMed = (uint)Drw.BackhandMed;
public const uint BackhandLow = (uint)Drw.BackhandLow;
// Missile attacks (Action 0x10) + reload (SubState 0x40, NOT the dead
// 2013 0x100000D4 — the real DRW Reload is 0x40000016).
public const uint Shoot = (uint)Drw.Shoot;
public const uint MissileAttack1 = (uint)Drw.MissileAttack1;
public const uint MissileAttack2 = (uint)Drw.MissileAttack2;
public const uint MissileAttack3 = (uint)Drw.MissileAttack3;
public const uint Reload = (uint)Drw.Reload;
// Creature attacks 1-6 (Action class 0x10). 1-3 are the unshifted low
// block (0x62-0x6A); 4-6 live in the shifted late block (0x186+).
public const uint AttackHigh1 = (uint)Drw.AttackHigh1;
public const uint AttackMed1 = (uint)Drw.AttackMed1;
public const uint AttackLow1 = (uint)Drw.AttackLow1;
public const uint AttackHigh2 = (uint)Drw.AttackHigh2;
public const uint AttackMed2 = (uint)Drw.AttackMed2;
public const uint AttackLow2 = (uint)Drw.AttackLow2;
public const uint AttackHigh3 = (uint)Drw.AttackHigh3;
public const uint AttackMed3 = (uint)Drw.AttackMed3;
public const uint AttackLow3 = (uint)Drw.AttackLow3;
public const uint AttackHigh4 = (uint)Drw.AttackHigh4;
public const uint AttackMed4 = (uint)Drw.AttackMed4;
public const uint AttackLow4 = (uint)Drw.AttackLow4;
public const uint AttackHigh5 = (uint)Drw.AttackHigh5;
public const uint AttackMed5 = (uint)Drw.AttackMed5;
public const uint AttackLow5 = (uint)Drw.AttackLow5;
public const uint AttackHigh6 = (uint)Drw.AttackHigh6;
public const uint AttackMed6 = (uint)Drw.AttackMed6;
public const uint AttackLow6 = (uint)Drw.AttackLow6;
// Spell casts (SubState class 0x40).
public const uint CastSpell = (uint)Drw.CastSpell;
public const uint UseMagicStaff = (uint)Drw.UseMagicStaff;
public const uint UseMagicWand = (uint)Drw.UseMagicWand;
// Multi-strike melee (Action class 0x10, unshifted 0x11F-0x12A).
public const uint DoubleSlashLow = (uint)Drw.DoubleSlashLow;
public const uint DoubleSlashMed = (uint)Drw.DoubleSlashMed;
public const uint DoubleSlashHigh = (uint)Drw.DoubleSlashHigh;
public const uint TripleSlashLow = (uint)Drw.TripleSlashLow;
public const uint TripleSlashMed = (uint)Drw.TripleSlashMed;
public const uint TripleSlashHigh = (uint)Drw.TripleSlashHigh;
public const uint DoubleThrustLow = (uint)Drw.DoubleThrustLow;
public const uint DoubleThrustMed = (uint)Drw.DoubleThrustMed;
public const uint DoubleThrustHigh = (uint)Drw.DoubleThrustHigh;
public const uint TripleThrustLow = (uint)Drw.TripleThrustLow;
public const uint TripleThrustMed = (uint)Drw.TripleThrustMed;
public const uint TripleThrustHigh = (uint)Drw.TripleThrustHigh;
// Offhand strikes (Action class 0x10, shifted late block 0x173+).
public const uint OffhandSlashHigh = (uint)Drw.OffhandSlashHigh;
public const uint OffhandSlashMed = (uint)Drw.OffhandSlashMed;
public const uint OffhandSlashLow = (uint)Drw.OffhandSlashLow;
public const uint OffhandThrustHigh = (uint)Drw.OffhandThrustHigh;
public const uint OffhandThrustMed = (uint)Drw.OffhandThrustMed;
public const uint OffhandThrustLow = (uint)Drw.OffhandThrustLow;
public const uint OffhandDoubleSlashLow = (uint)Drw.OffhandDoubleSlashLow;
public const uint OffhandDoubleSlashMed = (uint)Drw.OffhandDoubleSlashMed;
public const uint OffhandDoubleSlashHigh = (uint)Drw.OffhandDoubleSlashHigh;
public const uint OffhandTripleSlashLow = (uint)Drw.OffhandTripleSlashLow;
public const uint OffhandTripleSlashMed = (uint)Drw.OffhandTripleSlashMed;
public const uint OffhandTripleSlashHigh = (uint)Drw.OffhandTripleSlashHigh;
public const uint OffhandDoubleThrustLow = (uint)Drw.OffhandDoubleThrustLow;
public const uint OffhandDoubleThrustMed = (uint)Drw.OffhandDoubleThrustMed;
public const uint OffhandDoubleThrustHigh = (uint)Drw.OffhandDoubleThrustHigh;
public const uint OffhandTripleThrustLow = (uint)Drw.OffhandTripleThrustLow;
public const uint OffhandTripleThrustMed = (uint)Drw.OffhandTripleThrustMed;
public const uint OffhandTripleThrustHigh = (uint)Drw.OffhandTripleThrustHigh;
public const uint OffhandKick = (uint)Drw.OffhandKick;
// Punches (Action class 0x10, shifted late block).
public const uint PunchFastHigh = (uint)Drw.PunchFastHigh;
public const uint PunchFastMed = (uint)Drw.PunchFastMed;
public const uint PunchFastLow = (uint)Drw.PunchFastLow;
public const uint PunchSlowHigh = (uint)Drw.PunchSlowHigh;
public const uint PunchSlowMed = (uint)Drw.PunchSlowMed;
public const uint PunchSlowLow = (uint)Drw.PunchSlowLow;
public const uint OffhandPunchFastHigh = (uint)Drw.OffhandPunchFastHigh;
public const uint OffhandPunchFastMed = (uint)Drw.OffhandPunchFastMed;
public const uint OffhandPunchFastLow = (uint)Drw.OffhandPunchFastLow;
public const uint OffhandPunchSlowHigh = (uint)Drw.OffhandPunchSlowHigh;
public const uint OffhandPunchSlowMed = (uint)Drw.OffhandPunchSlowMed;
public const uint OffhandPunchSlowLow = (uint)Drw.OffhandPunchSlowLow;
}