mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-14 08:23:52 +00:00
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:
parent
e58f9b4b40
commit
b4989dc11f
53 changed files with 5110 additions and 2099 deletions
|
|
@ -940,7 +940,9 @@ public:
|
|||
|
||||
// Spell cast animation callbacks — true=start cast/channel, false=finish/cancel
|
||||
// guid: caster (may be player or another unit), isChannel: channel vs regular cast
|
||||
using SpellCastAnimCallback = std::function<void(uint64_t guid, bool start, bool isChannel)>;
|
||||
// castType: DIRECTED (unit target), OMNI (self/no target), AREA (ground AoE)
|
||||
using SpellCastAnimCallback = std::function<void(uint64_t guid, bool start, bool isChannel,
|
||||
SpellCastType castType)>;
|
||||
void setSpellCastAnimCallback(SpellCastAnimCallback cb) { spellCastAnimCallback_ = std::move(cb); }
|
||||
|
||||
// Fired when the player's own spell cast fails (spellId of the failed spell).
|
||||
|
|
|
|||
|
|
@ -40,13 +40,25 @@ struct TalentTabEntry {
|
|||
|
||||
// ---- Spell / cast state ----
|
||||
|
||||
// Spell targeting classification for animation selection.
|
||||
// Derived from the spell packet's targetGuid field — NOT the player's UI target.
|
||||
// DIRECTED — spell targets a specific unit (Frostbolt, Heal, Shadow Bolt)
|
||||
// OMNI — self-cast / no explicit target (Arcane Explosion, buffs)
|
||||
// AREA — ground-targeted AoE (Blizzard, Rain of Fire, Flamestrike)
|
||||
enum class SpellCastType : uint8_t {
|
||||
DIRECTED = 0, // Has a specific unit target
|
||||
OMNI = 1, // Self / no target
|
||||
AREA = 2, // Ground-targeted AoE
|
||||
};
|
||||
|
||||
struct UnitCastState {
|
||||
bool casting = false;
|
||||
bool isChannel = false;
|
||||
uint32_t spellId = 0;
|
||||
float timeRemaining = 0.0f;
|
||||
float timeTotal = 0.0f;
|
||||
bool interruptible = true;
|
||||
bool casting = false;
|
||||
bool isChannel = false;
|
||||
uint32_t spellId = 0;
|
||||
float timeRemaining = 0.0f;
|
||||
float timeTotal = 0.0f;
|
||||
bool interruptible = true;
|
||||
SpellCastType castType = SpellCastType::OMNI;
|
||||
};
|
||||
|
||||
// ---- Equipment sets (WotLK) ----
|
||||
|
|
|
|||
110
include/rendering/animation/activity_fsm.hpp
Normal file
110
include/rendering/animation/activity_fsm.hpp
Normal 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
|
||||
38
include/rendering/animation/anim_capability_probe.hpp
Normal file
38
include/rendering/animation/anim_capability_probe.hpp
Normal 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
|
||||
147
include/rendering/animation/anim_capability_set.hpp
Normal file
147
include/rendering/animation/anim_capability_set.hpp
Normal 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
|
||||
31
include/rendering/animation/anim_event.hpp
Normal file
31
include/rendering/animation/anim_event.hpp
Normal 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
|
||||
56
include/rendering/animation/animation_manager.hpp
Normal file
56
include/rendering/animation/animation_manager.hpp
Normal 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
|
||||
145
include/rendering/animation/character_animator.hpp
Normal file
145
include/rendering/animation/character_animator.hpp
Normal 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
|
||||
120
include/rendering/animation/combat_fsm.hpp
Normal file
120
include/rendering/animation/combat_fsm.hpp
Normal 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
|
||||
77
include/rendering/animation/emote_registry.hpp
Normal file
77
include/rendering/animation/emote_registry.hpp
Normal 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
|
||||
54
include/rendering/animation/footstep_driver.hpp
Normal file
54
include/rendering/animation/footstep_driver.hpp
Normal 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
|
||||
31
include/rendering/animation/i_anim_renderer.hpp
Normal file
31
include/rendering/animation/i_anim_renderer.hpp
Normal 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
|
||||
21
include/rendering/animation/i_animator.hpp
Normal file
21
include/rendering/animation/i_animator.hpp
Normal 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
|
||||
42
include/rendering/animation/i_character_animator.hpp
Normal file
42
include/rendering/animation/i_character_animator.hpp
Normal 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
|
||||
79
include/rendering/animation/locomotion_fsm.hpp
Normal file
79
include/rendering/animation/locomotion_fsm.hpp
Normal 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
|
||||
142
include/rendering/animation/mount_fsm.hpp
Normal file
142
include/rendering/animation/mount_fsm.hpp
Normal 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
|
||||
35
include/rendering/animation/sfx_state_driver.hpp
Normal file
35
include/rendering/animation/sfx_state_driver.hpp
Normal 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
|
||||
28
include/rendering/animation/weapon_type.hpp
Normal file
28
include/rendering/animation/weapon_type.hpp
Normal 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
|
||||
|
|
@ -4,22 +4,31 @@
|
|||
#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 audio { enum class FootstepSurface : uint8_t; }
|
||||
namespace rendering {
|
||||
|
||||
class Renderer;
|
||||
|
||||
/// Ranged weapon type for animation selection (bow/gun/crossbow/thrown)
|
||||
enum class RangedWeaponType : uint8_t { NONE = 0, BOW, GUN, CROSSBOW, THROWN };
|
||||
|
||||
// ============================================================================
|
||||
// AnimationController — extracted from Renderer (§4.2)
|
||||
// AnimationController — thin adapter wrapping CharacterAnimator
|
||||
//
|
||||
// Owns the character locomotion state machine, mount animation state,
|
||||
// emote system, footstep triggering, surface detection, melee combat
|
||||
// animation, and activity SFX transition tracking.
|
||||
// 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:
|
||||
|
|
@ -28,6 +37,11 @@ public:
|
|||
|
||||
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();
|
||||
|
|
@ -47,7 +61,7 @@ public:
|
|||
// ── Emote support ──────────────────────────────────────────────────────
|
||||
void playEmote(const std::string& emoteName);
|
||||
void cancelEmote();
|
||||
bool isEmoteActive() const { return emoteActive_; }
|
||||
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);
|
||||
|
|
@ -58,7 +72,7 @@ public:
|
|||
|
||||
// ── Targeting / combat ─────────────────────────────────────────────────
|
||||
void setTargetPosition(const glm::vec3* pos);
|
||||
void setInCombat(bool combat) { inCombat_ = combat; }
|
||||
void setInCombat(bool combat);
|
||||
bool isInCombat() const { return inCombat_; }
|
||||
const glm::vec3* getTargetPosition() const { return targetPosition_; }
|
||||
void resetCombatVisualState();
|
||||
|
|
@ -70,30 +84,19 @@ public:
|
|||
/// 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) {
|
||||
equippedWeaponInvType_ = inventoryType;
|
||||
equippedIs2HLoose_ = is2HLoose;
|
||||
equippedIsFist_ = isFist;
|
||||
equippedIsDagger_ = isDagger;
|
||||
equippedHasOffHand_ = hasOffHand;
|
||||
equippedHasShield_ = hasShield;
|
||||
meleeAnimId_ = 0; // Force re-resolve on next swing
|
||||
}
|
||||
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) { sprintAuraActive_ = active; }
|
||||
void setSprintAuraActive(bool active);
|
||||
|
||||
// ── Ranged combat ──────────────────────────────────────────────────────
|
||||
void setEquippedRangedType(RangedWeaponType type) {
|
||||
equippedRangedType_ = type;
|
||||
rangedAnimId_ = 0; // Force re-resolve
|
||||
}
|
||||
void setEquippedRangedType(RangedWeaponType type);
|
||||
/// Trigger a ranged shot animation (Auto Shot, Shoot, Throw)
|
||||
void triggerRangedShot();
|
||||
RangedWeaponType getEquippedRangedType() const { return equippedRangedType_; }
|
||||
void setCharging(bool charging) { charging_ = charging; }
|
||||
RangedWeaponType getEquippedRangedType() const { return weaponLoadout_.rangedType; }
|
||||
void setCharging(bool charging);
|
||||
bool isCharging() const { return charging_; }
|
||||
|
||||
// ── Spell casting ──────────────────────────────────────────────────────
|
||||
|
|
@ -122,7 +125,7 @@ public:
|
|||
|
||||
// ── Health-based idle ──────────────────────────────────────────────────
|
||||
/// When true, idle/combat-idle will prefer STAND_WOUND if the model has it.
|
||||
void setLowHealth(bool low) { lowHealth_ = low; }
|
||||
void setLowHealth(bool low);
|
||||
|
||||
// ── Stand state (sit/sleep/kneel transitions) ──────────────────────────
|
||||
// WoW UnitStandStateType constants
|
||||
|
|
@ -166,139 +169,55 @@ public:
|
|||
private:
|
||||
Renderer* renderer_ = nullptr;
|
||||
|
||||
// Character animation state machine
|
||||
enum class CharAnimState {
|
||||
IDLE, WALK, RUN, JUMP_START, JUMP_MID, JUMP_END, SIT_DOWN, SITTING,
|
||||
SIT_UP, EMOTE, SWIM_IDLE, SWIM, MELEE_SWING, MOUNT, CHARGE, COMBAT_IDLE,
|
||||
SPELL_PRECAST, SPELL_CASTING, SPELL_FINALIZE, HIT_REACTION, STUNNED, LOOTING,
|
||||
UNSHEATHE, SHEATHE, // Weapon draw/put-away one-shot transitions
|
||||
RANGED_SHOOT, RANGED_LOAD // Ranged attack sequence: shoot → reload
|
||||
};
|
||||
CharAnimState charAnimState_ = CharAnimState::IDLE;
|
||||
float locomotionStopGraceTimer_ = 0.0f;
|
||||
bool locomotionWasSprinting_ = false;
|
||||
// ── 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;
|
||||
|
||||
// Emote state
|
||||
bool emoteActive_ = false;
|
||||
uint32_t emoteAnimId_ = 0;
|
||||
bool emoteLoop_ = false;
|
||||
|
||||
// Spell cast sequence state (PRECAST → CASTING → FINALIZE)
|
||||
uint32_t spellPrecastAnimId_ = 0; // One-shot wind-up (phase 1)
|
||||
uint32_t spellCastAnimId_ = 0; // Looping cast hold (phase 2)
|
||||
uint32_t spellFinalizeAnimId_ = 0; // One-shot release (phase 3)
|
||||
bool spellCastLoop_ = false;
|
||||
|
||||
// Hit reaction state
|
||||
uint32_t hitReactionAnimId_ = 0;
|
||||
|
||||
// Crowd control
|
||||
bool stunned_ = false;
|
||||
|
||||
// Health-based idle
|
||||
bool lowHealth_ = false;
|
||||
|
||||
// Stand state (sit/sleep/kneel)
|
||||
uint8_t standState_ = 0;
|
||||
uint32_t sitDownAnim_ = 0; // Transition-in animation (one-shot)
|
||||
uint32_t sitLoopAnim_ = 0; // Looping pose animation
|
||||
uint32_t sitUpAnim_ = 0; // Transition-out animation (one-shot)
|
||||
|
||||
// Stealth
|
||||
bool stealthed_ = false;
|
||||
|
||||
// Target facing
|
||||
// ── Externally-queried state (mirrored to CharacterAnimator) ────────────
|
||||
const glm::vec3* targetPosition_ = nullptr;
|
||||
bool inCombat_ = false;
|
||||
|
||||
// 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;
|
||||
|
||||
// SFX transition state
|
||||
bool sfxStateInitialized_ = false;
|
||||
bool sfxPrevGrounded_ = true;
|
||||
bool sfxPrevJumping_ = false;
|
||||
bool sfxPrevFalling_ = false;
|
||||
bool sfxPrevSwimming_ = false;
|
||||
|
||||
// Melee combat
|
||||
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; // Non-zero during special attack (overrides resolveMeleeAnimId)
|
||||
uint32_t equippedWeaponInvType_ = 0;
|
||||
bool equippedIs2HLoose_ = false; // Polearm or staff
|
||||
bool equippedIsFist_ = false; // Fist weapon
|
||||
bool equippedIsDagger_ = false; // Dagger (uses pierce variants)
|
||||
bool equippedHasOffHand_ = false; // Has off-hand weapon (dual wield)
|
||||
bool equippedHasShield_ = false; // Has shield equipped (for SHIELD_BASH)
|
||||
bool meleeOffHandTurn_ = false; // Alternates main/off-hand swings
|
||||
uint32_t specialAttackAnimId_ = 0;
|
||||
WeaponLoadout weaponLoadout_;
|
||||
bool meleeOffHandTurn_ = false;
|
||||
|
||||
// Ranged weapon state
|
||||
RangedWeaponType equippedRangedType_ = RangedWeaponType::NONE;
|
||||
float rangedShootTimer_ = 0.0f; // Countdown for ranged attack animation
|
||||
uint32_t rangedAnimId_ = 0; // Cached ranged attack animation
|
||||
|
||||
// Mount animation capabilities (discovered at mount time, varies per model)
|
||||
struct MountAnimSet {
|
||||
uint32_t jumpStart = 0; // Jump start animation
|
||||
uint32_t jumpLoop = 0; // Jump airborne loop
|
||||
uint32_t jumpEnd = 0; // Jump landing
|
||||
uint32_t rearUp = 0; // Rear-up / special flourish
|
||||
uint32_t run = 0; // Run animation (discovered, don't assume)
|
||||
uint32_t stand = 0; // Stand animation (discovered)
|
||||
// Flight animations (discovered from mount model)
|
||||
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; // Idle fidget animations (head turn, tail swish, etc.)
|
||||
};
|
||||
|
||||
enum class MountAction { None, Jump, RearUp };
|
||||
// ── 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; // Up/down tilt (radians)
|
||||
float mountRoll_ = 0.0f; // Left/right banking (radians)
|
||||
int mountSeatAttachmentId_ = -1; // -1 unknown, -2 unavailable
|
||||
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;
|
||||
float prevMountYaw_ = 0.0f; // Previous yaw for turn rate calculation (procedural lean)
|
||||
float lastDeltaTime_ = 0.0f; // Cached for use in updateCharacterAnimation()
|
||||
MountAction mountAction_ = MountAction::None; // Current mount action (jump/rear-up)
|
||||
uint32_t mountActionPhase_ = 0; // 0=start, 1=loop, 2=end (for jump chaining)
|
||||
MountAnimSet mountAnims_; // Cached animation IDs for current mount
|
||||
float mountIdleFidgetTimer_ = 0.0f; // Timer for random idle fidgets
|
||||
float mountIdleSoundTimer_ = 0.0f; // Timer for ambient idle sounds
|
||||
uint32_t mountActiveFidget_ = 0; // Currently playing fidget animation ID (0 = none)
|
||||
bool taxiFlight_ = false;
|
||||
bool taxiAnimsLogged_ = false;
|
||||
bool sprintAuraActive_ = false; // Sprint/Dash aura active → use SPRINT anim
|
||||
bool sprintAuraActive_ = false;
|
||||
|
||||
// Private animation helpers
|
||||
bool shouldTriggerFootstepEvent(uint32_t animationId, float animationTimeMs, float animationDurationMs);
|
||||
audio::FootstepSurface resolveFootstepSurface() const;
|
||||
// ── Private helpers ──────────────────────────────────────────────────
|
||||
uint32_t resolveMeleeAnimId();
|
||||
void updateMountedAnimation(float deltaTime);
|
||||
void applyMountPositioning(float mountBob, float mountRoll, float characterYaw);
|
||||
};
|
||||
|
||||
} // namespace rendering
|
||||
|
|
|
|||
|
|
@ -98,6 +98,10 @@ public:
|
|||
using StandUpCallback = std::function<void()>;
|
||||
void setStandUpCallback(StandUpCallback cb) { standUpCallback_ = std::move(cb); }
|
||||
|
||||
// Callback invoked when the player sits down via local input (X key).
|
||||
using SitDownCallback = std::function<void()>;
|
||||
void setSitDownCallback(SitDownCallback cb) { sitDownCallback_ = std::move(cb); }
|
||||
|
||||
// Callback invoked when auto-follow is cancelled by user movement input.
|
||||
using AutoFollowCancelCallback = std::function<void()>;
|
||||
void setAutoFollowCancelCallback(AutoFollowCancelCallback cb) { autoFollowCancelCallback_ = std::move(cb); }
|
||||
|
|
@ -310,6 +314,7 @@ private:
|
|||
// Movement callback
|
||||
MovementCallback movementCallback;
|
||||
StandUpCallback standUpCallback_;
|
||||
SitDownCallback sitDownCallback_;
|
||||
AutoFollowCancelCallback autoFollowCancelCallback_;
|
||||
|
||||
// Movement speeds
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue