mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-06 05:03:52 +00:00
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).
224 lines
12 KiB
C++
224 lines
12 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <glm/glm.hpp>
|
|
#include "rendering/animation/footstep_driver.hpp"
|
|
#include "rendering/animation/sfx_state_driver.hpp"
|
|
#include "rendering/animation/weapon_type.hpp"
|
|
#include "rendering/animation/anim_capability_set.hpp"
|
|
#include "rendering/animation/character_animator.hpp"
|
|
|
|
namespace wowee {
|
|
namespace rendering {
|
|
|
|
class Renderer;
|
|
|
|
// ============================================================================
|
|
// AnimationController — thin adapter wrapping CharacterAnimator
|
|
//
|
|
// Bridges the Renderer world (camera state, CharacterRenderer, audio)
|
|
// and the pure-logic CharacterAnimator + sub-FSMs. Public API unchanged.
|
|
//
|
|
// Responsibilities:
|
|
// · Collect inputs from renderer/camera → CharacterAnimator::FrameInput
|
|
// · Forward state-changing calls → CharacterAnimator
|
|
// · Read AnimOutput from CharacterAnimator → apply via CharacterRenderer
|
|
// · Mount discovery (needs renderer for sequence queries)
|
|
// · Mount positioning (needs renderer for attachment queries)
|
|
// · Melee/ranged resolution (needs renderer for sequence queries)
|
|
// · Footstep and SFX drivers (already extracted)
|
|
// ============================================================================
|
|
class AnimationController {
|
|
public:
|
|
AnimationController();
|
|
~AnimationController();
|
|
|
|
void initialize(Renderer* renderer);
|
|
|
|
/// Probe (or re-probe) animation capabilities for the current character model.
|
|
/// Called once during initialize() / onCharacterFollow() and after model changes.
|
|
void probeCapabilities();
|
|
const AnimCapabilitySet& getCapabilities() const { return characterAnimator_.getCapabilities(); }
|
|
|
|
// ── Per-frame update hooks (called from Renderer::update) ──────────────
|
|
// Runs the character animation state machine (mounted + unmounted).
|
|
void updateCharacterAnimation();
|
|
// Processes animation-driven footstep events (player + mount).
|
|
void updateFootsteps(float deltaTime);
|
|
// Tracks state transitions for activity SFX (jump, landing, swim) and
|
|
// mount ambient sounds.
|
|
void updateSfxState(float deltaTime);
|
|
// Decrements melee swing timer / cooldown.
|
|
void updateMeleeTimers(float deltaTime);
|
|
// Store per-frame delta time (used inside animation state machine).
|
|
void setDeltaTime(float dt) { lastDeltaTime_ = dt; }
|
|
|
|
// ── Character follow ───────────────────────────────────────────────────
|
|
void onCharacterFollow(uint32_t instanceId);
|
|
|
|
// ── Emote support ──────────────────────────────────────────────────────
|
|
void playEmote(const std::string& emoteName);
|
|
void cancelEmote();
|
|
bool isEmoteActive() const { return characterAnimator_.getActivity().isEmoteActive(); }
|
|
static std::string getEmoteText(const std::string& emoteName,
|
|
const std::string* targetName = nullptr);
|
|
static uint32_t getEmoteDbcId(const std::string& emoteName);
|
|
static std::string getEmoteTextByDbcId(uint32_t dbcId,
|
|
const std::string& senderName,
|
|
const std::string* targetName = nullptr);
|
|
static uint32_t getEmoteAnimByDbcId(uint32_t dbcId);
|
|
|
|
// ── Targeting / combat ─────────────────────────────────────────────────
|
|
void setTargetPosition(const glm::vec3* pos);
|
|
void setInCombat(bool combat);
|
|
bool isInCombat() const { return inCombat_; }
|
|
const glm::vec3* getTargetPosition() const { return targetPosition_; }
|
|
void resetCombatVisualState();
|
|
bool isMoving() const;
|
|
|
|
// ── Melee combat ───────────────────────────────────────────────────────
|
|
void triggerMeleeSwing();
|
|
/// inventoryType: WoW inventory type (0=unarmed, 13=1H, 17=2H, 21=main-hand, …)
|
|
/// is2HLoose: true for polearms/staves (use ATTACK_2H_LOOSE instead of ATTACK_2H)
|
|
void setEquippedWeaponType(uint32_t inventoryType, bool is2HLoose = false,
|
|
bool isFist = false, bool isDagger = false,
|
|
bool hasOffHand = false, bool hasShield = false);
|
|
/// Play a special attack animation for a melee ability (spellId → SPECIAL_1H/2H/SHIELD_BASH/WHIRLWIND)
|
|
void triggerSpecialAttack(uint32_t spellId);
|
|
|
|
// ── Sprint aura animation ────────────────────────────────────────────
|
|
void setSprintAuraActive(bool active);
|
|
|
|
// ── Ranged combat ──────────────────────────────────────────────────────
|
|
void setEquippedRangedType(RangedWeaponType type);
|
|
/// Trigger a ranged shot animation (Auto Shot, Shoot, Throw)
|
|
void triggerRangedShot();
|
|
RangedWeaponType getEquippedRangedType() const { return weaponLoadout_.rangedType; }
|
|
void setCharging(bool charging);
|
|
bool isCharging() const { return charging_; }
|
|
|
|
// ── Spell casting ──────────────────────────────────────────────────────
|
|
/// Enter spell cast animation sequence:
|
|
/// precastAnimId (one-shot wind-up) → castAnimId (looping hold) → finalizeAnimId (one-shot release)
|
|
/// Any phase can be 0 to skip it.
|
|
void startSpellCast(uint32_t precastAnimId, uint32_t castAnimId, bool castLoop,
|
|
uint32_t finalizeAnimId = 0);
|
|
/// Leave spell cast animation state → plays finalization anim then idle.
|
|
void stopSpellCast();
|
|
|
|
// ── Loot animation ─────────────────────────────────────────────────────
|
|
void startLooting();
|
|
void stopLooting();
|
|
|
|
// ── Hit reactions ──────────────────────────────────────────────────────
|
|
/// Play a one-shot hit reaction animation (wound, dodge, block, etc.)
|
|
/// on the player character. The state machine returns to the previous
|
|
/// state once the reaction animation finishes.
|
|
void triggerHitReaction(uint32_t animId);
|
|
|
|
// ── Crowd control ──────────────────────────────────────────────────────
|
|
/// Enter/exit stunned state (loops STUN animation until cleared).
|
|
void setStunned(bool stunned);
|
|
bool isStunned() const { return stunned_; }
|
|
|
|
// ── Health-based idle ──────────────────────────────────────────────────
|
|
/// When true, idle/combat-idle will prefer STAND_WOUND if the model has it.
|
|
void setLowHealth(bool low);
|
|
|
|
// ── Stand state (sit/sleep/kneel transitions) ──────────────────────────
|
|
// 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;
|
|
static constexpr uint8_t STAND_STATE_SUBMERGED = 9;
|
|
void setStandState(uint8_t state);
|
|
|
|
// ── Stealth ────────────────────────────────────────────────────────────
|
|
/// When true, idle/walk/run use stealth animation variants.
|
|
void setStealthed(bool stealth);
|
|
|
|
// ── Effect triggers ────────────────────────────────────────────────────
|
|
void triggerLevelUpEffect(const glm::vec3& position);
|
|
void startChargeEffect(const glm::vec3& position, const glm::vec3& direction);
|
|
void emitChargeEffect(const glm::vec3& position, const glm::vec3& direction);
|
|
void stopChargeEffect();
|
|
|
|
// ── Mount ──────────────────────────────────────────────────────────────
|
|
void setMounted(uint32_t mountInstId, uint32_t mountDisplayId,
|
|
float heightOffset, const std::string& modelPath = "");
|
|
void setTaxiFlight(bool onTaxi) { taxiFlight_ = onTaxi; }
|
|
void setMountPitchRoll(float pitch, float roll) { mountPitch_ = pitch; mountRoll_ = roll; }
|
|
void clearMount();
|
|
bool isMounted() const { return mountInstanceId_ != 0; }
|
|
uint32_t getMountInstanceId() const { return mountInstanceId_; }
|
|
|
|
// ── Query helpers (used by Renderer) ───────────────────────────────────
|
|
bool isFootstepAnimationState() const;
|
|
float getMeleeSwingTimer() const { return meleeSwingTimer_; }
|
|
float getMountHeightOffset() const { return mountHeightOffset_; }
|
|
bool isTaxiFlight() const { return taxiFlight_; }
|
|
|
|
private:
|
|
Renderer* renderer_ = nullptr;
|
|
|
|
// ── CharacterAnimator: owns the complete character animation FSM ─────
|
|
CharacterAnimator characterAnimator_;
|
|
bool capabilitiesProbed_ = false;
|
|
|
|
// ── Animation change detection ───────────────────────────────────────
|
|
uint32_t lastPlayerAnimRequest_ = UINT32_MAX;
|
|
bool lastPlayerAnimLoopRequest_ = true;
|
|
float lastDeltaTime_ = 0.0f;
|
|
|
|
// ── Externally-queried state (mirrored to CharacterAnimator) ────────────
|
|
const glm::vec3* targetPosition_ = nullptr;
|
|
bool inCombat_ = false;
|
|
bool stunned_ = false;
|
|
bool charging_ = false;
|
|
|
|
// ── Footstep event tracking (delegated to FootstepDriver) ────────────
|
|
FootstepDriver footstepDriver_;
|
|
|
|
// ── SFX transition state (delegated to SfxStateDriver) ───────────────
|
|
SfxStateDriver sfxStateDriver_;
|
|
|
|
// ── Melee combat (needs renderer for sequence queries) ───────────────
|
|
float meleeSwingTimer_ = 0.0f;
|
|
float meleeSwingCooldown_ = 0.0f;
|
|
float meleeAnimDurationMs_ = 0.0f;
|
|
uint32_t meleeAnimId_ = 0;
|
|
uint32_t specialAttackAnimId_ = 0;
|
|
WeaponLoadout weaponLoadout_;
|
|
bool meleeOffHandTurn_ = false;
|
|
|
|
// ── Ranged weapon state ──────────────────────────────────────────────
|
|
float rangedShootTimer_ = 0.0f;
|
|
uint32_t rangedAnimId_ = 0;
|
|
|
|
// ── Mount state (discovery + positioning need renderer) ──────────────
|
|
uint32_t mountInstanceId_ = 0;
|
|
float mountHeightOffset_ = 0.0f;
|
|
float mountPitch_ = 0.0f;
|
|
float mountRoll_ = 0.0f; // External roll for taxi flights (lean roll computed by MountFSM)
|
|
int mountSeatAttachmentId_ = -1;
|
|
glm::vec3 smoothedMountSeatPos_ = glm::vec3(0.0f);
|
|
bool mountSeatSmoothingInit_ = false;
|
|
bool taxiFlight_ = false;
|
|
bool sprintAuraActive_ = false;
|
|
|
|
// ── Private helpers ──────────────────────────────────────────────────
|
|
uint32_t resolveMeleeAnimId();
|
|
void updateMountedAnimation(float deltaTime);
|
|
void applyMountPositioning(float mountBob, float mountRoll, float characterYaw);
|
|
};
|
|
|
|
} // namespace rendering
|
|
} // namespace wowee
|