Kelsidavis-WoWee/include/rendering/animation/mount_fsm.hpp
Paul b4989dc11f 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).
2026-04-05 12:27:35 +03:00

142 lines
4.3 KiB
C++

#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