feat(anim): Phase L.1c classify combat animation commands

This commit is contained in:
Erik 2026-04-28 11:37:49 +02:00
parent 268af82e28
commit 831392a7b2
3 changed files with 420 additions and 0 deletions

View file

@ -0,0 +1,278 @@
using AcDream.Core.Physics;
namespace AcDream.Core.Combat;
/// <summary>
/// Retail-faithful combat animation planner for server-sent motion commands.
///
/// Retail evidence:
/// - <c>ClientCombatSystem::ExecuteAttack</c> (0x0056BB70) only sends the
/// targeted melee/missile GameAction and sets response state; it does not
/// locally choose or play a swing animation.
/// - <c>ClientCombatSystem::HandleCommenceAttackEvent</c> (0x0056AD20)
/// updates the power bar/busy state; it carries no MotionCommand.
/// - ACE <c>Player_Melee.DoSwingMotion</c> chooses a swing via
/// <c>CombatManeuverTable.GetMotion</c> and broadcasts that MotionCommand
/// in <c>UpdateMotion</c>.
///
/// So acdream treats combat GameEvents as state/UI signals and treats
/// UpdateMotion command IDs as the animation authority.
/// </summary>
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.BowCombat
or CombatAnimationMotionCommands.CrossbowCombat
or CombatAnimationMotionCommands.SlingCombat
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 => 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,
}
internal static class CombatAnimationMotionCommands
{
public const uint HandCombat = 0x8000003Cu;
public const uint SwordCombat = 0x8000003Eu;
public const uint BowCombat = 0x8000003Fu;
public const uint SwordShieldCombat = 0x80000040u;
public const uint CrossbowCombat = 0x80000041u;
public const uint TwoHandedSwordCombat = 0x80000046u;
public const uint SlingCombat = 0x80000047u;
public const uint Magic = 0x80000049u;
public const uint AtlatlCombat = 0x8000013Bu;
public const uint ThrownShieldCombat = 0x8000013Cu;
public const uint FallDown = 0x10000050u;
public const uint Twitch1 = 0x10000051u;
public const uint Twitch2 = 0x10000052u;
public const uint Twitch3 = 0x10000053u;
public const uint Twitch4 = 0x10000054u;
public const uint StaggerBackward = 0x10000055u;
public const uint StaggerForward = 0x10000056u;
public const uint Sanctuary = 0x10000057u;
public const uint ThrustMed = 0x10000058u;
public const uint ThrustLow = 0x10000059u;
public const uint ThrustHigh = 0x1000005Au;
public const uint SlashHigh = 0x1000005Bu;
public const uint SlashMed = 0x1000005Cu;
public const uint SlashLow = 0x1000005Du;
public const uint BackhandHigh = 0x1000005Eu;
public const uint BackhandMed = 0x1000005Fu;
public const uint BackhandLow = 0x10000060u;
public const uint Shoot = 0x10000061u;
public const uint AttackHigh1 = 0x10000062u;
public const uint AttackMed1 = 0x10000063u;
public const uint AttackLow1 = 0x10000064u;
public const uint AttackHigh2 = 0x10000065u;
public const uint AttackMed2 = 0x10000066u;
public const uint AttackLow2 = 0x10000067u;
public const uint AttackHigh3 = 0x10000068u;
public const uint AttackMed3 = 0x10000069u;
public const uint AttackLow3 = 0x1000006Au;
public const uint MissileAttack1 = 0x100000D0u;
public const uint MissileAttack2 = 0x100000D1u;
public const uint MissileAttack3 = 0x100000D2u;
public const uint CastSpell = 0x400000D3u;
public const uint Reload = 0x100000D4u;
public const uint UseMagicStaff = 0x400000E0u;
public const uint UseMagicWand = 0x400000E1u;
public const uint DoubleSlashLow = 0x1000011Fu;
public const uint DoubleSlashMed = 0x10000120u;
public const uint DoubleSlashHigh = 0x10000121u;
public const uint TripleSlashLow = 0x10000122u;
public const uint TripleSlashMed = 0x10000123u;
public const uint TripleSlashHigh = 0x10000124u;
public const uint DoubleThrustLow = 0x10000125u;
public const uint DoubleThrustMed = 0x10000126u;
public const uint DoubleThrustHigh = 0x10000127u;
public const uint TripleThrustLow = 0x10000128u;
public const uint TripleThrustMed = 0x10000129u;
public const uint TripleThrustHigh = 0x1000012Au;
public const uint OffhandSlashHigh = 0x10000173u;
public const uint OffhandSlashMed = 0x10000174u;
public const uint OffhandSlashLow = 0x10000175u;
public const uint OffhandThrustHigh = 0x10000176u;
public const uint OffhandThrustMed = 0x10000177u;
public const uint OffhandThrustLow = 0x10000178u;
public const uint OffhandDoubleSlashLow = 0x10000179u;
public const uint OffhandDoubleSlashMed = 0x1000017Au;
public const uint OffhandDoubleSlashHigh = 0x1000017Bu;
public const uint OffhandTripleSlashLow = 0x1000017Cu;
public const uint OffhandTripleSlashMed = 0x1000017Du;
public const uint OffhandTripleSlashHigh = 0x1000017Eu;
public const uint OffhandDoubleThrustLow = 0x1000017Fu;
public const uint OffhandDoubleThrustMed = 0x10000180u;
public const uint OffhandDoubleThrustHigh = 0x10000181u;
public const uint OffhandTripleThrustLow = 0x10000182u;
public const uint OffhandTripleThrustMed = 0x10000183u;
public const uint OffhandTripleThrustHigh = 0x10000184u;
public const uint OffhandKick = 0x10000185u;
public const uint AttackHigh4 = 0x10000186u;
public const uint AttackMed4 = 0x10000187u;
public const uint AttackLow4 = 0x10000188u;
public const uint AttackHigh5 = 0x10000189u;
public const uint AttackMed5 = 0x1000018Au;
public const uint AttackLow5 = 0x1000018Bu;
public const uint AttackHigh6 = 0x1000018Cu;
public const uint AttackMed6 = 0x1000018Du;
public const uint AttackLow6 = 0x1000018Eu;
}