feat(animation): decompose AnimationController into FSM-based architecture

Replace the 2,200-line monolithic AnimationController (goto-driven,
single class, untestable) with a composed FSM architecture per
refactor.md.

New subsystem (src/rendering/animation/ — 16 headers, 10 sources):
- CharacterAnimator: FSM composer implementing ICharacterAnimator
- LocomotionFSM: idle/walk/run/sprint/jump/swim/strafe
- CombatFSM: melee/ranged/spell cast/stun/hit reaction/charge
- ActivityFSM: emote/loot/sit-down/sitting/sit-up
- MountFSM: idle/run/flight/taxi/fidget/rear-up (per-instance RNG)
- AnimCapabilitySet + AnimCapabilityProbe: probe once at model load,
  eliminate per-frame hasAnimation() linear search
- AnimationManager: registry of CharacterAnimator by GUID
- EmoteRegistry: DBC-backed emote command → animId singleton
- FootstepDriver, SfxStateDriver: extracted from AnimationController

animation_ids.hpp/.cpp moved to animation/ subdirectory (452 named
constants); all include paths updated.

AnimationController retained as thin adapter (~400 LOC): collects
FrameInput, delegates to CharacterAnimator, applies AnimOutput.

Priority order: Mount > Stun > HitReaction > Spell > Charge >
Melee/Ranged > CombatIdle > Emote > Loot > Sit > Locomotion.
STAY_IN_STATE policy when all FSMs return valid=false.

Bugs fixed:
- Remove static mt19937 in mount fidget (shared state across all
  mounted units) — replaced with per-instance seeded RNG
- Remove goto from mounted animation branch (skipped init)
- Remove per-frame hasAnimation() calls (now one probe at load)
- Fix VK_INDEX_TYPE_UINT16 → UINT32 in shadow pass

Tests (4 new suites, all ASAN+UBSan clean):
- test_locomotion_fsm: 167 assertions
- test_combat_fsm: 125 cases
- test_activity_fsm: 112 cases
- test_anim_capability: 56 cases

docs/ANIMATION_SYSTEM.md added (architecture reference).
This commit is contained in:
Paul 2026-04-05 12:27:35 +03:00
parent e58f9b4b40
commit b4989dc11f
53 changed files with 5110 additions and 2099 deletions

View file

