# Combat Animation Planner Pseudocode ## Sources - Retail `ClientCombatSystem::ExecuteAttack` (`0x0056BB70`): sends targeted melee or missile attack intent and records pending response state. It does not choose a local swing animation. - Retail `ClientCombatSystem::HandleCommenceAttackEvent` (`0x0056AD20`): starts/updates power-bar and busy UI state. The event carries no `MotionCommand`. - Retail command-name table around `0x00803F34`: combat commands include `Twitch1..4`, `StaggerBackward`, `StaggerForward`, `ThrustMed`, `SlashHigh`, `Shoot`, `AttackHigh1`, and later offhand/multistrike commands. - ACE `Player_Melee.DoSwingMotion` and `GetSwingAnimation`: server chooses a swing from `CombatManeuverTable.GetMotion(...)` and broadcasts the selected `MotionCommand` with `UpdateMotion`. - ACE `CombatManeuverTable.GetMotion`: indexes `(stance, attack height, attack type)` to one or more motion commands; power level chooses between multiple entries. ## Retail Rule Combat GameEvents are state/UI notifications. Motion state is the animation authority. ## Pseudocode ```text PlanForEvent(event): return None PlanFromWireCommand(wireCommand, speed): fullCommand = MotionCommandResolver.ReconstructFullCommand(wireCommand) return PlanFromFullCommand(fullCommand, speed) PlanFromFullCommand(fullCommand, speed): kind = ClassifyMotionCommand(fullCommand) if kind is None: return None routeKind = AnimationCommandRouter.Classify(fullCommand) return Plan(kind, routeKind, fullCommand, speed) ClassifyMotionCommand(fullCommand): if command is a combat stance: return CombatStance if command is a thrust/slash/backhand/offhand/multistrike motion: return MeleeSwing if command is Shoot, MissileAttack*, or Reload: return MissileAttack if command is AttackHigh/Med/Low 1..6: return CreatureAttack if command is CastSpell, UseMagicStaff, or UseMagicWand: return SpellCast if command is Twitch*, Stagger*, FallDown, or Sanctuary: return HitReaction if command is Dead: return Death return None ``` ## Maneuver Selection Pseudocode ```text SelectMotion(table, stance, attackHeight, attackType, powerLevel, isThrustSlashWeapon): candidates = [] for maneuver in table.CombatManeuvers: if maneuver.Style == stance and maneuver.AttackHeight == attackHeight and maneuver.AttackType == attackType: candidates.append(maneuver.Motion) if candidates is empty: return None subdivision = isThrustSlashWeapon ? 0.66 : 0.33 if candidates.Count > 1 and powerLevel < subdivision: motion = candidates[1] else: motion = candidates[0] return motion ``` This matches ACE `CombatManeuverTable.GetMotion` plus `Player_Melee.GetSwingAnimation`. The `prevMotion` parameter is present in ACE's table API but the current ACE implementation does not use it; the power threshold chooses between multiple entries. ## Named Retail Motion IDs `DatReaderWriter.Enums.MotionCommand` is shifted by three entries starting at `AllegianceHometownRecall`. Named retail command tables are: - `command_ids` table lines 1017626-1017658: `0x016E..0x0197 -> 0x1000016E..0x10000197`. - command-name table lines 1068272-1068313: `OffhandSlashHigh = 0x10000170`, `AttackLow6 = 0x1000018B`, `PunchFastLow = 0x1000018E`, etc. `MotionCommandResolver` therefore overrides that range after building the DRW reflection table, otherwise offhand and late unarmed attack actions resolve as UI/mappable commands and never reach `PlayAction`. ## Implementation Note The next table-driven layer can use `DatReaderWriter.DBObjs.CombatTable` and `DatReaderWriter.Types.CombatManeuver` directly. acdream already references `Chorizite.DatReaderWriter`; the missing live-state piece is a named `CombatTable` data-id on player/creature state.