@ -0,0 +1,110 @@
#pragma once
#include "rendering/animation/anim_capability_set.hpp"
#include "rendering/animation/anim_event.hpp"
#include <cstdint>
#include <string>
namespace wowee {
namespace rendering {
// ============================================================================
// ActivityFSM
//
// Pure logic state machine for non-combat activities: emote, loot, sit/sleep/kneel.
// Sit chain (down → loop → up) auto-advances on one-shot completion.
// All activities cancel on movement.
// ============================================================================
class ActivityFSM {
public:
enum class State : uint8_t {
NONE,
EMOTE,
LOOTING, // One-shot LOOT anim
LOOT_KNEELING, // KNEEL_LOOP until loot window closes
LOOT_END, // One-shot KNEEL_END exit anim
SIT_DOWN,
SITTING,
SIT_UP,
};
struct Input {
bool moving = false;
bool sprinting = false;
bool jumping = false;
bool grounded = true;
bool swimming = false;
bool sitting = false; // Camera controller sitting state
bool stunned = false;
// Animation state query for one-shot completion detection
uint32_t currentAnimId = 0;
float currentAnimTime = 0.0f;
float currentAnimDuration = 0.0f;
bool haveAnimState = false;
};
void onEvent(AnimEvent event);
/// Evaluate current state against input and capabilities.
AnimOutput resolve(const Input& in, const AnimCapabilitySet& caps);
State getState() const { return state_; }
void setState(State s) { state_ = s; }
bool isActive() const { return state_ != State::NONE; }
void reset();
// ── Emote management ────────────────────────────────────────────────
void startEmote(uint32_t animId, bool loop);
void cancelEmote();
bool isEmoteActive() const { return emoteActive_; }
uint32_t getEmoteAnimId() const { return emoteAnimId_; }
// ── Sit/sleep/kneel management ──────────────────────────────────────
// WoW UnitStandStateType constants
static constexpr uint8_t STAND_STATE_STAND = 0;
static constexpr uint8_t STAND_STATE_SIT = 1;
static constexpr uint8_t STAND_STATE_SIT_CHAIR = 2;
static constexpr uint8_t STAND_STATE_SLEEP = 3;
static constexpr uint8_t STAND_STATE_SIT_LOW = 4;
static constexpr uint8_t STAND_STATE_SIT_MED = 5;
static constexpr uint8_t STAND_STATE_SIT_HIGH = 6;
static constexpr uint8_t STAND_STATE_DEAD = 7;
static constexpr uint8_t STAND_STATE_KNEEL = 8;
void setStandState(uint8_t standState);
uint8_t getStandState() const { return standState_; }
// ── Loot management ─────────────────────────────────────────────────
void startLooting();
void stopLooting();
static constexpr uint8_t PRIORITY = 30;
private:
State state_ = State::NONE;
// Emote state
bool emoteActive_ = false;
uint32_t emoteAnimId_ = 0;
bool emoteLoop_ = false;
// Sit/sleep/kneel transition animations
uint8_t standState_ = 0;
uint32_t sitDownAnim_ = 0;
uint32_t sitLoopAnim_ = 0;
uint32_t sitUpAnim_ = 0;
bool sitDownAnimSeen_ = false; // Track whether one-shot has started playing
bool sitUpAnimSeen_ = false;
uint8_t sitDownFrames_ = 0; // Frames spent in SIT_DOWN (for safety timeout)
uint8_t sitUpFrames_ = 0; // Frames spent in SIT_UP
bool lootAnimSeen_ = false;
uint8_t lootFrames_ = 0;
bool lootEndAnimSeen_ = false;
uint8_t lootEndFrames_ = 0;
void updateTransitions(const Input& in);
bool oneShotComplete(const Input& in, uint32_t expectedAnimId) const;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,38 @@
#pragma once
#include <cstdint>
#include <vector>
#include "rendering/animation/anim_capability_set.hpp"
namespace wowee {
namespace rendering {
class Renderer;
// ============================================================================
// AnimCapabilityProbe
//
// Scans a model's animation sequences once and caches the results in an
// AnimCapabilitySet. All animation selection then uses the probed set
// instead of per-frame hasAnimation() calls.
// ============================================================================
class AnimCapabilityProbe {
public:
AnimCapabilityProbe() = default;
/// Probe all animation capabilities for the given character instance.
/// Returns a fully-populated AnimCapabilitySet.
static AnimCapabilitySet probe(Renderer* renderer, uint32_t instanceId);
/// Probe mount animation capabilities (separate model).
static AnimCapabilitySet probeMountModel(Renderer* renderer, uint32_t mountInstanceId);
private:
/// Pick the first available animation from candidates for the given instance.
/// Returns 0 if none available.
static uint32_t pickFirst(Renderer* renderer, uint32_t instanceId,
const uint32_t* candidates, size_t count);
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,147 @@
#pragma once
#include <cstdint>
namespace wowee {
namespace rendering {
// ============================================================================
// AnimFallbackPolicy
//
// Controls what happens when a requested animation is unavailable.
// ============================================================================
enum class AnimFallbackPolicy : uint8_t {
STAY_IN_STATE, // Keep current animation (default for player)
FIRST_AVAILABLE, // Try candidates list, stay if all fail
NONE, // Do nothing (default for expired queue)
};
// ============================================================================
// AnimOutput
//
// Unified animation selection result. When valid=false, callers should
// keep the currently playing animation (STAY_IN_STATE policy).
// ============================================================================
struct AnimOutput {
uint32_t animId = 0;
bool loop = false;
bool valid = false;
/// Construct a valid output.
static AnimOutput ok(uint32_t id, bool looping) {
return {id, looping, true};
}
/// Construct an invalid output (STAY policy).
static AnimOutput stay() {
return {0, false, false};
}
};
// ============================================================================
// AnimCapabilitySet
//
// Probed once per model load. Caches which animations a model supports
// and the resolved IDs (after fallback chains). This eliminates per-frame
// hasAnimation() calls.
// ============================================================================
struct AnimCapabilitySet {
// ── Locomotion resolved IDs ─────────────────────────────────────────
uint32_t resolvedStand = 0;
uint32_t resolvedWalk = 0;
uint32_t resolvedRun = 0;
uint32_t resolvedSprint = 0;
uint32_t resolvedWalkBackwards = 0;
uint32_t resolvedStrafeLeft = 0;
uint32_t resolvedStrafeRight = 0;
uint32_t resolvedRunLeft = 0;
uint32_t resolvedRunRight = 0;
uint32_t resolvedJumpStart = 0;
uint32_t resolvedJump = 0; // Mid-air loop
uint32_t resolvedJumpEnd = 0;
uint32_t resolvedSwimIdle = 0;
uint32_t resolvedSwim = 0;
uint32_t resolvedSwimBackwards = 0;
uint32_t resolvedSwimLeft = 0;
uint32_t resolvedSwimRight = 0;
// ── Combat resolved IDs ─────────────────────────────────────────────
uint32_t resolvedCombatIdle = 0;
uint32_t resolvedMelee1H = 0;
uint32_t resolvedMelee2H = 0;
uint32_t resolvedMelee2HLoose = 0;
uint32_t resolvedMeleeUnarmed = 0;
uint32_t resolvedMeleeFist = 0;
uint32_t resolvedMeleePierce = 0; // Dagger
uint32_t resolvedMeleeOffHand = 0;
uint32_t resolvedMeleeOffHandFist = 0;
uint32_t resolvedMeleeOffHandPierce = 0;
uint32_t resolvedMeleeOffHandUnarmed = 0;
// ── Ready stances ───────────────────────────────────────────────────
uint32_t resolvedReady1H = 0;
uint32_t resolvedReady2H = 0;
uint32_t resolvedReady2HLoose = 0;
uint32_t resolvedReadyUnarmed = 0;
uint32_t resolvedReadyFist = 0;
uint32_t resolvedReadyBow = 0;
uint32_t resolvedReadyRifle = 0;
uint32_t resolvedReadyCrossbow = 0;
uint32_t resolvedReadyThrown = 0;
// ── Ranged attack resolved IDs ──────────────────────────────────────
uint32_t resolvedFireBow = 0;
uint32_t resolvedAttackRifle = 0;
uint32_t resolvedAttackCrossbow = 0;
uint32_t resolvedAttackThrown = 0;
uint32_t resolvedLoadBow = 0;
uint32_t resolvedLoadRifle = 0;
// ── Special attacks ─────────────────────────────────────────────────
uint32_t resolvedSpecial1H = 0;
uint32_t resolvedSpecial2H = 0;
uint32_t resolvedSpecialUnarmed = 0;
uint32_t resolvedShieldBash = 0;
// ── Activity resolved IDs ───────────────────────────────────────────
uint32_t resolvedStandWound = 0;
uint32_t resolvedSitDown = 0;
uint32_t resolvedSitLoop = 0;
uint32_t resolvedSitUp = 0;
uint32_t resolvedKneel = 0;
uint32_t resolvedDeath = 0;
// ── Stealth ─────────────────────────────────────────────────────────
uint32_t resolvedStealthIdle = 0;
uint32_t resolvedStealthWalk = 0;
uint32_t resolvedStealthRun = 0;
// ── Misc ────────────────────────────────────────────────────────────
uint32_t resolvedMount = 0;
uint32_t resolvedUnsheathe = 0;
uint32_t resolvedSheathe = 0;
uint32_t resolvedStun = 0;
uint32_t resolvedCombatWound = 0;
uint32_t resolvedLoot = 0;
// ── Capability flags (bitfield) ─────────────────────────────────────
bool hasStand : 1;
bool hasWalk : 1;
bool hasRun : 1;
bool hasSprint : 1;
bool hasWalkBackwards : 1;
bool hasJump : 1;
bool hasSwim : 1;
bool hasMelee : 1;
bool hasStealth : 1;
bool hasDeath : 1;
bool hasMount : 1;
// Default-initialize all flags to false
AnimCapabilitySet()
: hasStand(false), hasWalk(false), hasRun(false), hasSprint(false),
hasWalkBackwards(false), hasJump(false), hasSwim(false),
hasMelee(false), hasStealth(false), hasDeath(false), hasMount(false) {}
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,31 @@
#pragma once
#include <cstdint>
namespace wowee {
namespace rendering {
// ============================================================================
// AnimEvent
//
// Event-driven animation state transitions. Sub-FSMs react to these events
// instead of polling conditions every frame.
// ============================================================================
enum class AnimEvent : uint8_t {
MOVE_START, MOVE_STOP,
SPRINT_START, SPRINT_STOP,
JUMP, LANDED,
SWIM_ENTER, SWIM_EXIT,
COMBAT_ENTER, COMBAT_EXIT,
STUN_ENTER, STUN_EXIT,
SPELL_START, SPELL_STOP,
HIT_REACT, CHARGE_START, CHARGE_END,
EMOTE_START, EMOTE_STOP,
LOOT_START, LOOT_STOP,
SIT, STAND_UP,
MOUNT, DISMOUNT,
STEALTH_ENTER, STEALTH_EXIT,
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,514 @@
#pragma once
// ============================================================================
// M2 Animation IDs — AnimationData.dbc
//
// Complete list from https://wowdev.wiki/M2/AnimationList
// Community names in comments describe what each animation looks like in-game.
// Organized by World of Warcraft expansion for easier management.
// ============================================================================
#include <cstdint>
#include <memory>
namespace wowee {
namespace pipeline { class DBCFile; }
namespace rendering {
namespace anim {
// ============================================================================
// Classic (Vanilla WoW 1.x) — Core character & creature animations
// IDs 0145
// ============================================================================
constexpr uint32_t STAND = 0; // Idle standing pose
constexpr uint32_t DEATH = 1; // Death animation
constexpr uint32_t SPELL = 2; // Generic spell cast
constexpr uint32_t STOP = 3; // Transition to stop
constexpr uint32_t WALK = 4; // Walking forward
constexpr uint32_t RUN = 5; // Running forward
constexpr uint32_t DEAD = 6; // Corpse on the ground
constexpr uint32_t RISE = 7; // Rising from death (resurrection)
constexpr uint32_t STAND_WOUND = 8; // Wounded idle stance
constexpr uint32_t COMBAT_WOUND = 9; // Wounded combat idle
constexpr uint32_t COMBAT_CRITICAL = 10; // Critical hit reaction
constexpr uint32_t SHUFFLE_LEFT = 11; // Strafe walk left
constexpr uint32_t SHUFFLE_RIGHT = 12; // Strafe walk right
constexpr uint32_t WALK_BACKWARDS = 13; // Walking backwards / backpedal
constexpr uint32_t STUN = 14; // Stunned
constexpr uint32_t HANDS_CLOSED = 15; // Hands closed (weapon grip idle)
constexpr uint32_t ATTACK_UNARMED = 16; // Unarmed melee attack
constexpr uint32_t ATTACK_1H = 17; // One-handed melee attack
constexpr uint32_t ATTACK_2H = 18; // Two-handed melee attack
constexpr uint32_t ATTACK_2H_LOOSE = 19; // Polearm/staff two-hand attack
constexpr uint32_t PARRY_UNARMED = 20; // Unarmed parry
constexpr uint32_t PARRY_1H = 21; // One-handed weapon parry
constexpr uint32_t PARRY_2H = 22; // Two-handed weapon parry
constexpr uint32_t PARRY_2H_LOOSE = 23; // Polearm/staff parry
constexpr uint32_t SHIELD_BLOCK = 24; // Shield block
constexpr uint32_t READY_UNARMED = 25; // Unarmed combat ready stance
constexpr uint32_t READY_1H = 26; // One-handed weapon ready stance
constexpr uint32_t READY_2H = 27; // Two-handed weapon ready stance
constexpr uint32_t READY_2H_LOOSE = 28; // Polearm/staff ready stance
constexpr uint32_t READY_BOW = 29; // Bow ready stance
constexpr uint32_t DODGE = 30; // Dodge
constexpr uint32_t SPELL_PRECAST = 31; // Spell precast wind-up
constexpr uint32_t SPELL_CAST = 32; // Spell cast
constexpr uint32_t SPELL_CAST_AREA = 33; // Area-of-effect spell cast
constexpr uint32_t NPC_WELCOME = 34; // NPC greeting animation
constexpr uint32_t NPC_GOODBYE = 35; // NPC farewell animation
constexpr uint32_t BLOCK = 36; // Block
constexpr uint32_t JUMP_START = 37; // Jump takeoff
constexpr uint32_t JUMP = 38; // Mid-air jump loop
constexpr uint32_t JUMP_END = 39; // Jump landing
constexpr uint32_t FALL = 40; // Falling
constexpr uint32_t SWIM_IDLE = 41; // Treading water
constexpr uint32_t SWIM = 42; // Swimming forward
constexpr uint32_t SWIM_LEFT = 43; // Swim strafe left
constexpr uint32_t SWIM_RIGHT = 44; // Swim strafe right
constexpr uint32_t SWIM_BACKWARDS = 45; // Swim backwards
constexpr uint32_t ATTACK_BOW = 46; // Bow attack
constexpr uint32_t FIRE_BOW = 47; // Fire bow shot
constexpr uint32_t READY_RIFLE = 48; // Rifle/gun ready stance
constexpr uint32_t ATTACK_RIFLE = 49; // Rifle/gun attack
constexpr uint32_t LOOT = 50; // Looting / bending down to pick up
constexpr uint32_t READY_SPELL_DIRECTED = 51; // Directed spell ready
constexpr uint32_t READY_SPELL_OMNI = 52; // Omni spell ready
constexpr uint32_t SPELL_CAST_DIRECTED = 53; // Directed spell cast
constexpr uint32_t SPELL_CAST_OMNI = 54; // Omni spell cast
constexpr uint32_t BATTLE_ROAR = 55; // Battle shout / roar
constexpr uint32_t READY_ABILITY = 56; // Ability ready stance
constexpr uint32_t SPECIAL_1H = 57; // Special one-handed attack
constexpr uint32_t SPECIAL_2H = 58; // Special two-handed attack
constexpr uint32_t SHIELD_BASH = 59; // Shield bash
constexpr uint32_t EMOTE_TALK = 60; // /talk
constexpr uint32_t EMOTE_EAT = 61; // /eat
constexpr uint32_t EMOTE_WORK = 62; // /work
constexpr uint32_t EMOTE_USE_STANDING = 63; // Standing use animation
constexpr uint32_t EMOTE_EXCLAMATION = 64; // NPC exclamation (!)
constexpr uint32_t EMOTE_QUESTION = 65; // NPC question (?)
constexpr uint32_t EMOTE_BOW = 66; // /bow
constexpr uint32_t EMOTE_WAVE = 67; // /wave
constexpr uint32_t EMOTE_CHEER = 68; // /cheer
constexpr uint32_t EMOTE_DANCE = 69; // /dance
constexpr uint32_t EMOTE_LAUGH = 70; // /laugh
constexpr uint32_t EMOTE_SLEEP = 71; // /sleep
constexpr uint32_t EMOTE_SIT_GROUND = 72; // /sit on ground
constexpr uint32_t EMOTE_RUDE = 73; // /rude
constexpr uint32_t EMOTE_ROAR = 74; // /roar
constexpr uint32_t EMOTE_KNEEL = 75; // /kneel
constexpr uint32_t EMOTE_KISS = 76; // /kiss
constexpr uint32_t EMOTE_CRY = 77; // /cry
constexpr uint32_t EMOTE_CHICKEN = 78; // /chicken — flap arms and strut
constexpr uint32_t EMOTE_BEG = 79; // /beg
constexpr uint32_t EMOTE_APPLAUD = 80; // /applaud
constexpr uint32_t EMOTE_SHOUT = 81; // /shout
constexpr uint32_t EMOTE_FLEX = 82; // /flex — show off muscles
constexpr uint32_t EMOTE_SHY = 83; // /shy
constexpr uint32_t EMOTE_POINT = 84; // /point
constexpr uint32_t ATTACK_1H_PIERCE = 85; // One-handed pierce (dagger stab)
constexpr uint32_t ATTACK_2H_LOOSE_PIERCE = 86; // Polearm/staff pierce
constexpr uint32_t ATTACK_OFF = 87; // Off-hand attack
constexpr uint32_t ATTACK_OFF_PIERCE = 88; // Off-hand pierce attack
constexpr uint32_t SHEATHE = 89; // Sheathe weapons
constexpr uint32_t HIP_SHEATHE = 90; // Hip sheathe
constexpr uint32_t MOUNT = 91; // Mounted idle
constexpr uint32_t RUN_RIGHT = 92; // Strafe run right
constexpr uint32_t RUN_LEFT = 93; // Strafe run left
constexpr uint32_t MOUNT_SPECIAL = 94; // Mount rearing / special move
constexpr uint32_t KICK = 95; // Kick
constexpr uint32_t SIT_GROUND_DOWN = 96; // Transition: standing → sitting
constexpr uint32_t SITTING = 97; // Sitting on ground loop
constexpr uint32_t SIT_GROUND_UP = 98; // Transition: sitting → standing
constexpr uint32_t SLEEP_DOWN = 99; // Transition: standing → sleeping
constexpr uint32_t SLEEP = 100; // Sleeping loop
constexpr uint32_t SLEEP_UP = 101; // Transition: sleeping → standing
constexpr uint32_t SIT_CHAIR_LOW = 102; // Sit in low chair
constexpr uint32_t SIT_CHAIR_MED = 103; // Sit in medium chair
constexpr uint32_t SIT_CHAIR_HIGH = 104; // Sit in high chair
constexpr uint32_t LOAD_BOW = 105; // Nock/load bow
constexpr uint32_t LOAD_RIFLE = 106; // Load rifle/gun
constexpr uint32_t ATTACK_THROWN = 107; // Thrown weapon attack
constexpr uint32_t READY_THROWN = 108; // Thrown weapon ready
constexpr uint32_t HOLD_BOW = 109; // Hold bow idle
constexpr uint32_t HOLD_RIFLE = 110; // Hold rifle/gun idle
constexpr uint32_t HOLD_THROWN = 111; // Hold thrown weapon idle
constexpr uint32_t LOAD_THROWN = 112; // Load thrown weapon
constexpr uint32_t EMOTE_SALUTE = 113; // /salute
constexpr uint32_t KNEEL_START = 114; // Transition: standing → kneeling
constexpr uint32_t KNEEL_LOOP = 115; // Kneeling loop
constexpr uint32_t KNEEL_END = 116; // Transition: kneeling → standing
constexpr uint32_t ATTACK_UNARMED_OFF = 117; // Off-hand unarmed attack
constexpr uint32_t SPECIAL_UNARMED = 118; // Special unarmed attack
constexpr uint32_t STEALTH_WALK = 119; // Stealth walking (rogue sneak)
constexpr uint32_t STEALTH_STAND = 120; // Stealth standing idle
constexpr uint32_t KNOCKDOWN = 121; // Knocked down
constexpr uint32_t EATING_LOOP = 122; // Eating loop (food/drink)
constexpr uint32_t USE_STANDING_LOOP = 123; // Use standing loop
constexpr uint32_t CHANNEL_CAST_DIRECTED = 124; // Channeled directed cast
constexpr uint32_t CHANNEL_CAST_OMNI = 125; // Channeled omni cast
constexpr uint32_t WHIRLWIND = 126; // Whirlwind attack (warrior)
constexpr uint32_t BIRTH = 127; // Creature birth/spawn
constexpr uint32_t USE_STANDING_START = 128; // Use standing start
constexpr uint32_t USE_STANDING_END = 129; // Use standing end
constexpr uint32_t CREATURE_SPECIAL = 130; // Creature special ability
constexpr uint32_t DROWN = 131; // Drowning
constexpr uint32_t DROWNED = 132; // Drowned corpse underwater
constexpr uint32_t FISHING_CAST = 133; // Fishing cast
constexpr uint32_t FISHING_LOOP = 134; // Fishing idle loop
constexpr uint32_t FLY = 135; // Flying generic
constexpr uint32_t EMOTE_WORK_NO_SHEATHE = 136; // Work emote (no weapon sheathe)
constexpr uint32_t EMOTE_STUN_NO_SHEATHE = 137; // Stun emote (no weapon sheathe)
constexpr uint32_t EMOTE_USE_STANDING_NO_SHEATHE = 138; // Use standing (no weapon sheathe)
constexpr uint32_t SPELL_SLEEP_DOWN = 139; // Spell-induced sleep down
constexpr uint32_t SPELL_KNEEL_START = 140; // Spell-induced kneel start
constexpr uint32_t SPELL_KNEEL_LOOP = 141; // Spell-induced kneel loop
constexpr uint32_t SPELL_KNEEL_END = 142; // Spell-induced kneel end
constexpr uint32_t SPRINT = 143; // Sprint / Custom Spell 01
constexpr uint32_t IN_FLIGHT = 144; // In-flight (flight path travel)
constexpr uint32_t SPAWN = 145; // Object/creature spawn animation
// ============================================================================
// The Burning Crusade (TBC 2.x) — Flying mounts, game objects, stealth run
// IDs 146199
// ============================================================================
constexpr uint32_t CLOSE = 146; // Game object close
constexpr uint32_t CLOSED = 147; // Game object closed loop
constexpr uint32_t OPEN = 148; // Game object open
constexpr uint32_t DESTROY = 149; // Game object destroy
constexpr uint32_t DESTROYED = 150; // Game object destroyed state
constexpr uint32_t UNSHEATHE = 151; // Unsheathe weapons
constexpr uint32_t SHEATHE_ALT = 152; // Sheathe weapons (alternate)
constexpr uint32_t ATTACK_UNARMED_NO_SHEATHE = 153; // Unarmed attack (no sheathe)
constexpr uint32_t STEALTH_RUN = 154; // Stealth running (rogue sprint)
constexpr uint32_t READY_CROSSBOW = 155; // Crossbow ready stance
constexpr uint32_t ATTACK_CROSSBOW = 156; // Crossbow attack
constexpr uint32_t EMOTE_TALK_EXCLAMATION = 157; // /talk with exclamation
constexpr uint32_t FLY_IDLE = 158; // Flying mount idle / hovering
constexpr uint32_t FLY_FORWARD = 159; // Flying mount forward
constexpr uint32_t FLY_BACKWARDS = 160; // Flying mount backwards
constexpr uint32_t FLY_LEFT = 161; // Flying mount strafe left
constexpr uint32_t FLY_RIGHT = 162; // Flying mount strafe right
constexpr uint32_t FLY_UP = 163; // Flying mount ascending
constexpr uint32_t FLY_DOWN = 164; // Flying mount descending
constexpr uint32_t FLY_LAND_START = 165; // Flying mount land start
constexpr uint32_t FLY_LAND_RUN = 166; // Flying mount land run
constexpr uint32_t FLY_LAND_END = 167; // Flying mount land end
constexpr uint32_t EMOTE_TALK_QUESTION = 168; // /talk with question
constexpr uint32_t EMOTE_READ = 169; // /read (reading animation)
constexpr uint32_t EMOTE_SHIELDBLOCK = 170; // Shield block emote
constexpr uint32_t EMOTE_CHOP = 171; // Chopping emote (lumber)
constexpr uint32_t EMOTE_HOLDRIFLE = 172; // Hold rifle emote
constexpr uint32_t EMOTE_HOLDBOW = 173; // Hold bow emote
constexpr uint32_t EMOTE_HOLDTHROWN = 174; // Hold thrown weapon emote
constexpr uint32_t CUSTOM_SPELL_02 = 175; // Custom spell animation 02
constexpr uint32_t CUSTOM_SPELL_03 = 176; // Custom spell animation 03
constexpr uint32_t CUSTOM_SPELL_04 = 177; // Custom spell animation 04
constexpr uint32_t CUSTOM_SPELL_05 = 178; // Custom spell animation 05
constexpr uint32_t CUSTOM_SPELL_06 = 179; // Custom spell animation 06
constexpr uint32_t CUSTOM_SPELL_07 = 180; // Custom spell animation 07
constexpr uint32_t CUSTOM_SPELL_08 = 181; // Custom spell animation 08
constexpr uint32_t CUSTOM_SPELL_09 = 182; // Custom spell animation 09
constexpr uint32_t CUSTOM_SPELL_10 = 183; // Custom spell animation 10
constexpr uint32_t EMOTE_STATE_DANCE = 184; // /dance state (looping dance)
// ============================================================================
// Wrath of the Lich King (WotLK 3.x) — Vehicles, reclined, crafting, etc.
// IDs 185+
// ============================================================================
constexpr uint32_t FLY_STAND = 185; // Flying stand (hover in place)
constexpr uint32_t EMOTE_STATE_LAUGH = 186; // /laugh state loop
constexpr uint32_t EMOTE_STATE_POINT = 187; // /point state loop
constexpr uint32_t EMOTE_STATE_EAT = 188; // /eat state loop
constexpr uint32_t EMOTE_STATE_WORK = 189; // /work state loop (crafting NPC)
constexpr uint32_t EMOTE_STATE_SIT_GROUND = 190; // /sit ground state loop
constexpr uint32_t EMOTE_STATE_HOLD_BOW = 191; // Hold bow state loop
constexpr uint32_t EMOTE_STATE_HOLD_RIFLE = 192; // Hold rifle state loop
constexpr uint32_t EMOTE_STATE_HOLD_THROWN = 193; // Hold thrown state loop
constexpr uint32_t FLY_COMBAT_WOUND = 194; // Flying wounded
constexpr uint32_t FLY_COMBAT_CRITICAL = 195; // Flying critical hit reaction
constexpr uint32_t RECLINED = 196; // Reclined / laid back pose
constexpr uint32_t EMOTE_STATE_ROAR = 197; // /roar state loop
constexpr uint32_t EMOTE_USE_STANDING_LOOP_2 = 198; // Use standing loop variant
constexpr uint32_t EMOTE_STATE_APPLAUD = 199; // /applaud state loop
constexpr uint32_t READY_FIST = 200; // Fist weapon ready stance
constexpr uint32_t SPELL_CHANNEL_DIRECTED_OMNI = 201; // Channel directed omni
constexpr uint32_t SPECIAL_ATTACK_1H_OFF = 202; // Special off-hand one-handed attack
constexpr uint32_t ATTACK_FIST_1H = 203; // Fist weapon one-hand attack
constexpr uint32_t ATTACK_FIST_1H_OFF = 204; // Fist weapon off-hand attack
constexpr uint32_t PARRY_FIST_1H = 205; // Fist weapon parry
constexpr uint32_t READY_FIST_1H = 206; // Fist weapon one-hand ready
constexpr uint32_t EMOTE_STATE_READ_AND_TALK = 207; // Read and talk NPC loop
constexpr uint32_t EMOTE_STATE_WORK_NO_SHEATHE = 208; // Work no sheathe state loop
constexpr uint32_t FLY_RUN = 209; // Flying run (fast forward flight)
constexpr uint32_t EMOTE_STATE_KNEEL_2 = 210; // Kneel state variant
constexpr uint32_t EMOTE_STATE_SPELL_KNEEL = 211; // Spell kneel state loop
constexpr uint32_t EMOTE_STATE_USE_STANDING = 212; // Use standing state
constexpr uint32_t EMOTE_STATE_STUN = 213; // Stun state loop
constexpr uint32_t EMOTE_STATE_STUN_NO_SHEATHE = 214; // Stun no sheathe state
constexpr uint32_t EMOTE_TRAIN = 215; // /train — choo choo!
constexpr uint32_t EMOTE_DEAD = 216; // /dead — play dead
constexpr uint32_t EMOTE_STATE_DANCE_ONCE = 217; // Single dance animation
constexpr uint32_t FLY_DEATH = 218; // Flying death
constexpr uint32_t FLY_STAND_WOUND = 219; // Flying wounded stand
constexpr uint32_t FLY_SHUFFLE_LEFT = 220; // Flying strafe left
constexpr uint32_t FLY_SHUFFLE_RIGHT = 221; // Flying strafe right
constexpr uint32_t FLY_WALK_BACKWARDS = 222; // Flying walk backwards
constexpr uint32_t FLY_STUN = 223; // Flying stunned
constexpr uint32_t FLY_HANDS_CLOSED = 224; // Flying hands closed
constexpr uint32_t FLY_ATTACK_UNARMED = 225; // Flying unarmed attack
constexpr uint32_t FLY_ATTACK_1H = 226; // Flying one-hand attack
constexpr uint32_t FLY_ATTACK_2H = 227; // Flying two-hand attack
constexpr uint32_t FLY_ATTACK_2H_LOOSE = 228; // Flying polearm attack
constexpr uint32_t FLY_SPELL = 229; // Flying spell — generic spell while flying
constexpr uint32_t FLY_STOP = 230; // Flying stop
constexpr uint32_t FLY_WALK = 231; // Flying walk
constexpr uint32_t FLY_DEAD = 232; // Flying dead (corpse mid-air)
constexpr uint32_t FLY_RISE = 233; // Flying rise — resurrection mid-air
constexpr uint32_t FLY_RUN_2 = 234; // Flying run variant
constexpr uint32_t FLY_FALL = 235; // Flying fall
constexpr uint32_t FLY_SWIM_IDLE = 236; // Flying swim idle
constexpr uint32_t FLY_SWIM = 237; // Flying swim
constexpr uint32_t FLY_SWIM_LEFT = 238; // Flying swim left
constexpr uint32_t FLY_SWIM_RIGHT = 239; // Flying swim right
constexpr uint32_t FLY_SWIM_BACKWARDS = 240; // Flying swim backwards
constexpr uint32_t FLY_ATTACK_BOW = 241; // Flying bow attack
constexpr uint32_t FLY_FIRE_BOW = 242; // Flying fire bow
constexpr uint32_t FLY_READY_RIFLE = 243; // Flying rifle ready
constexpr uint32_t FLY_ATTACK_RIFLE = 244; // Flying rifle attack
// ── WotLK Vehicle & extended movement animations ──────────────────────────
constexpr uint32_t TOTEM_SMALL = 245; // Small totem idle (shaman)
constexpr uint32_t TOTEM_MEDIUM = 246; // Medium totem idle
constexpr uint32_t TOTEM_LARGE = 247; // Large totem idle
constexpr uint32_t FLY_LOOT = 248; // Flying loot
constexpr uint32_t FLY_READY_SPELL_DIRECTED = 249; // Flying directed spell ready
constexpr uint32_t FLY_READY_SPELL_OMNI = 250; // Flying omni spell ready
constexpr uint32_t FLY_SPELL_CAST_DIRECTED = 251; // Flying directed spell cast
constexpr uint32_t FLY_SPELL_CAST_OMNI = 252; // Flying omni spell cast
constexpr uint32_t FLY_BATTLE_ROAR = 253; // Flying battle shout
constexpr uint32_t FLY_READY_ABILITY = 254; // Flying ability ready
constexpr uint32_t FLY_SPECIAL_1H = 255; // Flying special one-hand
constexpr uint32_t FLY_SPECIAL_2H = 256; // Flying special two-hand
constexpr uint32_t FLY_SHIELD_BASH = 257; // Flying shield bash
constexpr uint32_t FLY_EMOTE_TALK = 258; // Flying emote talk
constexpr uint32_t FLY_EMOTE_EAT = 259; // Flying emote eat
constexpr uint32_t FLY_EMOTE_WORK = 260; // Flying emote work
constexpr uint32_t FLY_EMOTE_USE_STANDING = 261; // Flying emote use standing
constexpr uint32_t FLY_EMOTE_BOW = 262; // Flying emote bow
constexpr uint32_t FLY_EMOTE_WAVE = 263; // Flying emote wave
constexpr uint32_t FLY_EMOTE_CHEER = 264; // Flying emote cheer
constexpr uint32_t FLY_EMOTE_DANCE = 265; // Flying emote dance
constexpr uint32_t FLY_EMOTE_LAUGH = 266; // Flying emote laugh
constexpr uint32_t FLY_EMOTE_SLEEP = 267; // Flying emote sleep
constexpr uint32_t FLY_EMOTE_SIT_GROUND = 268; // Flying emote sit ground
constexpr uint32_t FLY_EMOTE_RUDE = 269; // Flying emote rude
constexpr uint32_t FLY_EMOTE_ROAR = 270; // Flying emote roar
constexpr uint32_t FLY_EMOTE_KNEEL = 271; // Flying emote kneel
constexpr uint32_t FLY_EMOTE_KISS = 272; // Flying emote kiss
constexpr uint32_t FLY_EMOTE_CRY = 273; // Flying emote cry
constexpr uint32_t FLY_EMOTE_CHICKEN = 274; // Flying emote chicken
constexpr uint32_t FLY_EMOTE_BEG = 275; // Flying emote beg
constexpr uint32_t FLY_EMOTE_APPLAUD = 276; // Flying emote applaud
constexpr uint32_t FLY_EMOTE_SHOUT = 277; // Flying emote shout
constexpr uint32_t FLY_EMOTE_FLEX = 278; // Flying emote flex
constexpr uint32_t FLY_EMOTE_SHY = 279; // Flying emote shy
constexpr uint32_t FLY_EMOTE_POINT = 280; // Flying emote point
constexpr uint32_t FLY_ATTACK_1H_PIERCE = 281; // Flying one-hand pierce
constexpr uint32_t FLY_ATTACK_2H_LOOSE_PIERCE = 282; // Flying polearm pierce
constexpr uint32_t FLY_ATTACK_OFF = 283; // Flying off-hand attack
constexpr uint32_t FLY_ATTACK_OFF_PIERCE = 284; // Flying off-hand pierce
constexpr uint32_t FLY_SHEATHE = 285; // Flying sheathe
constexpr uint32_t FLY_HIP_SHEATHE = 286; // Flying hip sheathe
constexpr uint32_t FLY_MOUNT = 287; // Flying mounted
constexpr uint32_t FLY_RUN_RIGHT = 288; // Flying strafe run right
constexpr uint32_t FLY_RUN_LEFT = 289; // Flying strafe run left
constexpr uint32_t FLY_MOUNT_SPECIAL = 290; // Flying mount special
constexpr uint32_t FLY_KICK = 291; // Flying kick
constexpr uint32_t FLY_SIT_GROUND_DOWN = 292; // Flying sit ground down
constexpr uint32_t FLY_SITTING = 293; // Flying sitting
constexpr uint32_t FLY_SIT_GROUND_UP = 294; // Flying sit ground up
constexpr uint32_t FLY_SLEEP_DOWN = 295; // Flying sleep down
constexpr uint32_t FLY_SLEEP = 296; // Flying sleeping
constexpr uint32_t FLY_SLEEP_UP = 297; // Flying sleep up
constexpr uint32_t FLY_SIT_CHAIR_LOW = 298; // Flying sit chair low
constexpr uint32_t FLY_SIT_CHAIR_MED = 299; // Flying sit chair med
constexpr uint32_t FLY_SIT_CHAIR_HIGH = 300; // Flying sit chair high
constexpr uint32_t FLY_LOAD_BOW = 301; // Flying load bow
constexpr uint32_t FLY_LOAD_RIFLE = 302; // Flying load rifle
constexpr uint32_t FLY_ATTACK_THROWN = 303; // Flying thrown attack
constexpr uint32_t FLY_READY_THROWN = 304; // Flying thrown ready
constexpr uint32_t FLY_HOLD_BOW = 305; // Flying hold bow
constexpr uint32_t FLY_HOLD_RIFLE = 306; // Flying hold rifle
constexpr uint32_t FLY_HOLD_THROWN = 307; // Flying hold thrown
constexpr uint32_t FLY_LOAD_THROWN = 308; // Flying load thrown
constexpr uint32_t FLY_EMOTE_SALUTE = 309; // Flying emote salute
constexpr uint32_t FLY_KNEEL_START = 310; // Flying kneel start
constexpr uint32_t FLY_KNEEL_LOOP = 311; // Flying kneel loop
constexpr uint32_t FLY_KNEEL_END = 312; // Flying kneel end
constexpr uint32_t FLY_ATTACK_UNARMED_OFF = 313; // Flying off-hand unarmed
constexpr uint32_t FLY_SPECIAL_UNARMED = 314; // Flying special unarmed
constexpr uint32_t FLY_STEALTH_WALK = 315; // Flying stealth walk
constexpr uint32_t FLY_STEALTH_STAND = 316; // Flying stealth stand
constexpr uint32_t FLY_KNOCKDOWN = 317; // Flying knockdown
constexpr uint32_t FLY_EATING_LOOP = 318; // Flying eating loop
constexpr uint32_t FLY_USE_STANDING_LOOP = 319; // Flying use standing loop
constexpr uint32_t FLY_CHANNEL_CAST_DIRECTED = 320; // Flying directed channel
constexpr uint32_t FLY_CHANNEL_CAST_OMNI = 321; // Flying omni channel
constexpr uint32_t FLY_WHIRLWIND = 322; // Flying whirlwind
constexpr uint32_t FLY_BIRTH = 323; // Flying birth/spawn
constexpr uint32_t FLY_USE_STANDING_START = 324; // Flying use standing start
constexpr uint32_t FLY_USE_STANDING_END = 325; // Flying use standing end
constexpr uint32_t FLY_CREATURE_SPECIAL = 326; // Flying creature special
constexpr uint32_t FLY_DROWN = 327; // Flying drown
constexpr uint32_t FLY_DROWNED = 328; // Flying drowned
constexpr uint32_t FLY_FISHING_CAST = 329; // Flying fishing cast
constexpr uint32_t FLY_FISHING_LOOP = 330; // Flying fishing loop
constexpr uint32_t FLY_FLY = 331; // Flying fly
constexpr uint32_t FLY_EMOTE_WORK_NO_SHEATHE = 332; // Flying work no sheathe
constexpr uint32_t FLY_EMOTE_STUN_NO_SHEATHE = 333; // Flying stun no sheathe
constexpr uint32_t FLY_EMOTE_USE_STANDING_NO_SHEATHE = 334; // Flying use standing no sheathe
constexpr uint32_t FLY_SPELL_SLEEP_DOWN = 335; // Flying spell sleep down
constexpr uint32_t FLY_SPELL_KNEEL_START = 336; // Flying spell kneel start
constexpr uint32_t FLY_SPELL_KNEEL_LOOP = 337; // Flying spell kneel loop
constexpr uint32_t FLY_SPELL_KNEEL_END = 338; // Flying spell kneel end
constexpr uint32_t FLY_SPRINT = 339; // Flying sprint
constexpr uint32_t FLY_IN_FLIGHT = 340; // Flying in-flight
constexpr uint32_t FLY_SPAWN = 341; // Flying spawn
constexpr uint32_t FLY_CLOSE = 342; // Flying close
constexpr uint32_t FLY_CLOSED = 343; // Flying closed
constexpr uint32_t FLY_OPEN = 344; // Flying open
constexpr uint32_t FLY_DESTROY = 345; // Flying destroy
constexpr uint32_t FLY_DESTROYED = 346; // Flying destroyed
constexpr uint32_t FLY_UNSHEATHE = 347; // Flying unsheathe
constexpr uint32_t FLY_SHEATHE_ALT = 348; // Flying sheathe alt
constexpr uint32_t FLY_ATTACK_UNARMED_NO_SHEATHE = 349; // Flying unarmed no sheathe
constexpr uint32_t FLY_STEALTH_RUN = 350; // Flying stealth run
constexpr uint32_t FLY_READY_CROSSBOW = 351; // Flying crossbow ready
constexpr uint32_t FLY_ATTACK_CROSSBOW = 352; // Flying crossbow attack
constexpr uint32_t FLY_EMOTE_TALK_EXCLAMATION = 353; // Flying talk exclamation
constexpr uint32_t FLY_EMOTE_TALK_QUESTION = 354; // Flying talk question
constexpr uint32_t FLY_EMOTE_READ = 355; // Flying emote read
// ── WotLK extended creature animations ────────────────────────────────────
constexpr uint32_t EMOTE_HOLD_CROSSBOW = 356; // Hold crossbow emote
constexpr uint32_t FLY_EMOTE_HOLD_BOW = 357; // Flying hold bow emote
constexpr uint32_t FLY_EMOTE_HOLD_RIFLE = 358; // Flying hold rifle emote
constexpr uint32_t FLY_EMOTE_HOLD_THROWN = 359; // Flying hold thrown emote
constexpr uint32_t FLY_EMOTE_HOLD_CROSSBOW = 360; // Flying hold crossbow emote
constexpr uint32_t FLY_CUSTOM_SPELL_02 = 361; // Flying custom spell 02
constexpr uint32_t FLY_CUSTOM_SPELL_03 = 362; // Flying custom spell 03
constexpr uint32_t FLY_CUSTOM_SPELL_04 = 363; // Flying custom spell 04
constexpr uint32_t FLY_CUSTOM_SPELL_05 = 364; // Flying custom spell 05
constexpr uint32_t FLY_CUSTOM_SPELL_06 = 365; // Flying custom spell 06
constexpr uint32_t FLY_CUSTOM_SPELL_07 = 366; // Flying custom spell 07
constexpr uint32_t FLY_CUSTOM_SPELL_08 = 367; // Flying custom spell 08
constexpr uint32_t FLY_CUSTOM_SPELL_09 = 368; // Flying custom spell 09
constexpr uint32_t FLY_CUSTOM_SPELL_10 = 369; // Flying custom spell 10
constexpr uint32_t FLY_EMOTE_STATE_DANCE = 370; // Flying dance state
constexpr uint32_t EMOTE_EAT_NO_SHEATHE = 371; // Eat emote (no weapon sheathe)
constexpr uint32_t MOUNT_RUN_RIGHT = 372; // Mounted strafe run right
constexpr uint32_t MOUNT_RUN_LEFT = 373; // Mounted strafe run left
constexpr uint32_t MOUNT_WALK_BACKWARDS = 374; // Mounted walk backwards
constexpr uint32_t MOUNT_SWIM_IDLE = 375; // Mounted swimming idle
constexpr uint32_t MOUNT_SWIM = 376; // Mounted swimming forward
constexpr uint32_t MOUNT_SWIM_LEFT = 377; // Mounted swimming left
constexpr uint32_t MOUNT_SWIM_RIGHT = 378; // Mounted swimming right
constexpr uint32_t MOUNT_SWIM_BACKWARDS = 379; // Mounted swimming backwards
constexpr uint32_t MOUNT_FLIGHT_IDLE = 380; // Mounted flight idle (hovering)
constexpr uint32_t MOUNT_FLIGHT_FORWARD = 381; // Mounted flight forward
constexpr uint32_t MOUNT_FLIGHT_BACKWARDS = 382; // Mounted flight backwards
constexpr uint32_t MOUNT_FLIGHT_LEFT = 383; // Mounted flight left
constexpr uint32_t MOUNT_FLIGHT_RIGHT = 384; // Mounted flight right
constexpr uint32_t MOUNT_FLIGHT_UP = 385; // Mounted flight ascending
constexpr uint32_t MOUNT_FLIGHT_DOWN = 386; // Mounted flight descending
constexpr uint32_t MOUNT_FLIGHT_LAND_START = 387; // Mounted flight land start
constexpr uint32_t MOUNT_FLIGHT_LAND_RUN = 388; // Mounted flight land run
constexpr uint32_t MOUNT_FLIGHT_LAND_END = 389; // Mounted flight land end
constexpr uint32_t FLY_EMOTE_STATE_LAUGH = 390; // Flying laugh state
constexpr uint32_t FLY_EMOTE_STATE_POINT = 391; // Flying point state
constexpr uint32_t FLY_EMOTE_STATE_EAT = 392; // Flying eat state
constexpr uint32_t FLY_EMOTE_STATE_WORK = 393; // Flying work state
constexpr uint32_t FLY_EMOTE_STATE_SIT_GROUND = 394; // Flying sit ground state
constexpr uint32_t FLY_EMOTE_STATE_HOLD_BOW = 395; // Flying hold bow state
constexpr uint32_t FLY_EMOTE_STATE_HOLD_RIFLE = 396; // Flying hold rifle state
constexpr uint32_t FLY_EMOTE_STATE_HOLD_THROWN = 397; // Flying hold thrown state
constexpr uint32_t FLY_EMOTE_STATE_ROAR = 398; // Flying roar state
constexpr uint32_t FLY_RECLINED = 399; // Flying reclined
constexpr uint32_t EMOTE_TRAIN_2 = 400; // /train variant — choo choo!
constexpr uint32_t EMOTE_DEAD_2 = 401; // /dead variant (play dead)
constexpr uint32_t FLY_EMOTE_USE_STANDING_LOOP_2 = 402; // Flying use standing loop
constexpr uint32_t FLY_EMOTE_STATE_APPLAUD = 403; // Flying applaud state
constexpr uint32_t FLY_READY_FIST = 404; // Flying fist ready
constexpr uint32_t FLY_SPELL_CHANNEL_DIRECTED_OMNI = 405; // Flying channel directed omni
constexpr uint32_t FLY_SPECIAL_ATTACK_1H_OFF = 406; // Flying special off-hand
constexpr uint32_t FLY_ATTACK_FIST_1H = 407; // Flying fist attack
constexpr uint32_t FLY_ATTACK_FIST_1H_OFF = 408; // Flying fist off-hand
constexpr uint32_t FLY_PARRY_FIST_1H = 409; // Flying fist parry
constexpr uint32_t FLY_READY_FIST_1H = 410; // Flying fist one-hand ready
constexpr uint32_t FLY_EMOTE_STATE_READ_AND_TALK = 411; // Flying read and talk state
constexpr uint32_t FLY_EMOTE_STATE_WORK_NO_SHEATHE = 412; // Flying work no sheathe state
constexpr uint32_t FLY_EMOTE_STATE_KNEEL_2 = 413; // Flying kneel state variant
constexpr uint32_t FLY_EMOTE_STATE_SPELL_KNEEL = 414; // Flying spell kneel state
constexpr uint32_t FLY_EMOTE_STATE_USE_STANDING = 415; // Flying use standing state
constexpr uint32_t FLY_EMOTE_STATE_STUN = 416; // Flying stun state
constexpr uint32_t FLY_EMOTE_STATE_STUN_NO_SHEATHE = 417; // Flying stun no sheathe state
constexpr uint32_t FLY_EMOTE_TRAIN = 418; // Flying train emote
constexpr uint32_t FLY_EMOTE_DEAD = 419; // Flying dead emote
constexpr uint32_t FLY_EMOTE_STATE_DANCE_ONCE = 420; // Flying single dance
constexpr uint32_t FLY_EMOTE_EAT_NO_SHEATHE = 421; // Flying eat no sheathe
constexpr uint32_t FLY_MOUNT_RUN_RIGHT = 422; // Flying mount run right
constexpr uint32_t FLY_MOUNT_RUN_LEFT = 423; // Flying mount run left
constexpr uint32_t FLY_MOUNT_WALK_BACKWARDS = 424; // Flying mount walk backwards
constexpr uint32_t FLY_MOUNT_SWIM_IDLE = 425; // Flying mount swim idle
constexpr uint32_t FLY_MOUNT_SWIM = 426; // Flying mount swim
constexpr uint32_t FLY_MOUNT_SWIM_LEFT = 427; // Flying mount swim left
constexpr uint32_t FLY_MOUNT_SWIM_RIGHT = 428; // Flying mount swim right
constexpr uint32_t FLY_MOUNT_SWIM_BACKWARDS = 429; // Flying mount swim backwards
constexpr uint32_t FLY_MOUNT_FLIGHT_IDLE = 430; // Flying mount flight idle
constexpr uint32_t FLY_MOUNT_FLIGHT_FORWARD = 431; // Flying mount flight forward
constexpr uint32_t FLY_MOUNT_FLIGHT_BACKWARDS = 432; // Flying mount flight backwards
constexpr uint32_t FLY_MOUNT_FLIGHT_LEFT = 433; // Flying mount flight left
constexpr uint32_t FLY_MOUNT_FLIGHT_RIGHT = 434; // Flying mount flight right
constexpr uint32_t FLY_MOUNT_FLIGHT_UP = 435; // Flying mount flight up
constexpr uint32_t FLY_MOUNT_FLIGHT_DOWN = 436; // Flying mount flight down
constexpr uint32_t FLY_MOUNT_FLIGHT_LAND_START = 437; // Flying mount flight land start
constexpr uint32_t FLY_MOUNT_FLIGHT_LAND_RUN = 438; // Flying mount flight land run
constexpr uint32_t FLY_MOUNT_FLIGHT_LAND_END = 439; // Flying mount flight land end
constexpr uint32_t FLY_TOTEM_SMALL = 440; // Flying small totem
constexpr uint32_t FLY_TOTEM_MEDIUM = 441; // Flying medium totem
constexpr uint32_t FLY_TOTEM_LARGE = 442; // Flying large totem
constexpr uint32_t FLY_EMOTE_HOLD_CROSSBOW_2 = 443; // Flying hold crossbow (variant)
// ── WotLK vehicle-specific & late additions ───────────────────────────────
constexpr uint32_t VEHICLE_GRAB = 444; // Vehicle: grab object
constexpr uint32_t VEHICLE_THROW = 445; // Vehicle: throw object
constexpr uint32_t FLY_VEHICLE_GRAB = 446; // Flying vehicle grab
constexpr uint32_t FLY_VEHICLE_THROW = 447; // Flying vehicle throw
constexpr uint32_t GUILD_CHAMPION_1 = 448; // Guild champion pose 1
constexpr uint32_t GUILD_CHAMPION_2 = 449; // Guild champion pose 2
constexpr uint32_t FLY_GUILD_CHAMPION_1 = 450; // Flying guild champion 1
constexpr uint32_t FLY_GUILD_CHAMPION_2 = 451; // Flying guild champion 2
// Total number of animation IDs (0451 inclusive)
constexpr uint32_t ANIM_COUNT = 452;
/// Return the symbolic name for an animation ID (e.g. 0 → "STAND").
/// Returns "UNKNOWN" for IDs outside the known range.
const char* nameFromId(uint32_t id);
/// Return the FLY_* variant of a ground animation ID, or 0 if none exists.
uint32_t flyVariant(uint32_t groundId);
/// Validate animation_ids.hpp constants against AnimationData.dbc.
/// Logs warnings for IDs present in DBC but missing from constants, and vice versa.
void validateAgainstDBC(const std::shared_ptr<wowee::pipeline::DBCFile>& dbc);
} // namespace anim
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,56 @@
#pragma once
// Renamed from PlayerAnimator/NpcAnimator dual-map → unified CharacterAnimator registry.
// NpcAnimator removed — all characters use the same generic CharacterAnimator.
#include "rendering/animation/character_animator.hpp"
#include "rendering/animation/anim_capability_set.hpp"
#include <cstdint>
#include <unordered_map>
#include <memory>
namespace wowee {
namespace rendering {
// ============================================================================
// AnimationManager
//
// Central registry for all character animators. Owned by Renderer, replaces
// scattered AnimationController* passing.
//
// Single animator type:
// CharacterAnimator — generic animator for any character (player, NPC,
// companion). Full FSM composition with priority
// resolver.
//
// AnimationController becomes a thin shim delegating to this manager
// until all callsites are migrated.
// ============================================================================
class AnimationManager {
public:
AnimationManager() = default;
// ── Character animators ─────────────────────────────────────────────
/// Get or create a CharacterAnimator for the given instance ID.
CharacterAnimator& getOrCreate(uint32_t instanceId);
/// Get existing CharacterAnimator (nullptr if not found).
CharacterAnimator* get(uint32_t instanceId);
/// Remove a character animator.
void remove(uint32_t instanceId);
// ── Per-frame ───────────────────────────────────────────────────────
/// Update all registered animators.
void updateAll(float dt);
// ── Counts ──────────────────────────────────────────────────────────
size_t count() const { return animators_.size(); }
private:
std::unordered_map<uint32_t, std::unique_ptr<CharacterAnimator>> animators_;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,145 @@
#pragma once
#include "rendering/animation/i_character_animator.hpp"
#include "rendering/animation/anim_capability_set.hpp"
#include "rendering/animation/locomotion_fsm.hpp"
#include "rendering/animation/combat_fsm.hpp"
#include "rendering/animation/activity_fsm.hpp"
#include "rendering/animation/mount_fsm.hpp"
#include <cstdint>
namespace wowee {
namespace rendering {
// ============================================================================
// CharacterAnimator
//
// Generic animator for any character (player, NPC, companion).
// Composes LocomotionFSM, CombatFSM, ActivityFSM, and MountFSM with
// a priority resolver. Implements ICharacterAnimator.
//
// Priority order: Stun > HitReaction > Spell > Charge > Combat > Activity > Locomotion
//
// No idle fallback: if all FSMs return {valid=false}, last animation continues.
//
// Overlay layer (stealth, sprint) substitutes the resolved anim without
// changing sub-FSM state.
// ============================================================================
class CharacterAnimator final : public ICharacterAnimator {
public:
CharacterAnimator();
// ── IAnimator ───────────────────────────────────────────────────────
void onEvent(AnimEvent event) override;
void update(float dt) override;
// ── ICharacterAnimator ──────────────────────────────────────────────
void startSpellCast(uint32_t precast, uint32_t cast, bool loop, uint32_t finalize) override;
void stopSpellCast() override;
void triggerMeleeSwing() override;
void triggerRangedShot() override;
void triggerHitReaction(uint32_t animId) override;
void triggerSpecialAttack(uint32_t spellId) override;
void setEquippedWeaponType(const WeaponLoadout& loadout) override;
void setEquippedRangedType(RangedWeaponType type) override;
void playEmote(uint32_t animId, bool loop) override;
void cancelEmote() override;
void startLooting() override;
void stopLooting() override;
void setStunned(bool stunned) override;
void setCharging(bool charging) override;
void setStandState(uint8_t state) override;
void setStealthed(bool stealth) override;
void setInCombat(bool combat) override;
void setLowHealth(bool low) override;
void setSprintAuraActive(bool active) override;
// ── Configuration ───────────────────────────────────────────────────
void setCapabilities(const AnimCapabilitySet& caps) { caps_ = caps; }
const AnimCapabilitySet& getCapabilities() const { return caps_; }
void setWeaponLoadout(const WeaponLoadout& loadout) { loadout_ = loadout; }
const WeaponLoadout& getWeaponLoadout() const { return loadout_; }
// ── Mount ───────────────────────────────────────────────────────────
void configureMountFSM(const MountFSM::MountAnimSet& anims, bool taxiFlight);
void clearMountFSM();
bool isMountActive() const { return mount_.isActive(); }
MountFSM& getMountFSM() { return mount_; }
// ── Sub-FSM access (for transition queries) ─────────────────────────
LocomotionFSM& getLocomotion() { return locomotion_; }
const LocomotionFSM& getLocomotion() const { return locomotion_; }
CombatFSM& getCombat() { return combat_; }
const CombatFSM& getCombat() const { return combat_; }
ActivityFSM& getActivity() { return activity_; }
const ActivityFSM& getActivity() const { return activity_; }
// ── Last resolved output ────────────────────────────────────────────
AnimOutput getLastOutput() const { return lastOutput_; }
// ── Input injection (set per-frame from AnimationController) ────────
struct FrameInput {
// From camera controller
bool moving = false;
bool sprinting = false;
bool movingForward = false;
bool movingBackward = false;
bool autoRunning = false;
bool strafeLeft = false;
bool strafeRight = false;
bool grounded = true;
bool jumping = false;
bool swimming = false;
bool sitting = false;
bool flyingActive = false;
bool ascending = false;
bool descending = false;
bool jumpKeyPressed = false;
float characterYaw = 0.0f;
// Melee/ranged timers
float meleeSwingTimer = 0.0f;
float rangedShootTimer = 0.0f;
uint32_t specialAttackAnimId = 0;
uint32_t rangedAnimId = 0;
// Animation state query
uint32_t currentAnimId = 0;
float currentAnimTime = 0.0f;
float currentAnimDuration = 0.0f;
bool haveAnimState = false;
// Mount state query
uint32_t curMountAnim = 0;
float curMountTime = 0.0f;
float curMountDuration = 0.0f;
bool haveMountState = false;
};
void setFrameInput(const FrameInput& input) { frameInput_ = input; }
private:
AnimCapabilitySet caps_;
WeaponLoadout loadout_;
LocomotionFSM locomotion_;
CombatFSM combat_;
ActivityFSM activity_;
MountFSM mount_;
// Overlay flags
bool stealthed_ = false;
bool sprintAura_ = false;
bool lowHealth_ = false;
bool inCombat_ = false;
float lastDt_ = 0.0f;
FrameInput frameInput_;
AnimOutput lastOutput_;
/// Priority resolver: highest-priority active FSM wins.
AnimOutput resolveAnimation();
/// Apply stealth/sprint overlays to the resolved animation.
AnimOutput applyOverlays(AnimOutput base) const;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,120 @@
#pragma once
#include "rendering/animation/anim_capability_set.hpp"
#include "rendering/animation/anim_event.hpp"
#include "rendering/animation/weapon_type.hpp"
#include <cstdint>
namespace wowee {
namespace rendering {
// ============================================================================
// CombatFSM
//
// Pure logic state machine for combat animation. No renderer dependency.
// States: INACTIVE · COMBAT_IDLE · MELEE_SWING · RANGED_SHOOT · RANGED_LOAD ·
// SPELL_PRECAST · SPELL_CASTING · SPELL_FINALIZE · HIT_REACTION ·
// STUNNED · CHARGE · UNSHEATHE · SHEATHE
//
// Stun overrides all combat states. Spell state cleared on interrupts.
// offHandTurn_ alternation managed internally.
// ============================================================================
class CombatFSM {
public:
enum class State : uint8_t {
INACTIVE,
COMBAT_IDLE,
MELEE_SWING,
RANGED_SHOOT,
RANGED_LOAD,
SPELL_PRECAST,
SPELL_CASTING,
SPELL_FINALIZE,
HIT_REACTION,
STUNNED,
CHARGE,
UNSHEATHE,
SHEATHE,
};
struct Input {
bool inCombat = false;
bool grounded = true;
bool jumping = false;
bool swimming = false;
bool moving = false;
bool sprinting = false;
bool lowHealth = false;
float meleeSwingTimer = 0.0f; // >0 = melee active
float rangedShootTimer = 0.0f; // >0 = ranged active
uint32_t specialAttackAnimId = 0;
uint32_t rangedAnimId = 0;
// Animation state query for one-shot completion detection
uint32_t currentAnimId = 0;
float currentAnimTime = 0.0f;
float currentAnimDuration = 0.0f;
bool haveAnimState = false;
// Whether model has specific one-shot animations
bool hasUnsheathe = false;
bool hasSheathe = false;
};
void onEvent(AnimEvent event);
/// Evaluate current state against input and capabilities.
AnimOutput resolve(const Input& in, const AnimCapabilitySet& caps,
const WeaponLoadout& loadout);
State getState() const { return state_; }
void setState(State s) { state_ = s; }
bool isStunned() const { return state_ == State::STUNNED; }
bool isActive() const { return state_ != State::INACTIVE; }
void reset();
// ── Spell cast management ───────────────────────────────────────────
void startSpellCast(uint32_t precast, uint32_t cast, bool castLoop, uint32_t finalize);
void stopSpellCast();
void clearSpellState();
// ── Hit/stun management ─────────────────────────────────────────────
void triggerHitReaction(uint32_t animId);
void setStunned(bool stunned);
void setCharging(bool charging);
static constexpr uint8_t PRIORITY = 50;
private:
State state_ = State::INACTIVE;
// Spell cast sequence
uint32_t spellPrecastAnimId_ = 0;
uint32_t spellCastAnimId_ = 0;
uint32_t spellFinalizeAnimId_ = 0;
bool spellCastLoop_ = false;
bool spellPrecastAnimSeen_ = false;
uint8_t spellPrecastFrames_ = 0;
bool spellFinalizeAnimSeen_ = false;
uint8_t spellFinalizeFrames_ = 0;
// Hit reaction
uint32_t hitReactionAnimId_ = 0;
// Stun
bool stunned_ = false;
// Charge
bool charging_ = false;
// Off-hand alternation for dual wielding
bool offHandTurn_ = false;
/// Internal: update state transitions based on input.
void updateTransitions(const Input& in);
/// Detect if a one-shot animation has completed.
bool oneShotComplete(const Input& in, uint32_t expectedAnimId) const;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,77 @@
#pragma once
#include <cstdint>
#include <string>
#include <optional>
#include <unordered_map>
#include <vector>
namespace wowee {
namespace rendering {
// ============================================================================
// EmoteRegistry — extracted from AnimationController
//
// Owns all static emote data, DBC loading, emote text lookup, and
// animation ID resolution. Singleton — loaded once on first use.
// ============================================================================
struct EmoteInfo {
uint32_t animId = 0;
uint32_t dbcId = 0;
bool loop = false;
std::string textNoTarget;
std::string textTarget;
std::string othersNoTarget;
std::string othersTarget;
std::string command;
};
class EmoteRegistry {
public:
static EmoteRegistry& instance();
/// Load emotes from DBC files (called once on first use).
void loadFromDbc();
struct EmoteResult { uint32_t animId; bool loop; };
/// Look up an emote by chat command (e.g. "dance", "wave").
std::optional<EmoteResult> findEmote(const std::string& command) const;
/// Get the animation ID for a DBC emote ID.
uint32_t animByDbcId(uint32_t dbcId) const;
/// Get the emote state variant (looping) for a one-shot emote animation.
uint32_t getStateVariant(uint32_t oneShotAnimId) const;
/// Get first-person emote text for a command.
std::string textFor(const std::string& emoteName,
const std::string* targetName = nullptr) const;
/// Get DBC ID for an emote command.
uint32_t dbcIdFor(const std::string& emoteName) const;
/// Get third-person emote text by DBC ID.
std::string textByDbcId(uint32_t dbcId,
const std::string& senderName,
const std::string* targetName = nullptr) const;
/// Get the full EmoteInfo for a command (nullptr if not found).
const EmoteInfo* findInfo(const std::string& command) const;
private:
EmoteRegistry() = default;
EmoteRegistry(const EmoteRegistry&) = delete;
EmoteRegistry& operator=(const EmoteRegistry&) = delete;
void loadFallbackEmotes();
void buildDbcIdIndex();
bool loaded_ = false;
std::unordered_map<std::string, EmoteInfo> emoteTable_;
std::unordered_map<uint32_t, const EmoteInfo*> emoteByDbcId_;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,54 @@
#pragma once
#include <cstdint>
#include <glm/glm.hpp>
namespace wowee {
namespace audio { enum class FootstepSurface : uint8_t; }
namespace rendering {
class Renderer;
// ============================================================================
// FootstepDriver — extracted from AnimationController
//
// Owns animation-driven footstep event detection, surface resolution,
// and player/mount footstep tracking state.
// ============================================================================
class FootstepDriver {
public:
FootstepDriver() = default;
/// Process footstep events for this frame (called from Renderer::update).
void update(float deltaTime, Renderer* renderer,
bool mounted, uint32_t mountInstanceId, bool taxiFlight,
bool isFootstepState);
/// Detect if a footstep event should trigger based on animation phase crossing.
bool shouldTriggerFootstepEvent(uint32_t animationId, float animationTimeMs,
float animationDurationMs);
/// Resolve the surface type under the character for footstep sound selection.
audio::FootstepSurface resolveFootstepSurface(Renderer* renderer) const;
private:
// Player footstep event tracking (animation-driven)
uint32_t footstepLastAnimationId_ = 0;
float footstepLastNormTime_ = 0.0f;
bool footstepNormInitialized_ = false;
// Footstep surface cache (avoid expensive queries every step)
mutable audio::FootstepSurface cachedFootstepSurface_{};
mutable glm::vec3 cachedFootstepPosition_{0.0f, 0.0f, 0.0f};
mutable float cachedFootstepUpdateTimer_{999.0f};
// Mount footstep tracking (separate from player's)
uint32_t mountFootstepLastAnimId_ = 0;
float mountFootstepLastNormTime_ = 0.0f;
bool mountFootstepNormInitialized_ = false;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,31 @@
#pragma once
#include "rendering/animation/anim_capability_set.hpp"
#include "rendering/animation/anim_event.hpp"
#include <cstdint>
#include <vector>
namespace wowee {
namespace pipeline { struct M2Sequence; }
namespace rendering {
// ============================================================================
// IAnimRenderer
//
// Abstraction for renderer animation operations. Sub-FSMs and animators
// talk to this interface, not to CharacterRenderer directly.
// ============================================================================
class IAnimRenderer {
public:
virtual void playAnimation(uint32_t instanceId, uint32_t animId, bool loop) = 0;
virtual bool hasAnimation(uint32_t instanceId, uint32_t animId) const = 0;
virtual bool getAnimationState(uint32_t instanceId, uint32_t& outAnimId,
float& outTimeMs, float& outDurMs) const = 0;
virtual bool getAnimationSequences(uint32_t instanceId,
std::vector<pipeline::M2Sequence>& out) const = 0;
virtual ~IAnimRenderer() = default;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,21 @@
#pragma once
#include "rendering/animation/anim_event.hpp"
namespace wowee {
namespace rendering {
// ============================================================================
// IAnimator
//
// Base interface for all entity animators. Common to player + NPC.
// ============================================================================
class IAnimator {
public:
virtual void onEvent(AnimEvent event) = 0;
virtual void update(float dt) = 0;
virtual ~IAnimator() = default;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,42 @@
#pragma once
#include "rendering/animation/i_animator.hpp"
#include "rendering/animation/weapon_type.hpp"
#include <cstdint>
#include <string>
namespace wowee {
namespace rendering {
// ============================================================================
// ICharacterAnimator
//
// Player-specific animation interface. Extends IAnimator with combat,
// spell, emote, and mount operations.
// ============================================================================
class ICharacterAnimator : public IAnimator {
public:
virtual void startSpellCast(uint32_t precast, uint32_t cast, bool loop, uint32_t finalize) = 0;
virtual void stopSpellCast() = 0;
virtual void triggerMeleeSwing() = 0;
virtual void triggerRangedShot() = 0;
virtual void triggerHitReaction(uint32_t animId) = 0;
virtual void triggerSpecialAttack(uint32_t spellId) = 0;
virtual void setEquippedWeaponType(const WeaponLoadout& loadout) = 0;
virtual void setEquippedRangedType(RangedWeaponType type) = 0;
virtual void playEmote(uint32_t animId, bool loop) = 0;
virtual void cancelEmote() = 0;
virtual void startLooting() = 0;
virtual void stopLooting() = 0;
virtual void setStunned(bool stunned) = 0;
virtual void setCharging(bool charging) = 0;
virtual void setStandState(uint8_t state) = 0;
virtual void setStealthed(bool stealth) = 0;
virtual void setInCombat(bool combat) = 0;
virtual void setLowHealth(bool low) = 0;
virtual void setSprintAuraActive(bool active) = 0;
virtual ~ICharacterAnimator() = default;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,79 @@
#pragma once
#include "rendering/animation/anim_capability_set.hpp"
#include "rendering/animation/anim_event.hpp"
#include <cstdint>
namespace wowee {
namespace rendering {
// ============================================================================
// LocomotionFSM
//
// Pure logic state machine for movement animation. No renderer dependency.
// States: IDLE · WALK · RUN · JUMP_START · JUMP_MID · JUMP_END · SWIM_IDLE · SWIM
//
// Grace timer is internal — no external locomotionStopGraceTimer_ needed.
// ============================================================================
class LocomotionFSM {
public:
enum class State : uint8_t {
IDLE, WALK, RUN,
JUMP_START, JUMP_MID, JUMP_END,
SWIM_IDLE, SWIM,
};
struct Input {
bool moving = false;
bool movingForward = false;
bool sprinting = false;
bool movingBackward = false;
bool strafeLeft = false;
bool strafeRight = false;
bool grounded = true;
bool jumping = false;
bool swimming = false;
bool sitting = false;
bool sprintAura = false; // Sprint/Dash aura — use SPRINT anim
float deltaTime = 0.0f;
// Animation state for one-shot completion detection (jump start/end)
uint32_t currentAnimId = 0;
float currentAnimTime = 0.0f;
float currentAnimDuration = 0.0f;
bool haveAnimState = false;
};
/// Process event and update internal state.
void onEvent(AnimEvent event);
/// Evaluate current state against input and capabilities.
/// Returns AnimOutput with valid=false if no change needed (STAY policy).
AnimOutput resolve(const Input& in, const AnimCapabilitySet& caps);
State getState() const { return state_; }
void setState(State s) { state_ = s; }
void reset();
static constexpr uint8_t PRIORITY = 10;
private:
State state_ = State::IDLE;
// Grace timer: short delay before switching from WALK/RUN to IDLE
// to avoid flickering on network jitter
float graceTimer_ = 0.0f;
bool wasSprinting_ = false;
// One-shot tracking for jump start/end animations
bool jumpStartSeen_ = false;
bool jumpEndSeen_ = false;
static constexpr float kGraceSec = 0.12f;
/// Internal: update state transitions based on input.
void updateTransitions(const Input& in, const AnimCapabilitySet& caps);
bool oneShotComplete(const Input& in, uint32_t expectedAnimId) const;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,142 @@
#pragma once
#include "rendering/animation/anim_capability_set.hpp"
#include "rendering/animation/anim_event.hpp"
#include <cstdint>
#include <vector>
#include <random>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
// ============================================================================
// MountFSM
//
// Self-contained mount animation state machine. Replaces the ~400-line
// mounted branch of updateCharacterAnimation() and eliminates the `goto`.
//
// Owns: fidget timer, RNG, idle sound timer, mount action state.
// All static RNG replaced with per-instance members.
// ============================================================================
class MountFSM {
public:
// Animation set discovered at mount time (property-based, not hardcoded)
struct MountAnimSet {
uint32_t jumpStart = 0;
uint32_t jumpLoop = 0;
uint32_t jumpEnd = 0;
uint32_t rearUp = 0;
uint32_t run = 0;
uint32_t stand = 0;
// Flight animations
uint32_t flyIdle = 0;
uint32_t flyForward = 0;
uint32_t flyBackwards = 0;
uint32_t flyLeft = 0;
uint32_t flyRight = 0;
uint32_t flyUp = 0;
uint32_t flyDown = 0;
std::vector<uint32_t> fidgets;
};
enum class MountState : uint8_t {
IDLE, RUN, JUMP_START, JUMP_LOOP, JUMP_LAND, REAR_UP, FLY,
};
enum class MountAction : uint8_t { None, Jump, RearUp };
struct Input {
bool moving = false;
bool movingBackward = false;
bool strafeLeft = false;
bool strafeRight = false;
bool grounded = true;
bool jumpKeyPressed = false;
bool flying = false;
bool swimming = false;
bool ascending = false;
bool descending = false;
bool taxiFlight = false;
float deltaTime = 0.0f;
float characterYaw = 0.0f;
// Mount anim state query
uint32_t curMountAnim = 0;
float curMountTime = 0.0f;
float curMountDuration = 0.0f;
bool haveMountState = false;
};
/// Output from evaluate(): what to play on rider + mount, and positioning data.
struct Output {
// Mount animation
uint32_t mountAnimId = 0;
bool mountAnimLoop = true;
bool mountAnimChanged = false; // true = should call playAnimation
// Rider animation
uint32_t riderAnimId = 0;
bool riderAnimLoop = true;
bool riderAnimChanged = false;
// Mount procedural motion
float mountBob = 0.0f; // Vertical bob offset
float mountPitch = 0.0f; // Pitch (forward lean)
float mountRoll = 0.0f; // Roll (banking)
// Signals
bool playJumpSound = false;
bool playLandSound = false;
bool playRearUpSound = false;
bool playIdleSound = false;
bool triggerMountJump = false; // Tell camera controller to jump
bool fidgetStarted = false;
};
void configure(const MountAnimSet& anims, bool taxiFlight);
void clear();
void onEvent(AnimEvent event);
/// Main evaluation: produces Output describing what to play.
Output evaluate(const Input& in);
bool isActive() const { return active_; }
MountState getState() const { return state_; }
MountAction getAction() const { return action_; }
const MountAnimSet& getAnims() const { return anims_; }
private:
bool active_ = false;
MountState state_ = MountState::IDLE;
MountAction action_ = MountAction::None;
uint32_t actionPhase_ = 0;
MountAnimSet anims_;
bool taxiFlight_ = false;
// Fidget system — per-instance, not static
float fidgetTimer_ = 0.0f;
float nextFidgetTime_ = 8.0f;
uint32_t activeFidget_ = 0;
std::mt19937 rng_;
// Idle ambient sound timer
float idleSoundTimer_ = 0.0f;
float nextIdleSoundTime_ = 60.0f;
// Procedural lean
float prevYaw_ = 0.0f;
float roll_ = 0.0f;
// Last mount animation for change detection
uint32_t lastMountAnim_ = 0;
/// Resolve the mount animation for the given input (non-taxi).
uint32_t resolveGroundOrFlyAnim(const Input& in) const;
/// Check if an action animation has completed.
bool actionAnimComplete(const Input& in) const;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,35 @@
#pragma once
#include <cstdint>
namespace wowee {
namespace rendering {
class Renderer;
class FootstepDriver;
// ============================================================================
// SfxStateDriver — extracted from AnimationController
//
// Tracks state transitions for activity SFX (jump, landing, swim) and
// mount ambient sounds.
// ============================================================================
class SfxStateDriver {
public:
SfxStateDriver() = default;
/// Track state transitions and trigger appropriate SFX.
void update(float deltaTime, Renderer* renderer,
bool mounted, bool taxiFlight,
FootstepDriver& footstepDriver);
private:
bool initialized_ = false;
bool prevGrounded_ = true;
bool prevJumping_ = false;
bool prevFalling_ = false;
bool prevSwimming_ = false;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,28 @@
#pragma once
#include <cstdint>
namespace wowee {
namespace rendering {
/// Ranged weapon type for animation selection (bow/gun/crossbow/thrown)
enum class RangedWeaponType : uint8_t { NONE = 0, BOW, GUN, CROSSBOW, THROWN };
// ============================================================================
// WeaponLoadout — extracted from AnimationController
//
// Consolidates the 6 weapon boolean fields + inventory type + ranged type
// into a single value type.
// ============================================================================
struct WeaponLoadout {
uint32_t inventoryType = 0;
bool is2HLoose = false; // Polearm or staff
bool isFist = false; // Fist weapon
bool isDagger = false; // Dagger (uses pierce variants)
bool hasOffHand = false; // Has off-hand weapon (dual wield)
bool hasShield = false; // Has shield equipped (for SHIELD_BASH)
RangedWeaponType rangedType = RangedWeaponType::NONE;
};
} // namespace rendering
} // namespace wowee