chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
# include "rendering/animation_controller.hpp"
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
# include "rendering/animation_ids.hpp"
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
# include "rendering/renderer.hpp"
# include "rendering/camera.hpp"
# include "rendering/camera_controller.hpp"
# include "rendering/character_renderer.hpp"
# include "rendering/terrain_manager.hpp"
# include "rendering/wmo_renderer.hpp"
# include "rendering/water_renderer.hpp"
# include "rendering/m2_renderer.hpp"
# include "rendering/levelup_effect.hpp"
# include "rendering/charge_effect.hpp"
# include "rendering/spell_visual_system.hpp"
# include "pipeline/m2_loader.hpp"
# include "pipeline/asset_manager.hpp"
# include "pipeline/dbc_loader.hpp"
# include "pipeline/dbc_layout.hpp"
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
# include "game/inventory.hpp"
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
# include "core/application.hpp"
# include "core/logger.hpp"
# include "audio/audio_coordinator.hpp"
# include "audio/audio_engine.hpp"
# include "audio/footstep_manager.hpp"
# include "audio/activity_sound_manager.hpp"
# include "audio/mount_sound_manager.hpp"
# include "audio/music_manager.hpp"
# include "audio/movement_sound_manager.hpp"
# include "rendering/swim_effects.hpp"
# include <algorithm>
# include <cstdlib>
# include <unordered_map>
# include <unordered_set>
# include <set>
# include <random>
# include <cctype>
# include <cmath>
# include <glm/gtc/matrix_transform.hpp>
namespace wowee {
namespace rendering {
// ── Static emote data (shared across all AnimationController instances) ──────
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 ;
} ;
static std : : unordered_map < std : : string , EmoteInfo > EMOTE_TABLE ;
static std : : unordered_map < uint32_t , const EmoteInfo * > EMOTE_BY_DBCID ;
static bool emoteTableLoaded = false ;
static std : : vector < std : : string > parseEmoteCommands ( const std : : string & raw ) {
std : : vector < std : : string > out ;
std : : string cur ;
for ( char c : raw ) {
if ( std : : isalnum ( static_cast < unsigned char > ( c ) ) | | c = = ' _ ' ) {
cur . push_back ( static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ) ;
} else if ( ! cur . empty ( ) ) {
out . push_back ( cur ) ;
cur . clear ( ) ;
}
}
if ( ! cur . empty ( ) ) out . push_back ( cur ) ;
return out ;
}
static bool isLoopingEmote ( const std : : string & command ) {
static const std : : unordered_set < std : : string > kLooping = {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
" dance " , " train " , " dead " , " eat " , " work " ,
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
} ;
return kLooping . find ( command ) ! = kLooping . end ( ) ;
}
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
// Map one-shot emote animation IDs to their persistent EMOTE_STATE_* looping variants.
// When a looping emote is played, we prefer the STATE variant if the model has it.
static uint32_t getEmoteStateVariant ( uint32_t oneShotAnimId ) {
static const std : : unordered_map < uint32_t , uint32_t > kStateMap = {
{ anim : : EMOTE_DANCE , anim : : EMOTE_STATE_DANCE } ,
{ anim : : EMOTE_LAUGH , anim : : EMOTE_STATE_LAUGH } ,
{ anim : : EMOTE_POINT , anim : : EMOTE_STATE_POINT } ,
{ anim : : EMOTE_EAT , anim : : EMOTE_STATE_EAT } ,
{ anim : : EMOTE_ROAR , anim : : EMOTE_STATE_ROAR } ,
{ anim : : EMOTE_APPLAUD , anim : : EMOTE_STATE_APPLAUD } ,
{ anim : : EMOTE_WORK , anim : : EMOTE_STATE_WORK } ,
{ anim : : EMOTE_USE_STANDING , anim : : EMOTE_STATE_USE_STANDING } ,
{ anim : : EATING_LOOP , anim : : EMOTE_STATE_EAT } ,
} ;
auto it = kStateMap . find ( oneShotAnimId ) ;
return it ! = kStateMap . end ( ) ? it - > second : 0 ;
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
static void loadFallbackEmotes ( ) {
if ( ! EMOTE_TABLE . empty ( ) ) return ;
EMOTE_TABLE = {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
{ " wave " , { anim : : EMOTE_WAVE , 0 , false , " You wave. " , " You wave at %s. " , " %s waves. " , " %s waves at %s. " , " wave " } } ,
{ " bow " , { anim : : EMOTE_BOW , 0 , false , " You bow down graciously. " , " You bow down before %s. " , " %s bows down graciously. " , " %s bows down before %s. " , " bow " } } ,
{ " laugh " , { anim : : EMOTE_LAUGH , 0 , false , " You laugh. " , " You laugh at %s. " , " %s laughs. " , " %s laughs at %s. " , " laugh " } } ,
{ " point " , { anim : : EMOTE_POINT , 0 , false , " You point over yonder. " , " You point at %s. " , " %s points over yonder. " , " %s points at %s. " , " point " } } ,
{ " cheer " , { anim : : EMOTE_CHEER , 0 , false , " You cheer! " , " You cheer at %s. " , " %s cheers! " , " %s cheers at %s. " , " cheer " } } ,
{ " dance " , { anim : : EMOTE_DANCE , 0 , true , " You burst into dance. " , " You dance with %s. " , " %s bursts into dance. " , " %s dances with %s. " , " dance " } } ,
{ " kneel " , { anim : : EMOTE_KNEEL , 0 , false , " You kneel down. " , " You kneel before %s. " , " %s kneels down. " , " %s kneels before %s. " , " kneel " } } ,
{ " applaud " , { anim : : EMOTE_APPLAUD , 0 , false , " You applaud. Bravo! " , " You applaud at %s. Bravo! " , " %s applauds. Bravo! " , " %s applauds at %s. Bravo! " , " applaud " } } ,
{ " shout " , { anim : : EMOTE_SHOUT , 0 , false , " You shout. " , " You shout at %s. " , " %s shouts. " , " %s shouts at %s. " , " shout " } } ,
{ " chicken " , { anim : : EMOTE_CHICKEN , 0 , false , " With arms flapping, you strut around. Cluck, Cluck, Chicken! " ,
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
" With arms flapping, you strut around %s. Cluck, Cluck, Chicken! " ,
" %s struts around. Cluck, Cluck, Chicken! " , " %s struts around %s. Cluck, Cluck, Chicken! " , " chicken " } } ,
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
{ " cry " , { anim : : EMOTE_CRY , 0 , false , " You cry. " , " You cry on %s's shoulder. " , " %s cries. " , " %s cries on %s's shoulder. " , " cry " } } ,
{ " kiss " , { anim : : EMOTE_KISS , 0 , false , " You blow a kiss into the wind. " , " You blow a kiss to %s. " , " %s blows a kiss into the wind. " , " %s blows a kiss to %s. " , " kiss " } } ,
{ " roar " , { anim : : EMOTE_ROAR , 0 , false , " You roar with bestial vigor. So fierce! " , " You roar with bestial vigor at %s. So fierce! " , " %s roars with bestial vigor. So fierce! " , " %s roars with bestial vigor at %s. So fierce! " , " roar " } } ,
{ " salute " , { anim : : EMOTE_SALUTE , 0 , false , " You salute. " , " You salute %s with respect. " , " %s salutes. " , " %s salutes %s with respect. " , " salute " } } ,
{ " rude " , { anim : : EMOTE_RUDE , 0 , false , " You make a rude gesture. " , " You make a rude gesture at %s. " , " %s makes a rude gesture. " , " %s makes a rude gesture at %s. " , " rude " } } ,
{ " flex " , { anim : : EMOTE_FLEX , 0 , false , " You flex your muscles. Oooooh so strong! " , " You flex at %s. Oooooh so strong! " , " %s flexes. Oooooh so strong! " , " %s flexes at %s. Oooooh so strong! " , " flex " } } ,
{ " shy " , { anim : : EMOTE_SHY , 0 , false , " You smile shyly. " , " You smile shyly at %s. " , " %s smiles shyly. " , " %s smiles shyly at %s. " , " shy " } } ,
{ " beg " , { anim : : EMOTE_BEG , 0 , false , " You beg everyone around you. How pathetic. " , " You beg %s. How pathetic. " , " %s begs everyone around. How pathetic. " , " %s begs %s. How pathetic. " , " beg " } } ,
{ " eat " , { anim : : EMOTE_EAT , 0 , true , " You begin to eat. " , " You begin to eat in front of %s. " , " %s begins to eat. " , " %s begins to eat in front of %s. " , " eat " } } ,
{ " talk " , { anim : : EMOTE_TALK , 0 , false , " You talk. " , " You talk to %s. " , " %s talks. " , " %s talks to %s. " , " talk " } } ,
{ " work " , { anim : : EMOTE_WORK , 0 , true , " You begin to work. " , " You begin to work near %s. " , " %s begins to work. " , " %s begins to work near %s. " , " work " } } ,
{ " train " , { anim : : EMOTE_TRAIN , 0 , true , " You let off a train whistle. Choo Choo! " , " You let off a train whistle at %s. Choo Choo! " , " %s lets off a train whistle. Choo Choo! " , " %s lets off a train whistle at %s. Choo Choo! " , " train " } } ,
{ " dead " , { anim : : EMOTE_DEAD , 0 , true , " You play dead. " , " You play dead in front of %s. " , " %s plays dead. " , " %s plays dead in front of %s. " , " dead " } } ,
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
} ;
}
static std : : string replacePlaceholders ( const std : : string & text , const std : : string * targetName ) {
if ( text . empty ( ) ) return text ;
std : : string out ;
out . reserve ( text . size ( ) + 16 ) ;
for ( size_t i = 0 ; i < text . size ( ) ; + + i ) {
if ( text [ i ] = = ' % ' & & i + 1 < text . size ( ) & & text [ i + 1 ] = = ' s ' ) {
if ( targetName & & ! targetName - > empty ( ) ) out + = * targetName ;
i + + ;
} else {
out . push_back ( text [ i ] ) ;
}
}
return out ;
}
static void loadEmotesFromDbc ( ) {
if ( emoteTableLoaded ) return ;
emoteTableLoaded = true ;
auto * assetManager = core : : Application : : getInstance ( ) . getAssetManager ( ) ;
if ( ! assetManager ) {
LOG_WARNING ( " Emotes: no AssetManager " ) ;
loadFallbackEmotes ( ) ;
return ;
}
auto emotesTextDbc = assetManager - > loadDBC ( " EmotesText.dbc " ) ;
auto emotesTextDataDbc = assetManager - > loadDBC ( " EmotesTextData.dbc " ) ;
if ( ! emotesTextDbc | | ! emotesTextDataDbc | | ! emotesTextDbc - > isLoaded ( ) | | ! emotesTextDataDbc - > isLoaded ( ) ) {
LOG_WARNING ( " Emotes: DBCs not available (EmotesText/EmotesTextData) " ) ;
loadFallbackEmotes ( ) ;
return ;
}
const auto * activeLayout = pipeline : : getActiveDBCLayout ( ) ;
const auto * etdL = activeLayout ? activeLayout - > getLayout ( " EmotesTextData " ) : nullptr ;
const auto * emL = activeLayout ? activeLayout - > getLayout ( " Emotes " ) : nullptr ;
const auto * etL = activeLayout ? activeLayout - > getLayout ( " EmotesText " ) : nullptr ;
std : : unordered_map < uint32_t , std : : string > textData ;
textData . reserve ( emotesTextDataDbc - > getRecordCount ( ) ) ;
for ( uint32_t r = 0 ; r < emotesTextDataDbc - > getRecordCount ( ) ; + + r ) {
uint32_t id = emotesTextDataDbc - > getUInt32 ( r , etdL ? ( * etdL ) [ " ID " ] : 0 ) ;
std : : string text = emotesTextDataDbc - > getString ( r , etdL ? ( * etdL ) [ " Text " ] : 1 ) ;
if ( ! text . empty ( ) ) textData . emplace ( id , std : : move ( text ) ) ;
}
std : : unordered_map < uint32_t , uint32_t > emoteIdToAnim ;
if ( auto emotesDbc = assetManager - > loadDBC ( " Emotes.dbc " ) ; emotesDbc & & emotesDbc - > isLoaded ( ) ) {
emoteIdToAnim . reserve ( emotesDbc - > getRecordCount ( ) ) ;
for ( uint32_t r = 0 ; r < emotesDbc - > getRecordCount ( ) ; + + r ) {
uint32_t emoteId = emotesDbc - > getUInt32 ( r , emL ? ( * emL ) [ " ID " ] : 0 ) ;
uint32_t animId = emotesDbc - > getUInt32 ( r , emL ? ( * emL ) [ " AnimID " ] : 2 ) ;
if ( animId ! = 0 ) emoteIdToAnim [ emoteId ] = animId ;
}
}
EMOTE_TABLE . clear ( ) ;
EMOTE_TABLE . reserve ( emotesTextDbc - > getRecordCount ( ) ) ;
for ( uint32_t r = 0 ; r < emotesTextDbc - > getRecordCount ( ) ; + + r ) {
uint32_t recordId = emotesTextDbc - > getUInt32 ( r , etL ? ( * etL ) [ " ID " ] : 0 ) ;
std : : string cmdRaw = emotesTextDbc - > getString ( r , etL ? ( * etL ) [ " Command " ] : 1 ) ;
if ( cmdRaw . empty ( ) ) continue ;
uint32_t emoteRef = emotesTextDbc - > getUInt32 ( r , etL ? ( * etL ) [ " EmoteRef " ] : 2 ) ;
uint32_t animId = 0 ;
auto animIt = emoteIdToAnim . find ( emoteRef ) ;
if ( animIt ! = emoteIdToAnim . end ( ) ) {
animId = animIt - > second ;
} else {
animId = emoteRef ;
}
uint32_t senderTargetTextId = emotesTextDbc - > getUInt32 ( r , etL ? ( * etL ) [ " SenderTargetTextID " ] : 5 ) ;
uint32_t senderNoTargetTextId = emotesTextDbc - > getUInt32 ( r , etL ? ( * etL ) [ " SenderNoTargetTextID " ] : 9 ) ;
uint32_t othersTargetTextId = emotesTextDbc - > getUInt32 ( r , etL ? ( * etL ) [ " OthersTargetTextID " ] : 3 ) ;
uint32_t othersNoTargetTextId = emotesTextDbc - > getUInt32 ( r , etL ? ( * etL ) [ " OthersNoTargetTextID " ] : 7 ) ;
std : : string textTarget , textNoTarget , oTarget , oNoTarget ;
if ( auto it = textData . find ( senderTargetTextId ) ; it ! = textData . end ( ) ) textTarget = it - > second ;
if ( auto it = textData . find ( senderNoTargetTextId ) ; it ! = textData . end ( ) ) textNoTarget = it - > second ;
if ( auto it = textData . find ( othersTargetTextId ) ; it ! = textData . end ( ) ) oTarget = it - > second ;
if ( auto it = textData . find ( othersNoTargetTextId ) ; it ! = textData . end ( ) ) oNoTarget = it - > second ;
for ( const std : : string & cmd : parseEmoteCommands ( cmdRaw ) ) {
if ( cmd . empty ( ) ) continue ;
EmoteInfo info ;
info . animId = animId ;
info . dbcId = recordId ;
info . loop = isLoopingEmote ( cmd ) ;
info . textNoTarget = textNoTarget ;
info . textTarget = textTarget ;
info . othersNoTarget = oNoTarget ;
info . othersTarget = oTarget ;
info . command = cmd ;
EMOTE_TABLE . emplace ( cmd , std : : move ( info ) ) ;
}
}
if ( EMOTE_TABLE . empty ( ) ) {
LOG_WARNING ( " Emotes: DBC loaded but no commands parsed, using fallback list " ) ;
loadFallbackEmotes ( ) ;
} else {
LOG_INFO ( " Emotes: loaded " , EMOTE_TABLE . size ( ) , " commands from DBC " ) ;
}
EMOTE_BY_DBCID . clear ( ) ;
for ( auto & [ cmd , info ] : EMOTE_TABLE ) {
if ( info . dbcId ! = 0 ) {
EMOTE_BY_DBCID . emplace ( info . dbcId , & info ) ;
}
}
}
// ── AnimationController implementation ───────────────────────────────────────
AnimationController : : AnimationController ( ) = default ;
AnimationController : : ~ AnimationController ( ) = default ;
void AnimationController : : initialize ( Renderer * renderer ) {
renderer_ = renderer ;
}
void AnimationController : : onCharacterFollow ( uint32_t /*instanceId*/ ) {
// Reset animation state when follow target changes
}
// ── Emote support ────────────────────────────────────────────────────────────
void AnimationController : : playEmote ( const std : : string & emoteName ) {
loadEmotesFromDbc ( ) ;
auto it = EMOTE_TABLE . find ( emoteName ) ;
if ( it = = EMOTE_TABLE . end ( ) ) return ;
const auto & info = it - > second ;
if ( info . animId = = 0 ) return ;
emoteActive_ = true ;
emoteAnimId_ = info . animId ;
emoteLoop_ = info . loop ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
// For looping emotes, prefer the EMOTE_STATE_* variant if the model has it
if ( emoteLoop_ ) {
uint32_t stateVariant = getEmoteStateVariant ( emoteAnimId_ ) ;
if ( stateVariant ! = 0 ) {
auto * characterRenderer = renderer_ - > getCharacterRenderer ( ) ;
uint32_t characterInstanceId = renderer_ - > getCharacterInstanceId ( ) ;
if ( characterRenderer & & characterInstanceId > 0 & &
characterRenderer - > hasAnimation ( characterInstanceId , stateVariant ) ) {
emoteAnimId_ = stateVariant ;
}
}
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
charAnimState_ = CharAnimState : : EMOTE ;
auto * characterRenderer = renderer_ - > getCharacterRenderer ( ) ;
uint32_t characterInstanceId = renderer_ - > getCharacterInstanceId ( ) ;
if ( characterRenderer & & characterInstanceId > 0 ) {
characterRenderer - > playAnimation ( characterInstanceId , emoteAnimId_ , emoteLoop_ ) ;
}
}
void AnimationController : : cancelEmote ( ) {
emoteActive_ = false ;
emoteAnimId_ = 0 ;
emoteLoop_ = false ;
}
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
void AnimationController : : startSpellCast ( uint32_t precastAnimId , uint32_t castAnimId , bool castLoop ,
uint32_t finalizeAnimId ) {
spellPrecastAnimId_ = precastAnimId ;
spellCastAnimId_ = castAnimId ;
spellCastLoop_ = castLoop ;
spellFinalizeAnimId_ = finalizeAnimId ;
// Start with precast phase if available, otherwise go straight to cast
if ( spellPrecastAnimId_ ! = 0 ) {
charAnimState_ = CharAnimState : : SPELL_PRECAST ;
} else {
charAnimState_ = CharAnimState : : SPELL_CASTING ;
}
// Force immediate animation update by invalidating the last request
lastPlayerAnimRequest_ = UINT32_MAX ;
}
void AnimationController : : stopSpellCast ( ) {
if ( charAnimState_ ! = CharAnimState : : SPELL_PRECAST & &
charAnimState_ ! = CharAnimState : : SPELL_CASTING ) return ;
if ( spellFinalizeAnimId_ ! = 0 ) {
// Transition to finalization phase — one-shot release animation
charAnimState_ = CharAnimState : : SPELL_FINALIZE ;
lastPlayerAnimRequest_ = UINT32_MAX ;
} else if ( spellCastLoop_ ) {
// No finalize anim — let current cast cycle finish as one-shot
spellCastLoop_ = false ;
charAnimState_ = CharAnimState : : SPELL_FINALIZE ;
lastPlayerAnimRequest_ = UINT32_MAX ;
} else {
// Instant cast (no finalize, no loop) — wait for completion in current state
charAnimState_ = CharAnimState : : SPELL_FINALIZE ;
lastPlayerAnimRequest_ = UINT32_MAX ;
}
}
void AnimationController : : startLooting ( ) {
// Don't override jump, swim, stun, or death states
if ( charAnimState_ = = CharAnimState : : JUMP_START | |
charAnimState_ = = CharAnimState : : JUMP_MID | |
charAnimState_ = = CharAnimState : : JUMP_END | |
charAnimState_ = = CharAnimState : : SWIM | |
charAnimState_ = = CharAnimState : : SWIM_IDLE | |
charAnimState_ = = CharAnimState : : STUNNED ) return ;
charAnimState_ = CharAnimState : : LOOTING ;
lastPlayerAnimRequest_ = UINT32_MAX ;
}
void AnimationController : : stopLooting ( ) {
if ( charAnimState_ ! = CharAnimState : : LOOTING ) return ;
charAnimState_ = CharAnimState : : IDLE ;
lastPlayerAnimRequest_ = UINT32_MAX ;
}
void AnimationController : : triggerHitReaction ( uint32_t animId ) {
// Hit reactions interrupt spell casting but not jumps/swimming/stun
if ( charAnimState_ = = CharAnimState : : JUMP_START | |
charAnimState_ = = CharAnimState : : JUMP_MID | |
charAnimState_ = = CharAnimState : : JUMP_END | |
charAnimState_ = = CharAnimState : : SWIM | |
charAnimState_ = = CharAnimState : : SWIM_IDLE | |
charAnimState_ = = CharAnimState : : STUNNED ) return ;
if ( charAnimState_ = = CharAnimState : : SPELL_CASTING | |
charAnimState_ = = CharAnimState : : SPELL_PRECAST | |
charAnimState_ = = CharAnimState : : SPELL_FINALIZE ) {
spellPrecastAnimId_ = 0 ;
spellCastAnimId_ = 0 ;
spellCastLoop_ = false ;
spellFinalizeAnimId_ = 0 ;
}
hitReactionAnimId_ = animId ;
charAnimState_ = CharAnimState : : HIT_REACTION ;
lastPlayerAnimRequest_ = UINT32_MAX ;
}
void AnimationController : : setStunned ( bool stunned ) {
stunned_ = stunned ;
if ( stunned ) {
// Stun overrides most states (not swimming/jumping — those are physics)
if ( charAnimState_ = = CharAnimState : : SWIM | |
charAnimState_ = = CharAnimState : : SWIM_IDLE ) return ;
// Interrupt spell casting
if ( charAnimState_ = = CharAnimState : : SPELL_CASTING | |
charAnimState_ = = CharAnimState : : SPELL_PRECAST | |
charAnimState_ = = CharAnimState : : SPELL_FINALIZE ) {
spellPrecastAnimId_ = 0 ;
spellCastAnimId_ = 0 ;
spellCastLoop_ = false ;
spellFinalizeAnimId_ = 0 ;
}
hitReactionAnimId_ = 0 ;
charAnimState_ = CharAnimState : : STUNNED ;
lastPlayerAnimRequest_ = UINT32_MAX ;
} else {
if ( charAnimState_ = = CharAnimState : : STUNNED ) {
charAnimState_ = inCombat_ ? CharAnimState : : COMBAT_IDLE : CharAnimState : : IDLE ;
lastPlayerAnimRequest_ = UINT32_MAX ;
}
}
}
void AnimationController : : setStandState ( uint8_t state ) {
if ( state = = standState_ ) return ;
standState_ = state ;
if ( state = = STAND_STATE_STAND ) {
// Standing up — exit animation handled by state machine (!sitting → SIT_UP)
// sitUpAnim_ is retained from the entry so the correct exit animation plays.
return ;
}
// Configure transition/loop/exit animations per stand-state type
if ( state = = STAND_STATE_SIT ) {
// Ground sit
sitDownAnim_ = anim : : SIT_GROUND_DOWN ;
sitLoopAnim_ = anim : : SITTING ;
sitUpAnim_ = anim : : SIT_GROUND_UP ;
charAnimState_ = CharAnimState : : SIT_DOWN ;
} else if ( state = = STAND_STATE_SLEEP ) {
// Sleep
sitDownAnim_ = anim : : SLEEP_DOWN ;
sitLoopAnim_ = anim : : SLEEP ;
sitUpAnim_ = anim : : SLEEP_UP ;
charAnimState_ = CharAnimState : : SIT_DOWN ;
} else if ( state = = STAND_STATE_KNEEL ) {
// Kneel
sitDownAnim_ = anim : : KNEEL_START ;
sitLoopAnim_ = anim : : KNEEL_LOOP ;
sitUpAnim_ = anim : : KNEEL_END ;
charAnimState_ = CharAnimState : : SIT_DOWN ;
} else if ( state > = STAND_STATE_SIT_CHAIR & & state < = STAND_STATE_SIT_HIGH ) {
// Chair variants — no transition animation, go directly to loop
sitDownAnim_ = 0 ;
sitUpAnim_ = 0 ;
if ( state = = STAND_STATE_SIT_LOW ) {
sitLoopAnim_ = anim : : SIT_CHAIR_LOW ;
} else if ( state = = STAND_STATE_SIT_HIGH ) {
sitLoopAnim_ = anim : : SIT_CHAIR_HIGH ;
} else {
sitLoopAnim_ = anim : : SIT_CHAIR_MED ;
}
charAnimState_ = CharAnimState : : SITTING ;
} else if ( state = = STAND_STATE_DEAD ) {
// Dead — leave to death handling elsewhere
sitDownAnim_ = 0 ;
sitLoopAnim_ = 0 ;
sitUpAnim_ = 0 ;
return ;
}
lastPlayerAnimRequest_ = UINT32_MAX ;
}
void AnimationController : : setStealthed ( bool stealth ) {
if ( stealthed_ = = stealth ) return ;
stealthed_ = stealth ;
lastPlayerAnimRequest_ = UINT32_MAX ;
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
std : : string AnimationController : : getEmoteText ( const std : : string & emoteName , const std : : string * targetName ) {
loadEmotesFromDbc ( ) ;
auto it = EMOTE_TABLE . find ( emoteName ) ;
if ( it ! = EMOTE_TABLE . end ( ) ) {
const auto & info = it - > second ;
const std : : string & base = ( targetName ? info . textTarget : info . textNoTarget ) ;
if ( ! base . empty ( ) ) {
return replacePlaceholders ( base , targetName ) ;
}
if ( targetName & & ! targetName - > empty ( ) ) {
return " You " + info . command + " at " + * targetName + " . " ;
}
return " You " + info . command + " . " ;
}
return " " ;
}
uint32_t AnimationController : : getEmoteDbcId ( const std : : string & emoteName ) {
loadEmotesFromDbc ( ) ;
auto it = EMOTE_TABLE . find ( emoteName ) ;
if ( it ! = EMOTE_TABLE . end ( ) ) {
return it - > second . dbcId ;
}
return 0 ;
}
std : : string AnimationController : : getEmoteTextByDbcId ( uint32_t dbcId , const std : : string & senderName ,
const std : : string * targetName ) {
loadEmotesFromDbc ( ) ;
auto it = EMOTE_BY_DBCID . find ( dbcId ) ;
if ( it = = EMOTE_BY_DBCID . end ( ) ) return " " ;
const EmoteInfo & info = * it - > second ;
if ( targetName & & ! targetName - > empty ( ) ) {
if ( ! info . othersTarget . empty ( ) ) {
std : : string out ;
out . reserve ( info . othersTarget . size ( ) + senderName . size ( ) + targetName - > size ( ) ) ;
bool firstReplaced = false ;
for ( size_t i = 0 ; i < info . othersTarget . size ( ) ; + + i ) {
if ( info . othersTarget [ i ] = = ' % ' & & i + 1 < info . othersTarget . size ( ) & & info . othersTarget [ i + 1 ] = = ' s ' ) {
out + = firstReplaced ? * targetName : senderName ;
firstReplaced = true ;
+ + i ;
} else {
out . push_back ( info . othersTarget [ i ] ) ;
}
}
return out ;
}
return senderName + " " + info . command + " s at " + * targetName + " . " ;
} else {
if ( ! info . othersNoTarget . empty ( ) ) {
return replacePlaceholders ( info . othersNoTarget , & senderName ) ;
}
return senderName + " " + info . command + " s. " ;
}
}
uint32_t AnimationController : : getEmoteAnimByDbcId ( uint32_t dbcId ) {
loadEmotesFromDbc ( ) ;
auto it = EMOTE_BY_DBCID . find ( dbcId ) ;
if ( it ! = EMOTE_BY_DBCID . end ( ) ) {
return it - > second - > animId ;
}
return 0 ;
}
// ── Targeting / combat ───────────────────────────────────────────────────────
void AnimationController : : setTargetPosition ( const glm : : vec3 * pos ) {
targetPosition_ = pos ;
}
void AnimationController : : resetCombatVisualState ( ) {
inCombat_ = false ;
targetPosition_ = nullptr ;
meleeSwingTimer_ = 0.0f ;
meleeSwingCooldown_ = 0.0f ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
specialAttackAnimId_ = 0 ;
rangedShootTimer_ = 0.0f ;
rangedAnimId_ = 0 ;
spellPrecastAnimId_ = 0 ;
spellCastAnimId_ = 0 ;
spellCastLoop_ = false ;
spellFinalizeAnimId_ = 0 ;
hitReactionAnimId_ = 0 ;
stunned_ = false ;
lowHealth_ = false ;
if ( charAnimState_ = = CharAnimState : : SPELL_CASTING | |
charAnimState_ = = CharAnimState : : SPELL_PRECAST | |
charAnimState_ = = CharAnimState : : SPELL_FINALIZE | |
charAnimState_ = = CharAnimState : : HIT_REACTION | |
charAnimState_ = = CharAnimState : : STUNNED | |
charAnimState_ = = CharAnimState : : RANGED_SHOOT )
charAnimState_ = CharAnimState : : IDLE ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
if ( auto * svs = renderer_ - > getSpellVisualSystem ( ) ) svs - > reset ( ) ;
}
bool AnimationController : : isMoving ( ) const {
auto * cameraController = renderer_ - > getCameraController ( ) ;
return cameraController & & cameraController - > isMoving ( ) ;
}
// ── Melee combat ─────────────────────────────────────────────────────────────
void AnimationController : : triggerMeleeSwing ( ) {
auto * characterRenderer = renderer_ - > getCharacterRenderer ( ) ;
uint32_t characterInstanceId = renderer_ - > getCharacterInstanceId ( ) ;
if ( ! characterRenderer | | characterInstanceId = = 0 ) return ;
if ( meleeSwingCooldown_ > 0.0f ) return ;
if ( emoteActive_ ) {
cancelEmote ( ) ;
}
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
specialAttackAnimId_ = 0 ; // Clear any special attack override
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
resolveMeleeAnimId ( ) ;
meleeSwingCooldown_ = 0.1f ;
float durationSec = meleeAnimDurationMs_ > 0.0f ? meleeAnimDurationMs_ / 1000.0f : 0.6f ;
if ( durationSec < 0.25f ) durationSec = 0.25f ;
if ( durationSec > 1.0f ) durationSec = 1.0f ;
meleeSwingTimer_ = durationSec ;
if ( renderer_ - > getAudioCoordinator ( ) - > getActivitySoundManager ( ) ) {
renderer_ - > getAudioCoordinator ( ) - > getActivitySoundManager ( ) - > playMeleeSwing ( ) ;
}
}
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
void AnimationController : : triggerSpecialAttack ( uint32_t /*spellId*/ ) {
auto * characterRenderer = renderer_ - > getCharacterRenderer ( ) ;
uint32_t characterInstanceId = renderer_ - > getCharacterInstanceId ( ) ;
if ( ! characterRenderer | | characterInstanceId = = 0 ) return ;
if ( meleeSwingCooldown_ > 0.0f ) return ;
if ( emoteActive_ ) {
cancelEmote ( ) ;
}
auto has = [ & ] ( uint32_t id ) { return characterRenderer - > hasAnimation ( characterInstanceId , id ) ; } ;
// Choose special attack animation based on equipped weapon type
uint32_t specAnim = 0 ;
if ( equippedHasShield_ & & has ( anim : : SHIELD_BASH ) ) {
specAnim = anim : : SHIELD_BASH ;
} else if ( ( equippedWeaponInvType_ = = game : : InvType : : TWO_HAND | | equippedIs2HLoose_ ) & & has ( anim : : SPECIAL_2H ) ) {
specAnim = anim : : SPECIAL_2H ;
} else if ( equippedWeaponInvType_ ! = game : : InvType : : NON_EQUIP & & has ( anim : : SPECIAL_1H ) ) {
specAnim = anim : : SPECIAL_1H ;
} else if ( has ( anim : : SPECIAL_UNARMED ) ) {
specAnim = anim : : SPECIAL_UNARMED ;
} else if ( has ( anim : : SPECIAL_1H ) ) {
specAnim = anim : : SPECIAL_1H ;
}
if ( specAnim = = 0 ) {
// No special animation available — fall back to regular melee swing
triggerMeleeSwing ( ) ;
return ;
}
specialAttackAnimId_ = specAnim ;
meleeSwingCooldown_ = 0.1f ;
// Query the special attack animation duration
std : : vector < pipeline : : M2Sequence > sequences ;
float dur = 0.6f ;
if ( characterRenderer - > getAnimationSequences ( characterInstanceId , sequences ) ) {
for ( const auto & seq : sequences ) {
if ( seq . id = = specAnim & & seq . duration > 0 ) {
dur = static_cast < float > ( seq . duration ) / 1000.0f ;
break ;
}
}
}
if ( dur < 0.25f ) dur = 0.25f ;
if ( dur > 1.0f ) dur = 1.0f ;
meleeSwingTimer_ = dur ;
if ( renderer_ - > getAudioCoordinator ( ) - > getActivitySoundManager ( ) ) {
renderer_ - > getAudioCoordinator ( ) - > getActivitySoundManager ( ) - > playMeleeSwing ( ) ;
}
}
// ── Ranged combat ────────────────────────────────────────────────────────────
void AnimationController : : triggerRangedShot ( ) {
auto * characterRenderer = renderer_ - > getCharacterRenderer ( ) ;
uint32_t characterInstanceId = renderer_ - > getCharacterInstanceId ( ) ;
if ( ! characterRenderer | | characterInstanceId = = 0 ) return ;
if ( rangedShootTimer_ > 0.0f ) return ;
if ( emoteActive_ ) cancelEmote ( ) ;
auto has = [ & ] ( uint32_t id ) { return characterRenderer - > hasAnimation ( characterInstanceId , id ) ; } ;
// Resolve ranged attack animation based on weapon type
uint32_t shootAnim = 0 ;
switch ( equippedRangedType_ ) {
case RangedWeaponType : : BOW :
if ( has ( anim : : FIRE_BOW ) ) shootAnim = anim : : FIRE_BOW ;
else if ( has ( anim : : ATTACK_BOW ) ) shootAnim = anim : : ATTACK_BOW ;
break ;
case RangedWeaponType : : GUN :
if ( has ( anim : : ATTACK_RIFLE ) ) shootAnim = anim : : ATTACK_RIFLE ;
break ;
case RangedWeaponType : : CROSSBOW :
if ( has ( anim : : ATTACK_CROSSBOW ) ) shootAnim = anim : : ATTACK_CROSSBOW ;
else if ( has ( anim : : ATTACK_BOW ) ) shootAnim = anim : : ATTACK_BOW ;
break ;
case RangedWeaponType : : THROWN :
if ( has ( anim : : ATTACK_THROWN ) ) shootAnim = anim : : ATTACK_THROWN ;
break ;
default : break ;
}
if ( shootAnim = = 0 ) return ; // Model has no ranged animation
rangedAnimId_ = shootAnim ;
// Query animation duration
std : : vector < pipeline : : M2Sequence > sequences ;
float dur = 0.6f ;
if ( characterRenderer - > getAnimationSequences ( characterInstanceId , sequences ) ) {
for ( const auto & seq : sequences ) {
if ( seq . id = = shootAnim & & seq . duration > 0 ) {
dur = static_cast < float > ( seq . duration ) / 1000.0f ;
break ;
}
}
}
if ( dur < 0.25f ) dur = 0.25f ;
if ( dur > 1.5f ) dur = 1.5f ;
rangedShootTimer_ = dur ;
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
uint32_t AnimationController : : resolveMeleeAnimId ( ) {
auto * characterRenderer = renderer_ - > getCharacterRenderer ( ) ;
uint32_t characterInstanceId = renderer_ - > getCharacterInstanceId ( ) ;
if ( ! characterRenderer | | characterInstanceId = = 0 ) {
meleeAnimId_ = 0 ;
meleeAnimDurationMs_ = 0.0f ;
return 0 ;
}
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
// When dual-wielding, bypass cache to alternate main/off-hand animations
if ( ! equippedHasOffHand_ & & meleeAnimId_ ! = 0 & & characterRenderer - > hasAnimation ( characterInstanceId , meleeAnimId_ ) ) {
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
return meleeAnimId_ ;
}
std : : vector < pipeline : : M2Sequence > sequences ;
if ( ! characterRenderer - > getAnimationSequences ( characterInstanceId , sequences ) ) {
meleeAnimId_ = 0 ;
meleeAnimDurationMs_ = 0.0f ;
return 0 ;
}
auto findDuration = [ & ] ( uint32_t id ) - > float {
for ( const auto & seq : sequences ) {
if ( seq . id = = id & & seq . duration > 0 ) {
return static_cast < float > ( seq . duration ) ;
}
}
return 0.0f ;
} ;
const uint32_t * attackCandidates ;
size_t candidateCount ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
static const uint32_t candidates2H [ ] = { anim : : ATTACK_2H , anim : : ATTACK_1H , anim : : ATTACK_UNARMED , anim : : ATTACK_2H_LOOSE , anim : : PARRY_UNARMED , anim : : PARRY_1H } ;
static const uint32_t candidates2HLoosePierce [ ] = { anim : : ATTACK_2H_LOOSE_PIERCE , anim : : ATTACK_2H_LOOSE , anim : : ATTACK_2H , anim : : ATTACK_1H , anim : : ATTACK_UNARMED } ;
static const uint32_t candidates1H [ ] = { anim : : ATTACK_1H , anim : : ATTACK_2H , anim : : ATTACK_UNARMED , anim : : ATTACK_2H_LOOSE , anim : : PARRY_UNARMED , anim : : PARRY_1H } ;
static const uint32_t candidatesDagger [ ] = { anim : : ATTACK_1H_PIERCE , anim : : ATTACK_1H , anim : : ATTACK_UNARMED } ;
static const uint32_t candidatesUnarmed [ ] = { anim : : ATTACK_UNARMED , anim : : ATTACK_1H , anim : : ATTACK_2H , anim : : ATTACK_2H_LOOSE , anim : : PARRY_UNARMED , anim : : PARRY_1H } ;
static const uint32_t candidatesFist [ ] = { anim : : ATTACK_FIST_1H , anim : : ATTACK_FIST_1H_OFF , anim : : ATTACK_1H , anim : : ATTACK_UNARMED , anim : : PARRY_FIST_1H , anim : : PARRY_1H } ;
// Off-hand attack variants (used when dual-wielding on off-hand turn)
static const uint32_t candidatesOffHand [ ] = { anim : : ATTACK_OFF , anim : : ATTACK_1H , anim : : ATTACK_UNARMED } ;
static const uint32_t candidatesOffHandPierce [ ] = { anim : : ATTACK_OFF_PIERCE , anim : : ATTACK_OFF , anim : : ATTACK_1H_PIERCE , anim : : ATTACK_1H } ;
static const uint32_t candidatesOffHandFist [ ] = { anim : : ATTACK_FIST_1H_OFF , anim : : ATTACK_OFF , anim : : ATTACK_FIST_1H , anim : : ATTACK_1H } ;
static const uint32_t candidatesOffHandUnarmed [ ] = { anim : : ATTACK_UNARMED_OFF , anim : : ATTACK_UNARMED , anim : : ATTACK_OFF , anim : : ATTACK_1H } ;
// Dual-wield: alternate main-hand and off-hand swings
bool useOffHand = equippedHasOffHand_ & & meleeOffHandTurn_ ;
meleeOffHandTurn_ = equippedHasOffHand_ ? ! meleeOffHandTurn_ : false ;
if ( useOffHand ) {
if ( equippedIsFist_ ) {
attackCandidates = candidatesOffHandFist ;
candidateCount = 4 ;
} else if ( equippedIsDagger_ ) {
attackCandidates = candidatesOffHandPierce ;
candidateCount = 4 ;
} else if ( equippedWeaponInvType_ = = game : : InvType : : NON_EQUIP ) {
attackCandidates = candidatesOffHandUnarmed ;
candidateCount = 4 ;
} else {
attackCandidates = candidatesOffHand ;
candidateCount = 3 ;
}
} else if ( equippedIsFist_ ) {
attackCandidates = candidatesFist ;
candidateCount = 6 ;
} else if ( equippedIsDagger_ ) {
attackCandidates = candidatesDagger ;
candidateCount = 3 ;
} else if ( equippedIs2HLoose_ ) {
// Polearm thrust uses pierce variant
attackCandidates = candidates2HLoosePierce ;
candidateCount = 5 ;
} else if ( equippedWeaponInvType_ = = game : : InvType : : TWO_HAND ) {
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
attackCandidates = candidates2H ;
candidateCount = 6 ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
} else if ( equippedWeaponInvType_ = = game : : InvType : : NON_EQUIP ) {
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
attackCandidates = candidatesUnarmed ;
candidateCount = 6 ;
} else {
attackCandidates = candidates1H ;
candidateCount = 6 ;
}
for ( size_t ci = 0 ; ci < candidateCount ; ci + + ) {
uint32_t id = attackCandidates [ ci ] ;
if ( characterRenderer - > hasAnimation ( characterInstanceId , id ) ) {
meleeAnimId_ = id ;
meleeAnimDurationMs_ = findDuration ( id ) ;
return meleeAnimId_ ;
}
}
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
const uint32_t avoidIds [ ] = { anim : : STAND , anim : : DEATH , anim : : WALK , anim : : RUN , anim : : SHUFFLE_LEFT , anim : : SHUFFLE_RIGHT , anim : : WALK_BACKWARDS , anim : : JUMP_START , anim : : JUMP , anim : : JUMP_END , anim : : SWIM_IDLE , anim : : SWIM , anim : : SITTING } ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
auto isAvoid = [ & ] ( uint32_t id ) - > bool {
for ( uint32_t avoid : avoidIds ) {
if ( id = = avoid ) return true ;
}
return false ;
} ;
uint32_t bestId = 0 ;
uint32_t bestDuration = 0 ;
for ( const auto & seq : sequences ) {
if ( seq . duration = = 0 ) continue ;
if ( isAvoid ( seq . id ) ) continue ;
if ( seq . movingSpeed > 0.1f ) continue ;
if ( seq . duration < 150 | | seq . duration > 2000 ) continue ;
if ( bestId = = 0 | | seq . duration < bestDuration ) {
bestId = seq . id ;
bestDuration = seq . duration ;
}
}
if ( bestId = = 0 ) {
for ( const auto & seq : sequences ) {
if ( seq . duration = = 0 ) continue ;
if ( isAvoid ( seq . id ) ) continue ;
if ( bestId = = 0 | | seq . duration < bestDuration ) {
bestId = seq . id ;
bestDuration = seq . duration ;
}
}
}
meleeAnimId_ = bestId ;
meleeAnimDurationMs_ = static_cast < float > ( bestDuration ) ;
return meleeAnimId_ ;
}
// ── Effect triggers ──────────────────────────────────────────────────────────
void AnimationController : : triggerLevelUpEffect ( const glm : : vec3 & position ) {
auto * levelUpEffect = renderer_ - > getLevelUpEffect ( ) ;
if ( ! levelUpEffect ) return ;
if ( ! levelUpEffect - > isModelLoaded ( ) ) {
auto * m2Renderer = renderer_ - > getM2Renderer ( ) ;
if ( m2Renderer ) {
auto * assetManager = core : : Application : : getInstance ( ) . getAssetManager ( ) ;
if ( ! assetManager ) {
LOG_WARNING ( " LevelUpEffect: no asset manager available " ) ;
} else {
auto m2Data = assetManager - > readFile ( " Spells \\ LevelUp \\ LevelUp.m2 " ) ;
auto skinData = assetManager - > readFile ( " Spells \\ LevelUp \\ LevelUp00.skin " ) ;
LOG_INFO ( " LevelUpEffect: m2Data= " , m2Data . size ( ) , " skinData= " , skinData . size ( ) ) ;
if ( ! m2Data . empty ( ) ) {
levelUpEffect - > loadModel ( m2Renderer , m2Data , skinData ) ;
} else {
LOG_WARNING ( " LevelUpEffect: failed to read Spell \\ LevelUp \\ LevelUp.m2 " ) ;
}
}
}
}
levelUpEffect - > trigger ( position ) ;
}
void AnimationController : : startChargeEffect ( const glm : : vec3 & position , const glm : : vec3 & direction ) {
auto * chargeEffect = renderer_ - > getChargeEffect ( ) ;
if ( ! chargeEffect ) return ;
if ( ! chargeEffect - > isActive ( ) ) {
auto * m2Renderer = renderer_ - > getM2Renderer ( ) ;
if ( m2Renderer ) {
auto * assetManager = core : : Application : : getInstance ( ) . getAssetManager ( ) ;
if ( assetManager ) {
chargeEffect - > tryLoadM2Models ( m2Renderer , assetManager ) ;
}
}
}
chargeEffect - > start ( position , direction ) ;
}
void AnimationController : : emitChargeEffect ( const glm : : vec3 & position , const glm : : vec3 & direction ) {
if ( auto * chargeEffect = renderer_ - > getChargeEffect ( ) ) {
chargeEffect - > emit ( position , direction ) ;
}
}
void AnimationController : : stopChargeEffect ( ) {
if ( auto * chargeEffect = renderer_ - > getChargeEffect ( ) ) {
chargeEffect - > stop ( ) ;
}
}
// ── Mount ────────────────────────────────────────────────────────────────────
void AnimationController : : setMounted ( uint32_t mountInstId , uint32_t mountDisplayId , float heightOffset , const std : : string & modelPath ) {
auto * characterRenderer = renderer_ - > getCharacterRenderer ( ) ;
auto * cameraController = renderer_ - > getCameraController ( ) ;
mountInstanceId_ = mountInstId ;
mountHeightOffset_ = heightOffset ;
mountSeatAttachmentId_ = - 1 ;
smoothedMountSeatPos_ = renderer_ - > getCharacterPosition ( ) ;
mountSeatSmoothingInit_ = false ;
mountAction_ = MountAction : : None ;
mountActionPhase_ = 0 ;
charAnimState_ = CharAnimState : : MOUNT ;
if ( cameraController ) {
cameraController - > setMounted ( true ) ;
cameraController - > setMountHeightOffset ( heightOffset ) ;
}
if ( characterRenderer & & mountInstId > 0 ) {
characterRenderer - > dumpAnimations ( mountInstId ) ;
}
// Discover mount animation capabilities (property-based, not hardcoded IDs)
LOG_DEBUG ( " === Mount Animation Dump (Display ID " , mountDisplayId , " ) === " ) ;
if ( characterRenderer ) characterRenderer - > dumpAnimations ( mountInstId ) ;
std : : vector < pipeline : : M2Sequence > sequences ;
if ( ! characterRenderer | | ! characterRenderer - > getAnimationSequences ( mountInstId , sequences ) ) {
LOG_WARNING ( " Failed to get animation sequences for mount, using fallback IDs " ) ;
sequences . clear ( ) ;
}
auto findFirst = [ & ] ( std : : initializer_list < uint32_t > candidates ) - > uint32_t {
for ( uint32_t id : candidates ) {
if ( characterRenderer & & characterRenderer - > hasAnimation ( mountInstId , id ) ) {
return id ;
}
}
return 0 ;
} ;
// Property-based jump animation discovery with chain-based scoring
auto discoverJumpSet = [ & ] ( ) {
LOG_DEBUG ( " === Full sequence table for mount === " ) ;
for ( const auto & seq : sequences ) {
LOG_DEBUG ( " SEQ id= " , seq . id ,
" dur= " , seq . duration ,
" flags=0x " , std : : hex , seq . flags , std : : dec ,
" moveSpd= " , seq . movingSpeed ,
" blend= " , seq . blendTime ,
" next= " , seq . nextAnimation ,
" alias= " , seq . aliasNext ) ;
}
LOG_DEBUG ( " === End sequence table === " ) ;
std : : set < uint32_t > forbiddenIds = { 53 , 54 , 16 } ;
auto scoreNear = [ ] ( int a , int b ) - > int {
int d = std : : abs ( a - b ) ;
return ( d < = 8 ) ? ( 20 - d ) : 0 ;
} ;
auto isForbidden = [ & ] ( uint32_t id ) {
return forbiddenIds . count ( id ) ! = 0 ;
} ;
auto findSeqById = [ & ] ( uint32_t id ) - > const pipeline : : M2Sequence * {
for ( const auto & s : sequences ) {
if ( s . id = = id ) return & s ;
}
return nullptr ;
} ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
uint32_t runId = findFirst ( { anim : : RUN , anim : : WALK } ) ;
uint32_t standId = findFirst ( { anim : : STAND } ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
std : : vector < uint32_t > loops ;
for ( const auto & seq : sequences ) {
if ( isForbidden ( seq . id ) ) continue ;
bool isLoop = ( seq . flags & 0x01 ) = = 0 ;
if ( isLoop & & seq . duration > = 350 & & seq . duration < = 1000 & &
seq . id ! = runId & & seq . id ! = standId ) {
loops . push_back ( seq . id ) ;
}
}
uint32_t loop = 0 ;
if ( ! loops . empty ( ) ) {
uint32_t best = loops [ 0 ] ;
int bestScore = - 999 ;
for ( uint32_t id : loops ) {
int sc = 0 ;
sc + = scoreNear ( static_cast < int > ( id ) , 38 ) ;
const auto * s = findSeqById ( id ) ;
if ( s ) sc + = ( s - > duration > = 500 & & s - > duration < = 800 ) ? 5 : 0 ;
if ( sc > bestScore ) {
bestScore = sc ;
best = id ;
}
}
loop = best ;
}
uint32_t start = 0 , end = 0 ;
int bestStart = - 999 , bestEnd = - 999 ;
for ( const auto & seq : sequences ) {
if ( isForbidden ( seq . id ) ) continue ;
bool isLoop = ( seq . flags & 0x01 ) = = 0 ;
if ( isLoop ) continue ;
if ( seq . duration > = 450 & & seq . duration < = 1100 ) {
int sc = 0 ;
if ( loop ) sc + = scoreNear ( static_cast < int > ( seq . id ) , static_cast < int > ( loop ) ) ;
if ( loop & & ( seq . nextAnimation = = static_cast < int16_t > ( loop ) | | seq . aliasNext = = loop ) ) sc + = 30 ;
if ( loop & & scoreNear ( seq . nextAnimation , static_cast < int > ( loop ) ) > 0 ) sc + = 10 ;
if ( seq . blendTime > 400 ) sc - = 5 ;
if ( sc > bestStart ) {
bestStart = sc ;
start = seq . id ;
}
}
if ( seq . duration > = 650 & & seq . duration < = 1600 ) {
int sc = 0 ;
if ( loop ) sc + = scoreNear ( static_cast < int > ( seq . id ) , static_cast < int > ( loop ) ) ;
if ( seq . nextAnimation = = static_cast < int16_t > ( runId ) | | seq . nextAnimation = = static_cast < int16_t > ( standId ) ) sc + = 10 ;
if ( seq . nextAnimation < 0 ) sc + = 5 ;
if ( sc > bestEnd ) {
bestEnd = sc ;
end = seq . id ;
}
}
}
LOG_DEBUG ( " Property-based jump discovery: start= " , start , " loop= " , loop , " end= " , end ,
" scores: start= " , bestStart , " end= " , bestEnd ) ;
return std : : make_tuple ( start , loop , end ) ;
} ;
auto [ discoveredStart , discoveredLoop , discoveredEnd ] = discoverJumpSet ( ) ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
mountAnims_ . jumpStart = discoveredStart > 0 ? discoveredStart : findFirst ( { anim : : FALL , anim : : JUMP_START } ) ;
mountAnims_ . jumpLoop = discoveredLoop > 0 ? discoveredLoop : findFirst ( { anim : : JUMP } ) ;
mountAnims_ . jumpEnd = discoveredEnd > 0 ? discoveredEnd : findFirst ( { anim : : JUMP_END } ) ;
mountAnims_ . rearUp = findFirst ( { anim : : MOUNT_SPECIAL , anim : : RUN_RIGHT , anim : : FALL } ) ;
mountAnims_ . run = findFirst ( { anim : : RUN , anim : : WALK } ) ;
mountAnims_ . stand = findFirst ( { anim : : STAND } ) ;
// Discover flight animations (flying mounts only — may all be 0 for ground mounts)
mountAnims_ . flyIdle = findFirst ( { anim : : FLY_IDLE } ) ;
mountAnims_ . flyForward = findFirst ( { anim : : FLY_FORWARD , anim : : FLY_RUN_2 } ) ;
mountAnims_ . flyBackwards = findFirst ( { anim : : FLY_BACKWARDS , anim : : FLY_WALK_BACKWARDS } ) ;
mountAnims_ . flyLeft = findFirst ( { anim : : FLY_LEFT , anim : : FLY_SHUFFLE_LEFT } ) ;
mountAnims_ . flyRight = findFirst ( { anim : : FLY_RIGHT , anim : : FLY_SHUFFLE_RIGHT } ) ;
mountAnims_ . flyUp = findFirst ( { anim : : FLY_UP , anim : : FLY_RISE } ) ;
mountAnims_ . flyDown = findFirst ( { anim : : FLY_DOWN } ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
// Discover idle fidget animations using proper WoW M2 metadata
mountAnims_ . fidgets . clear ( ) ;
core : : Logger : : getInstance ( ) . debug ( " Scanning for fidget animations in " , sequences . size ( ) , " sequences " ) ;
core : : Logger : : getInstance ( ) . debug ( " === ALL potential fidgets (no metadata filter) === " ) ;
for ( const auto & seq : sequences ) {
bool isLoop = ( seq . flags & 0x01 ) = = 0 ;
bool isStationary = std : : abs ( seq . movingSpeed ) < 0.05f ;
bool reasonableDuration = seq . duration > = 400 & & seq . duration < = 2500 ;
if ( ! isLoop & & reasonableDuration & & isStationary ) {
core : : Logger : : getInstance ( ) . debug ( " ALL: id= " , seq . id ,
" dur= " , seq . duration , " ms " ,
" freq= " , seq . frequency ,
" replay= " , seq . replayMin , " - " , seq . replayMax ,
" flags=0x " , std : : hex , seq . flags , std : : dec ,
" next= " , seq . nextAnimation ) ;
}
}
for ( const auto & seq : sequences ) {
bool isLoop = ( seq . flags & 0x01 ) = = 0 ;
bool hasFrequency = seq . frequency > 0 ;
bool hasReplay = seq . replayMax > 0 ;
bool isStationary = std : : abs ( seq . movingSpeed ) < 0.05f ;
bool reasonableDuration = seq . duration > = 400 & & seq . duration < = 2500 ;
if ( ! isLoop & & reasonableDuration & & isStationary & & ( hasFrequency | | hasReplay ) ) {
core : : Logger : : getInstance ( ) . debug ( " Candidate: id= " , seq . id ,
" dur= " , seq . duration , " ms " ,
" freq= " , seq . frequency ,
" replay= " , seq . replayMin , " - " , seq . replayMax ,
" next= " , seq . nextAnimation ,
" speed= " , seq . movingSpeed ) ;
}
bool isDeathOrWound = ( seq . id > = 5 & & seq . id < = 9 ) ;
bool isAttackOrCombat = ( seq . id > = 11 & & seq . id < = 21 ) ;
bool isSpecial = ( seq . id = = 2 | | seq . id = = 3 ) ;
if ( ! isLoop & & ( hasFrequency | | hasReplay ) & & isStationary & & reasonableDuration & &
! isDeathOrWound & & ! isAttackOrCombat & & ! isSpecial ) {
bool chainsToStand = ( seq . nextAnimation = = static_cast < int16_t > ( mountAnims_ . stand ) ) | |
( seq . aliasNext = = mountAnims_ . stand ) | |
( seq . nextAnimation = = - 1 ) ;
mountAnims_ . fidgets . push_back ( seq . id ) ;
core : : Logger : : getInstance ( ) . debug ( " >> Selected fidget: id= " , seq . id ,
( chainsToStand ? " (chains to stand) " : " " ) ) ;
}
}
if ( mountAnims_ . run = = 0 ) mountAnims_ . run = mountAnims_ . stand ;
core : : Logger : : getInstance ( ) . debug ( " Mount animation set: jumpStart= " , mountAnims_ . jumpStart ,
" jumpLoop= " , mountAnims_ . jumpLoop ,
" jumpEnd= " , mountAnims_ . jumpEnd ,
" rearUp= " , mountAnims_ . rearUp ,
" run= " , mountAnims_ . run ,
" stand= " , mountAnims_ . stand ,
" fidgets= " , mountAnims_ . fidgets . size ( ) ) ;
if ( renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) ) {
bool isFlying = taxiFlight_ ;
renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) - > onMount ( mountDisplayId , isFlying , modelPath ) ;
}
}
void AnimationController : : clearMount ( ) {
mountInstanceId_ = 0 ;
mountHeightOffset_ = 0.0f ;
mountPitch_ = 0.0f ;
mountRoll_ = 0.0f ;
mountSeatAttachmentId_ = - 1 ;
smoothedMountSeatPos_ = glm : : vec3 ( 0.0f ) ;
mountSeatSmoothingInit_ = false ;
mountAction_ = MountAction : : None ;
mountActionPhase_ = 0 ;
charAnimState_ = CharAnimState : : IDLE ;
if ( auto * cameraController = renderer_ - > getCameraController ( ) ) {
cameraController - > setMounted ( false ) ;
cameraController - > setMountHeightOffset ( 0.0f ) ;
}
if ( renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) ) {
renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) - > onDismount ( ) ;
}
}
// ── Query helpers ────────────────────────────────────────────────────────────
bool AnimationController : : isFootstepAnimationState ( ) const {
return charAnimState_ = = CharAnimState : : WALK | | charAnimState_ = = CharAnimState : : RUN ;
}
// ── Melee timers ─────────────────────────────────────────────────────────────
void AnimationController : : updateMeleeTimers ( float deltaTime ) {
if ( meleeSwingCooldown_ > 0.0f ) {
meleeSwingCooldown_ = std : : max ( 0.0f , meleeSwingCooldown_ - deltaTime ) ;
}
if ( meleeSwingTimer_ > 0.0f ) {
meleeSwingTimer_ = std : : max ( 0.0f , meleeSwingTimer_ - deltaTime ) ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
if ( meleeSwingTimer_ < = 0.0f ) specialAttackAnimId_ = 0 ;
}
// Ranged shot timer (same pattern as melee)
if ( rangedShootTimer_ > 0.0f ) {
rangedShootTimer_ = std : : max ( 0.0f , rangedShootTimer_ - deltaTime ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
}
// ── Character animation state machine ────────────────────────────────────────
void AnimationController : : updateCharacterAnimation ( ) {
auto * characterRenderer = renderer_ - > getCharacterRenderer ( ) ;
auto * cameraController = renderer_ - > getCameraController ( ) ;
uint32_t characterInstanceId = renderer_ - > getCharacterInstanceId ( ) ;
CharAnimState newState = charAnimState_ ;
const bool rawMoving = cameraController - > isMoving ( ) ;
const bool rawSprinting = cameraController - > isSprinting ( ) ;
constexpr float kLocomotionStopGraceSec = 0.12f ;
if ( rawMoving ) {
locomotionStopGraceTimer_ = kLocomotionStopGraceSec ;
locomotionWasSprinting_ = rawSprinting ;
} else {
locomotionStopGraceTimer_ = std : : max ( 0.0f , locomotionStopGraceTimer_ - lastDeltaTime_ ) ;
}
bool moving = rawMoving | | locomotionStopGraceTimer_ > 0.0f ;
bool movingForward = cameraController - > isMovingForward ( ) ;
bool movingBackward = cameraController - > isMovingBackward ( ) ;
bool autoRunning = cameraController - > isAutoRunning ( ) ;
bool strafeLeft = cameraController - > isStrafingLeft ( ) ;
bool strafeRight = cameraController - > isStrafingRight ( ) ;
bool pureStrafe = ! movingForward & & ! movingBackward & & ! autoRunning ;
bool anyStrafeLeft = strafeLeft & & ! strafeRight & & pureStrafe ;
bool anyStrafeRight = strafeRight & & ! strafeLeft & & pureStrafe ;
bool grounded = cameraController - > isGrounded ( ) ;
bool jumping = cameraController - > isJumping ( ) ;
bool sprinting = rawSprinting | | ( ! rawMoving & & moving & & locomotionWasSprinting_ ) ;
bool sitting = cameraController - > isSitting ( ) ;
bool swim = cameraController - > isSwimming ( ) ;
bool forceMelee = meleeSwingTimer_ > 0.0f & & grounded & & ! swim ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
bool forceRanged = rangedShootTimer_ > 0.0f & & grounded & & ! swim ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
const glm : : vec3 & characterPosition = renderer_ - > getCharacterPosition ( ) ;
float characterYaw = renderer_ - > getCharacterYaw ( ) ;
// When mounted, force MOUNT state and skip normal transitions
if ( isMounted ( ) ) {
newState = CharAnimState : : MOUNT ;
charAnimState_ = newState ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
// Rider animation — defaults to MOUNT, but uses MOUNT_FLIGHT_* variants when flying
uint32_t riderAnim = anim : : MOUNT ;
if ( cameraController - > isFlyingActive ( ) ) {
auto hasRider = [ & ] ( uint32_t id ) { return characterRenderer - > hasAnimation ( characterInstanceId , id ) ; } ;
if ( moving ) {
if ( cameraController - > isAscending ( ) & & hasRider ( anim : : MOUNT_FLIGHT_UP ) )
riderAnim = anim : : MOUNT_FLIGHT_UP ;
else if ( cameraController - > isDescending ( ) & & hasRider ( anim : : MOUNT_FLIGHT_DOWN ) )
riderAnim = anim : : MOUNT_FLIGHT_DOWN ;
else if ( hasRider ( anim : : MOUNT_FLIGHT_FORWARD ) )
riderAnim = anim : : MOUNT_FLIGHT_FORWARD ;
} else {
if ( hasRider ( anim : : MOUNT_FLIGHT_IDLE ) )
riderAnim = anim : : MOUNT_FLIGHT_IDLE ;
}
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
uint32_t currentAnimId = 0 ;
float currentAnimTimeMs = 0.0f , currentAnimDurationMs = 0.0f ;
bool haveState = characterRenderer - > getAnimationState ( characterInstanceId , currentAnimId , currentAnimTimeMs , currentAnimDurationMs ) ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
if ( ! haveState | | currentAnimId ! = riderAnim ) {
characterRenderer - > playAnimation ( characterInstanceId , riderAnim , true ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
float mountBob = 0.0f ;
float mountYawRad = glm : : radians ( characterYaw ) ;
if ( mountInstanceId_ > 0 ) {
characterRenderer - > setInstancePosition ( mountInstanceId_ , characterPosition ) ;
if ( ! taxiFlight_ & & moving & & lastDeltaTime_ > 0.0f ) {
float currentYawDeg = characterYaw ;
float turnRate = ( currentYawDeg - prevMountYaw_ ) / lastDeltaTime_ ;
while ( turnRate > 180.0f ) turnRate - = 360.0f ;
while ( turnRate < - 180.0f ) turnRate + = 360.0f ;
float targetLean = glm : : clamp ( turnRate * 0.15f , - 0.25f , 0.25f ) ;
mountRoll_ = glm : : mix ( mountRoll_ , targetLean , lastDeltaTime_ * 6.0f ) ;
prevMountYaw_ = currentYawDeg ;
} else {
mountRoll_ = glm : : mix ( mountRoll_ , 0.0f , lastDeltaTime_ * 8.0f ) ;
}
characterRenderer - > setInstanceRotation ( mountInstanceId_ , glm : : vec3 ( mountPitch_ , mountRoll_ , mountYawRad ) ) ;
auto pickMountAnim = [ & ] ( std : : initializer_list < uint32_t > candidates , uint32_t fallback ) - > uint32_t {
for ( uint32_t id : candidates ) {
if ( characterRenderer - > hasAnimation ( mountInstanceId_ , id ) ) {
return id ;
}
}
return fallback ;
} ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
uint32_t mountAnimId = anim : : STAND ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
uint32_t curMountAnim = 0 ;
float curMountTime = 0 , curMountDur = 0 ;
bool haveMountState = characterRenderer - > getAnimationState ( mountInstanceId_ , curMountAnim , curMountTime , curMountDur ) ;
if ( taxiFlight_ ) {
if ( ! taxiAnimsLogged_ ) {
taxiAnimsLogged_ = true ;
LOG_INFO ( " Taxi flight active: mountInstanceId_= " , mountInstanceId_ ,
" curMountAnim= " , curMountAnim , " haveMountState= " , haveMountState ) ;
std : : vector < pipeline : : M2Sequence > seqs ;
if ( characterRenderer - > getAnimationSequences ( mountInstanceId_ , seqs ) ) {
std : : string animList ;
for ( const auto & s : seqs ) {
if ( ! animList . empty ( ) ) animList + = " , " ;
animList + = std : : to_string ( s . id ) ;
}
LOG_INFO ( " Taxi mount available animations: [ " , animList , " ] " ) ;
}
}
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
uint32_t flyAnims [ ] = { anim : : FLY_FORWARD , anim : : FLY_IDLE , anim : : FLY_RUN_2 , anim : : FLY_SPELL , anim : : FLY_RISE , anim : : SPELL_KNEEL_LOOP , anim : : FLY_CUSTOM_SPELL_10 , anim : : DEAD , anim : : RUN } ;
mountAnimId = anim : : STAND ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
for ( uint32_t fa : flyAnims ) {
if ( characterRenderer - > hasAnimation ( mountInstanceId_ , fa ) ) {
mountAnimId = fa ;
break ;
}
}
if ( ! haveMountState | | curMountAnim ! = mountAnimId ) {
LOG_INFO ( " Taxi mount: playing animation " , mountAnimId ) ;
characterRenderer - > playAnimation ( mountInstanceId_ , mountAnimId , true ) ;
}
goto taxi_mount_done ;
} else {
taxiAnimsLogged_ = false ;
}
// Check for jump trigger
if ( cameraController - > isJumpKeyPressed ( ) & & grounded & & mountAction_ = = MountAction : : None ) {
if ( moving & & mountAnims_ . jumpLoop > 0 ) {
LOG_DEBUG ( " Mount jump triggered while moving: using jumpLoop anim " , mountAnims_ . jumpLoop ) ;
characterRenderer - > playAnimation ( mountInstanceId_ , mountAnims_ . jumpLoop , true ) ;
mountAction_ = MountAction : : Jump ;
mountActionPhase_ = 1 ;
mountAnimId = mountAnims_ . jumpLoop ;
if ( renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) ) {
renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) - > playJumpSound ( ) ;
}
if ( cameraController ) {
cameraController - > triggerMountJump ( ) ;
}
} else if ( ! moving & & mountAnims_ . rearUp > 0 ) {
LOG_DEBUG ( " Mount rear-up triggered: playing rearUp anim " , mountAnims_ . rearUp ) ;
characterRenderer - > playAnimation ( mountInstanceId_ , mountAnims_ . rearUp , false ) ;
mountAction_ = MountAction : : RearUp ;
mountActionPhase_ = 0 ;
mountAnimId = mountAnims_ . rearUp ;
if ( renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) ) {
renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) - > playRearUpSound ( ) ;
}
}
}
// Handle active mount actions (jump chaining or rear-up)
if ( mountAction_ ! = MountAction : : None ) {
bool animFinished = haveMountState & & curMountDur > 0.1f & &
( curMountTime > = curMountDur - 0.05f ) ;
if ( mountAction_ = = MountAction : : Jump ) {
if ( mountActionPhase_ = = 0 & & animFinished & & mountAnims_ . jumpLoop > 0 ) {
LOG_DEBUG ( " Mount jump: phase 0→1 (JumpStart→JumpLoop anim " , mountAnims_ . jumpLoop , " ) " ) ;
characterRenderer - > playAnimation ( mountInstanceId_ , mountAnims_ . jumpLoop , true ) ;
mountActionPhase_ = 1 ;
mountAnimId = mountAnims_ . jumpLoop ;
} else if ( mountActionPhase_ = = 0 & & animFinished & & mountAnims_ . jumpLoop = = 0 ) {
LOG_DEBUG ( " Mount jump: phase 0→1 (no JumpLoop, holding JumpStart) " ) ;
mountActionPhase_ = 1 ;
} else if ( mountActionPhase_ = = 1 & & grounded & & mountAnims_ . jumpEnd > 0 ) {
LOG_DEBUG ( " Mount jump: phase 1→2 (landed, JumpEnd anim " , mountAnims_ . jumpEnd , " ) " ) ;
characterRenderer - > playAnimation ( mountInstanceId_ , mountAnims_ . jumpEnd , false ) ;
mountActionPhase_ = 2 ;
mountAnimId = mountAnims_ . jumpEnd ;
if ( renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) ) {
renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) - > playLandSound ( ) ;
}
} else if ( mountActionPhase_ = = 1 & & grounded & & mountAnims_ . jumpEnd = = 0 ) {
LOG_DEBUG ( " Mount jump: phase 1→done (landed, no JumpEnd, returning to " ,
moving ? " run " : " stand " , " anim " , ( moving ? mountAnims_ . run : mountAnims_ . stand ) , " ) " ) ;
mountAction_ = MountAction : : None ;
mountAnimId = moving ? mountAnims_ . run : mountAnims_ . stand ;
characterRenderer - > playAnimation ( mountInstanceId_ , mountAnimId , true ) ;
} else if ( mountActionPhase_ = = 2 & & animFinished ) {
LOG_DEBUG ( " Mount jump: phase 2→done (JumpEnd finished, returning to " ,
moving ? " run " : " stand " , " anim " , ( moving ? mountAnims_ . run : mountAnims_ . stand ) , " ) " ) ;
mountAction_ = MountAction : : None ;
mountAnimId = moving ? mountAnims_ . run : mountAnims_ . stand ;
characterRenderer - > playAnimation ( mountInstanceId_ , mountAnimId , true ) ;
} else {
mountAnimId = curMountAnim ;
}
} else if ( mountAction_ = = MountAction : : RearUp ) {
if ( animFinished ) {
LOG_DEBUG ( " Mount rear-up: finished, returning to " ,
moving ? " run " : " stand " , " anim " , ( moving ? mountAnims_ . run : mountAnims_ . stand ) ) ;
mountAction_ = MountAction : : None ;
mountAnimId = moving ? mountAnims_ . run : mountAnims_ . stand ;
characterRenderer - > playAnimation ( mountInstanceId_ , mountAnimId , true ) ;
} else {
mountAnimId = curMountAnim ;
}
}
} else if ( moving ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
const bool flying = cameraController - > isFlyingActive ( ) ;
const bool mountSwim = cameraController - > isSwimming ( ) ;
if ( flying ) {
// Directional flying animations for mount
if ( cameraController - > isAscending ( ) ) {
mountAnimId = pickMountAnim ( { anim : : FLY_UP , anim : : FLY_FORWARD } , anim : : RUN ) ;
} else if ( cameraController - > isDescending ( ) ) {
mountAnimId = pickMountAnim ( { anim : : FLY_DOWN , anim : : FLY_FORWARD } , anim : : RUN ) ;
} else if ( anyStrafeLeft ) {
mountAnimId = pickMountAnim ( { anim : : FLY_LEFT , anim : : FLY_SHUFFLE_LEFT , anim : : FLY_FORWARD } , anim : : RUN ) ;
} else if ( anyStrafeRight ) {
mountAnimId = pickMountAnim ( { anim : : FLY_RIGHT , anim : : FLY_SHUFFLE_RIGHT , anim : : FLY_FORWARD } , anim : : RUN ) ;
} else if ( movingBackward ) {
mountAnimId = pickMountAnim ( { anim : : FLY_BACKWARDS , anim : : FLY_WALK_BACKWARDS , anim : : FLY_FORWARD } , anim : : RUN ) ;
} else {
mountAnimId = pickMountAnim ( { anim : : FLY_FORWARD , anim : : FLY_IDLE } , anim : : RUN ) ;
}
} else if ( mountSwim ) {
// Mounted swimming animations
if ( anyStrafeLeft ) {
mountAnimId = pickMountAnim ( { anim : : MOUNT_SWIM_LEFT , anim : : SWIM_LEFT , anim : : MOUNT_SWIM } , anim : : RUN ) ;
} else if ( anyStrafeRight ) {
mountAnimId = pickMountAnim ( { anim : : MOUNT_SWIM_RIGHT , anim : : SWIM_RIGHT , anim : : MOUNT_SWIM } , anim : : RUN ) ;
} else if ( movingBackward ) {
mountAnimId = pickMountAnim ( { anim : : MOUNT_SWIM_BACKWARDS , anim : : SWIM_BACKWARDS , anim : : MOUNT_SWIM } , anim : : RUN ) ;
} else {
mountAnimId = pickMountAnim ( { anim : : MOUNT_SWIM , anim : : SWIM } , anim : : RUN ) ;
}
} else if ( anyStrafeLeft ) {
mountAnimId = pickMountAnim ( { anim : : MOUNT_RUN_LEFT , anim : : RUN_LEFT , anim : : SHUFFLE_LEFT , anim : : RUN } , anim : : RUN ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
} else if ( anyStrafeRight ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
mountAnimId = pickMountAnim ( { anim : : MOUNT_RUN_RIGHT , anim : : RUN_RIGHT , anim : : SHUFFLE_RIGHT , anim : : RUN } , anim : : RUN ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
} else if ( movingBackward ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
mountAnimId = pickMountAnim ( { anim : : MOUNT_WALK_BACKWARDS , anim : : WALK_BACKWARDS } , anim : : RUN ) ;
} else {
mountAnimId = anim : : RUN ;
}
} else if ( ! moving & & cameraController - > isSwimming ( ) ) {
// Mounted swim idle
mountAnimId = pickMountAnim ( { anim : : MOUNT_SWIM_IDLE , anim : : SWIM_IDLE } , anim : : STAND ) ;
} else if ( ! moving & & cameraController - > isFlyingActive ( ) ) {
// Hovering in flight — use FLY_IDLE instead of STAND
if ( cameraController - > isAscending ( ) ) {
mountAnimId = pickMountAnim ( { anim : : FLY_UP , anim : : FLY_IDLE } , anim : : STAND ) ;
} else if ( cameraController - > isDescending ( ) ) {
mountAnimId = pickMountAnim ( { anim : : FLY_DOWN , anim : : FLY_IDLE } , anim : : STAND ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
} else {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
mountAnimId = pickMountAnim ( { anim : : FLY_IDLE , anim : : FLY_FORWARD } , anim : : STAND ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
}
// Cancel active fidget immediately if movement starts
if ( moving & & mountActiveFidget_ ! = 0 ) {
mountActiveFidget_ = 0 ;
characterRenderer - > playAnimation ( mountInstanceId_ , mountAnimId , true ) ;
}
// Check if active fidget has completed
if ( ! moving & & mountActiveFidget_ ! = 0 ) {
uint32_t curAnim = 0 ;
float curTime = 0.0f , curDur = 0.0f ;
if ( characterRenderer - > getAnimationState ( mountInstanceId_ , curAnim , curTime , curDur ) ) {
if ( curAnim ! = mountActiveFidget_ | | curTime > = curDur * 0.95f ) {
mountActiveFidget_ = 0 ;
LOG_DEBUG ( " Mount fidget completed " ) ;
}
}
}
// Idle fidgets
if ( ! moving & & mountAction_ = = MountAction : : None & & mountActiveFidget_ = = 0 & & ! mountAnims_ . fidgets . empty ( ) ) {
mountIdleFidgetTimer_ + = lastDeltaTime_ ;
static std : : mt19937 idleRng ( std : : random_device { } ( ) ) ;
static float nextFidgetTime = std : : uniform_real_distribution < float > ( 6.0f , 12.0f ) ( idleRng ) ;
if ( mountIdleFidgetTimer_ > = nextFidgetTime ) {
std : : uniform_int_distribution < size_t > dist ( 0 , mountAnims_ . fidgets . size ( ) - 1 ) ;
uint32_t fidgetAnim = mountAnims_ . fidgets [ dist ( idleRng ) ] ;
characterRenderer - > playAnimation ( mountInstanceId_ , fidgetAnim , false ) ;
mountActiveFidget_ = fidgetAnim ;
mountIdleFidgetTimer_ = 0.0f ;
nextFidgetTime = std : : uniform_real_distribution < float > ( 6.0f , 12.0f ) ( idleRng ) ;
LOG_DEBUG ( " Mount idle fidget: playing anim " , fidgetAnim ) ;
}
}
if ( moving ) {
mountIdleFidgetTimer_ = 0.0f ;
}
// Idle ambient sounds
if ( ! moving & & renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) ) {
mountIdleSoundTimer_ + = lastDeltaTime_ ;
static std : : mt19937 soundRng ( std : : random_device { } ( ) ) ;
static float nextIdleSoundTime = std : : uniform_real_distribution < float > ( 45.0f , 90.0f ) ( soundRng ) ;
if ( mountIdleSoundTimer_ > = nextIdleSoundTime ) {
renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) - > playIdleSound ( ) ;
mountIdleSoundTimer_ = 0.0f ;
nextIdleSoundTime = std : : uniform_real_distribution < float > ( 45.0f , 90.0f ) ( soundRng ) ;
}
} else if ( moving ) {
mountIdleSoundTimer_ = 0.0f ;
}
// Only update animation if changed and not in action or fidget
if ( mountAction_ = = MountAction : : None & & mountActiveFidget_ = = 0 & & ( ! haveMountState | | curMountAnim ! = mountAnimId ) ) {
bool loop = true ;
characterRenderer - > playAnimation ( mountInstanceId_ , mountAnimId , loop ) ;
}
taxi_mount_done :
mountBob = 0.0f ;
if ( moving & & haveMountState & & curMountDur > 1.0f ) {
float wrappedTime = curMountTime ;
while ( wrappedTime > = curMountDur ) {
wrappedTime - = curMountDur ;
}
float norm = wrappedTime / curMountDur ;
float bobSpeed = taxiFlight_ ? 2.0f : 1.0f ;
mountBob = std : : sin ( norm * 2.0f * 3.14159f * bobSpeed ) * 0.12f ;
}
}
// Use mount's attachment point for proper bone-driven rider positioning.
if ( taxiFlight_ ) {
glm : : mat4 mountSeatTransform ( 1.0f ) ;
bool haveSeat = false ;
static constexpr uint32_t kTaxiSeatAttachmentId = 0 ;
if ( mountSeatAttachmentId_ = = - 1 ) {
mountSeatAttachmentId_ = static_cast < int > ( kTaxiSeatAttachmentId ) ;
}
if ( mountSeatAttachmentId_ > = 0 ) {
haveSeat = characterRenderer - > getAttachmentTransform (
mountInstanceId_ , static_cast < uint32_t > ( mountSeatAttachmentId_ ) , mountSeatTransform ) ;
}
if ( ! haveSeat ) {
mountSeatAttachmentId_ = - 2 ;
}
if ( haveSeat ) {
glm : : vec3 targetRiderPos = glm : : vec3 ( mountSeatTransform [ 3 ] ) + glm : : vec3 ( 0.0f , 0.0f , 0.02f ) ;
mountSeatSmoothingInit_ = false ;
smoothedMountSeatPos_ = targetRiderPos ;
characterRenderer - > setInstancePosition ( characterInstanceId , targetRiderPos ) ;
} else {
mountSeatSmoothingInit_ = false ;
glm : : vec3 playerPos = characterPosition + glm : : vec3 ( 0.0f , 0.0f , mountHeightOffset_ + 0.10f ) ;
characterRenderer - > setInstancePosition ( characterInstanceId , playerPos ) ;
}
float riderPitch = mountPitch_ * 0.35f ;
float riderRoll = mountRoll_ * 0.35f ;
float mountYawRadVal = glm : : radians ( characterYaw ) ;
characterRenderer - > setInstanceRotation ( characterInstanceId , glm : : vec3 ( riderPitch , riderRoll , mountYawRadVal ) ) ;
return ;
}
// Ground mounts: try a seat attachment first.
glm : : mat4 mountSeatTransform ;
bool haveSeat = false ;
if ( mountSeatAttachmentId_ > = 0 ) {
haveSeat = characterRenderer - > getAttachmentTransform (
mountInstanceId_ , static_cast < uint32_t > ( mountSeatAttachmentId_ ) , mountSeatTransform ) ;
} else if ( mountSeatAttachmentId_ = = - 1 ) {
static constexpr uint32_t kSeatAttachments [ ] = { 0 , 5 , 6 , 7 , 8 } ;
for ( uint32_t attId : kSeatAttachments ) {
if ( characterRenderer - > getAttachmentTransform ( mountInstanceId_ , attId , mountSeatTransform ) ) {
mountSeatAttachmentId_ = static_cast < int > ( attId ) ;
haveSeat = true ;
break ;
}
}
if ( ! haveSeat ) {
mountSeatAttachmentId_ = - 2 ;
}
}
if ( haveSeat ) {
glm : : vec3 mountSeatPos = glm : : vec3 ( mountSeatTransform [ 3 ] ) ;
glm : : vec3 seatOffset = glm : : vec3 ( 0.0f , 0.0f , taxiFlight_ ? 0.04f : 0.08f ) ;
glm : : vec3 targetRiderPos = mountSeatPos + seatOffset ;
if ( moving ) {
mountSeatSmoothingInit_ = false ;
smoothedMountSeatPos_ = targetRiderPos ;
} else if ( ! mountSeatSmoothingInit_ ) {
smoothedMountSeatPos_ = targetRiderPos ;
mountSeatSmoothingInit_ = true ;
} else {
float smoothHz = taxiFlight_ ? 10.0f : 14.0f ;
float alpha = 1.0f - std : : exp ( - smoothHz * std : : max ( lastDeltaTime_ , 0.001f ) ) ;
smoothedMountSeatPos_ = glm : : mix ( smoothedMountSeatPos_ , targetRiderPos , alpha ) ;
}
characterRenderer - > setInstancePosition ( characterInstanceId , smoothedMountSeatPos_ ) ;
float yawRad = glm : : radians ( characterYaw ) ;
float riderPitch = mountPitch_ * 0.35f ;
float riderRoll = mountRoll_ * 0.35f ;
characterRenderer - > setInstanceRotation ( characterInstanceId , glm : : vec3 ( riderPitch , riderRoll , yawRad ) ) ;
} else {
mountSeatSmoothingInit_ = false ;
float yawRad = glm : : radians ( characterYaw ) ;
glm : : mat4 mountRotation = glm : : mat4 ( 1.0f ) ;
mountRotation = glm : : rotate ( mountRotation , yawRad , glm : : vec3 ( 0.0f , 0.0f , 1.0f ) ) ;
mountRotation = glm : : rotate ( mountRotation , mountRoll_ , glm : : vec3 ( 1.0f , 0.0f , 0.0f ) ) ;
mountRotation = glm : : rotate ( mountRotation , mountPitch_ , glm : : vec3 ( 0.0f , 1.0f , 0.0f ) ) ;
glm : : vec3 localOffset ( 0.0f , 0.0f , mountHeightOffset_ + mountBob ) ;
glm : : vec3 worldOffset = glm : : vec3 ( mountRotation * glm : : vec4 ( localOffset , 0.0f ) ) ;
glm : : vec3 playerPos = characterPosition + worldOffset ;
characterRenderer - > setInstancePosition ( characterInstanceId , playerPos ) ;
characterRenderer - > setInstanceRotation ( characterInstanceId , glm : : vec3 ( mountPitch_ , mountRoll_ , yawRad ) ) ;
}
return ;
}
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
if ( ! forceMelee & & ! forceRanged ) switch ( charAnimState_ ) {
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
case CharAnimState : : IDLE :
if ( swim ) {
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( sitting & & grounded ) {
newState = CharAnimState : : SIT_DOWN ;
} else if ( ! grounded & & jumping ) {
newState = CharAnimState : : JUMP_START ;
} else if ( ! grounded ) {
newState = CharAnimState : : JUMP_MID ;
} else if ( moving & & sprinting ) {
newState = CharAnimState : : RUN ;
} else if ( moving ) {
newState = CharAnimState : : WALK ;
} else if ( inCombat_ & & grounded ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
// Play unsheathe one-shot before entering combat idle
if ( characterRenderer & & characterInstanceId > 0 & &
characterRenderer - > hasAnimation ( characterInstanceId , anim : : UNSHEATHE ) ) {
newState = CharAnimState : : UNSHEATHE ;
} else {
newState = CharAnimState : : COMBAT_IDLE ;
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
break ;
case CharAnimState : : WALK :
if ( swim ) {
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( ! grounded & & jumping ) {
newState = CharAnimState : : JUMP_START ;
} else if ( ! grounded ) {
newState = CharAnimState : : JUMP_MID ;
} else if ( ! moving ) {
newState = CharAnimState : : IDLE ;
} else if ( sprinting ) {
newState = CharAnimState : : RUN ;
}
break ;
case CharAnimState : : RUN :
if ( swim ) {
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( ! grounded & & jumping ) {
newState = CharAnimState : : JUMP_START ;
} else if ( ! grounded ) {
newState = CharAnimState : : JUMP_MID ;
} else if ( ! moving ) {
newState = CharAnimState : : IDLE ;
} else if ( ! sprinting ) {
newState = CharAnimState : : WALK ;
}
break ;
case CharAnimState : : JUMP_START :
if ( swim ) {
newState = CharAnimState : : SWIM_IDLE ;
} else if ( grounded ) {
newState = CharAnimState : : JUMP_END ;
} else {
newState = CharAnimState : : JUMP_MID ;
}
break ;
case CharAnimState : : JUMP_MID :
if ( swim ) {
newState = CharAnimState : : SWIM_IDLE ;
} else if ( grounded ) {
newState = CharAnimState : : JUMP_END ;
}
break ;
case CharAnimState : : JUMP_END :
if ( swim ) {
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( moving & & sprinting ) {
newState = CharAnimState : : RUN ;
} else if ( moving ) {
newState = CharAnimState : : WALK ;
} else {
newState = CharAnimState : : IDLE ;
}
break ;
case CharAnimState : : SIT_DOWN :
if ( swim ) {
newState = CharAnimState : : SWIM_IDLE ;
} else if ( ! sitting ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
// Stand up requested — play exit animation if available and not moving
if ( sitUpAnim_ ! = 0 & & ! moving ) {
newState = CharAnimState : : SIT_UP ;
} else {
newState = CharAnimState : : IDLE ;
}
} else if ( sitDownAnim_ ! = 0 & & characterRenderer & & characterInstanceId > 0 ) {
// Auto-chain: when sit-down one-shot finishes → enter loop
uint32_t curId = 0 ; float curT = 0.0f , curDur = 0.0f ;
if ( characterRenderer - > getAnimationState ( characterInstanceId , curId , curT , curDur ) ) {
// Renderer auto-returns one-shots to STAND — detect that OR normal completion
if ( curId ! = sitDownAnim_ | | ( curDur > 0.1f & & curT > = curDur - 0.05f ) ) {
newState = CharAnimState : : SITTING ;
}
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
break ;
case CharAnimState : : SITTING :
if ( swim ) {
newState = CharAnimState : : SWIM_IDLE ;
} else if ( ! sitting ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
if ( sitUpAnim_ ! = 0 & & ! moving ) {
newState = CharAnimState : : SIT_UP ;
} else {
newState = CharAnimState : : IDLE ;
}
}
break ;
case CharAnimState : : SIT_UP :
if ( swim ) {
newState = CharAnimState : : SWIM_IDLE ;
} else if ( moving ) {
// Movement cancels exit animation
newState = sprinting ? CharAnimState : : RUN : CharAnimState : : WALK ;
} else if ( characterRenderer & & characterInstanceId > 0 ) {
uint32_t curId = 0 ; float curT = 0.0f , curDur = 0.0f ;
if ( characterRenderer - > getAnimationState ( characterInstanceId , curId , curT , curDur ) ) {
// Renderer auto-returns one-shots to STAND — detect that OR normal completion
if ( curId ! = ( sitUpAnim_ ? sitUpAnim_ : anim : : SIT_GROUND_UP )
| | ( curDur > 0.1f & & curT > = curDur - 0.05f ) ) {
newState = CharAnimState : : IDLE ;
}
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
break ;
case CharAnimState : : EMOTE :
if ( swim ) {
cancelEmote ( ) ;
newState = CharAnimState : : SWIM_IDLE ;
} else if ( jumping | | ! grounded ) {
cancelEmote ( ) ;
newState = CharAnimState : : JUMP_START ;
} else if ( moving ) {
cancelEmote ( ) ;
newState = sprinting ? CharAnimState : : RUN : CharAnimState : : WALK ;
} else if ( sitting ) {
cancelEmote ( ) ;
newState = CharAnimState : : SIT_DOWN ;
} else if ( ! emoteLoop_ & & characterRenderer & & characterInstanceId > 0 ) {
uint32_t curId = 0 ; float curT = 0.0f , curDur = 0.0f ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
if ( characterRenderer - > getAnimationState ( characterInstanceId , curId , curT , curDur ) ) {
// Renderer auto-returns one-shots to STAND — detect that OR normal completion
if ( curId ! = emoteAnimId_ | | ( curDur > 0.1f & & curT > = curDur - 0.05f ) ) {
cancelEmote ( ) ;
newState = CharAnimState : : IDLE ;
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
}
break ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
case CharAnimState : : LOOTING :
// Cancel loot animation on movement, jump, swim, combat
if ( swim ) {
stopLooting ( ) ;
newState = CharAnimState : : SWIM_IDLE ;
} else if ( jumping | | ! grounded ) {
stopLooting ( ) ;
newState = CharAnimState : : JUMP_START ;
} else if ( moving ) {
stopLooting ( ) ;
newState = sprinting ? CharAnimState : : RUN : CharAnimState : : WALK ;
}
break ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
case CharAnimState : : SWIM_IDLE :
if ( ! swim ) {
newState = moving ? CharAnimState : : WALK : CharAnimState : : IDLE ;
} else if ( moving ) {
newState = CharAnimState : : SWIM ;
}
break ;
case CharAnimState : : SWIM :
if ( ! swim ) {
newState = moving ? CharAnimState : : WALK : CharAnimState : : IDLE ;
} else if ( ! moving ) {
newState = CharAnimState : : SWIM_IDLE ;
}
break ;
case CharAnimState : : MELEE_SWING :
if ( swim ) {
newState = CharAnimState : : SWIM_IDLE ;
} else if ( ! grounded & & jumping ) {
newState = CharAnimState : : JUMP_START ;
} else if ( ! grounded ) {
newState = CharAnimState : : JUMP_MID ;
} else if ( moving & & sprinting ) {
newState = CharAnimState : : RUN ;
} else if ( moving ) {
newState = CharAnimState : : WALK ;
} else if ( sitting ) {
newState = CharAnimState : : SIT_DOWN ;
} else if ( inCombat_ ) {
newState = CharAnimState : : COMBAT_IDLE ;
} else {
newState = CharAnimState : : IDLE ;
}
break ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
case CharAnimState : : RANGED_SHOOT :
if ( swim ) {
newState = CharAnimState : : SWIM_IDLE ;
} else if ( ! grounded & & jumping ) {
newState = CharAnimState : : JUMP_START ;
} else if ( ! grounded ) {
newState = CharAnimState : : JUMP_MID ;
} else if ( moving & & sprinting ) {
newState = CharAnimState : : RUN ;
} else if ( moving ) {
newState = CharAnimState : : WALK ;
} else if ( inCombat_ ) {
newState = CharAnimState : : RANGED_LOAD ;
} else {
newState = CharAnimState : : IDLE ;
}
break ;
case CharAnimState : : RANGED_LOAD :
if ( swim ) {
newState = CharAnimState : : SWIM_IDLE ;
} else if ( ! grounded & & jumping ) {
newState = CharAnimState : : JUMP_START ;
} else if ( ! grounded ) {
newState = CharAnimState : : JUMP_MID ;
} else if ( moving & & sprinting ) {
newState = CharAnimState : : RUN ;
} else if ( moving ) {
newState = CharAnimState : : WALK ;
} else if ( inCombat_ ) {
newState = CharAnimState : : COMBAT_IDLE ;
} else {
newState = CharAnimState : : IDLE ;
}
break ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
case CharAnimState : : MOUNT :
if ( swim ) {
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( sitting & & grounded ) {
newState = CharAnimState : : SIT_DOWN ;
} else if ( ! grounded & & jumping ) {
newState = CharAnimState : : JUMP_START ;
} else if ( ! grounded ) {
newState = CharAnimState : : JUMP_MID ;
} else if ( moving & & sprinting ) {
newState = CharAnimState : : RUN ;
} else if ( moving ) {
newState = CharAnimState : : WALK ;
} else {
newState = CharAnimState : : IDLE ;
}
break ;
case CharAnimState : : COMBAT_IDLE :
if ( swim ) {
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( ! grounded & & jumping ) {
newState = CharAnimState : : JUMP_START ;
} else if ( ! grounded ) {
newState = CharAnimState : : JUMP_MID ;
} else if ( moving & & sprinting ) {
newState = CharAnimState : : RUN ;
} else if ( moving ) {
newState = CharAnimState : : WALK ;
} else if ( ! inCombat_ ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
// Play sheathe one-shot before returning to idle
if ( characterRenderer & & characterInstanceId > 0 & &
characterRenderer - > hasAnimation ( characterInstanceId , anim : : SHEATHE ) ) {
newState = CharAnimState : : SHEATHE ;
} else {
newState = CharAnimState : : IDLE ;
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
break ;
case CharAnimState : : CHARGE :
break ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
case CharAnimState : : UNSHEATHE :
// One-shot weapon draw: when complete → COMBAT_IDLE
if ( swim ) {
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( moving ) {
newState = inCombat_ ? ( sprinting ? CharAnimState : : RUN : CharAnimState : : WALK )
: ( sprinting ? CharAnimState : : RUN : CharAnimState : : WALK ) ;
} else if ( characterRenderer & & characterInstanceId > 0 ) {
uint32_t curId = 0 ; float curT = 0.0f , curDur = 0.0f ;
if ( characterRenderer - > getAnimationState ( characterInstanceId , curId , curT , curDur ) ) {
if ( curId ! = anim : : UNSHEATHE | | ( curDur > 0.1f & & curT > = curDur - 0.05f ) ) {
newState = CharAnimState : : COMBAT_IDLE ;
}
}
}
break ;
case CharAnimState : : SHEATHE :
// One-shot weapon put-away: when complete → IDLE
if ( swim ) {
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( moving ) {
newState = sprinting ? CharAnimState : : RUN : CharAnimState : : WALK ;
} else if ( inCombat_ ) {
// Re-entered combat during sheathe — go straight to combat idle
newState = CharAnimState : : COMBAT_IDLE ;
} else if ( characterRenderer & & characterInstanceId > 0 ) {
uint32_t curId = 0 ; float curT = 0.0f , curDur = 0.0f ;
if ( characterRenderer - > getAnimationState ( characterInstanceId , curId , curT , curDur ) ) {
if ( curId ! = anim : : SHEATHE | | ( curDur > 0.1f & & curT > = curDur - 0.05f ) ) {
newState = CharAnimState : : IDLE ;
}
}
}
break ;
case CharAnimState : : SPELL_PRECAST :
// One-shot wind-up: auto-advance to SPELL_CASTING when complete
if ( swim ) {
spellPrecastAnimId_ = 0 ; spellCastAnimId_ = 0 ; spellFinalizeAnimId_ = 0 ;
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( ! grounded & & jumping ) {
spellPrecastAnimId_ = 0 ; spellCastAnimId_ = 0 ; spellFinalizeAnimId_ = 0 ;
newState = CharAnimState : : JUMP_START ;
} else if ( ! grounded ) {
spellPrecastAnimId_ = 0 ; spellCastAnimId_ = 0 ; spellFinalizeAnimId_ = 0 ;
newState = CharAnimState : : JUMP_MID ;
} else if ( characterRenderer & & characterInstanceId > 0 ) {
uint32_t curId = 0 ; float curT = 0.0f , curDur = 0.0f ;
uint32_t expectedAnim = spellPrecastAnimId_ ? spellPrecastAnimId_ : anim : : SPELL_PRECAST ;
if ( characterRenderer - > getAnimationState ( characterInstanceId , curId , curT , curDur ) ) {
if ( curId ! = expectedAnim | | ( curDur > 0.1f & & curT > = curDur - 0.05f ) ) {
// Precast finished → advance to casting phase
newState = CharAnimState : : SPELL_CASTING ;
}
}
}
break ;
case CharAnimState : : SPELL_CASTING :
// Spell cast loop holds until interrupted by movement, jump, swim, or stopSpellCast()
if ( swim ) {
spellPrecastAnimId_ = 0 ; spellCastAnimId_ = 0 ; spellFinalizeAnimId_ = 0 ;
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( ! grounded & & jumping ) {
spellPrecastAnimId_ = 0 ; spellCastAnimId_ = 0 ; spellFinalizeAnimId_ = 0 ;
newState = CharAnimState : : JUMP_START ;
} else if ( ! grounded ) {
spellPrecastAnimId_ = 0 ; spellCastAnimId_ = 0 ; spellFinalizeAnimId_ = 0 ;
newState = CharAnimState : : JUMP_MID ;
} else if ( moving ) {
spellPrecastAnimId_ = 0 ; spellCastAnimId_ = 0 ; spellFinalizeAnimId_ = 0 ;
newState = sprinting ? CharAnimState : : RUN : CharAnimState : : WALK ;
}
// Looping cast stays until stopSpellCast() is called externally
break ;
case CharAnimState : : SPELL_FINALIZE : {
// One-shot release: play finalize anim completely, then return to idle
if ( swim ) {
spellPrecastAnimId_ = 0 ; spellCastAnimId_ = 0 ; spellFinalizeAnimId_ = 0 ;
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( ! grounded & & jumping ) {
spellPrecastAnimId_ = 0 ; spellCastAnimId_ = 0 ; spellFinalizeAnimId_ = 0 ;
newState = CharAnimState : : JUMP_START ;
} else if ( characterRenderer & & characterInstanceId > 0 ) {
uint32_t curId = 0 ; float curT = 0.0f , curDur = 0.0f ;
// Determine which animation we expect to be playing
uint32_t expectedAnim = spellFinalizeAnimId_ ? spellFinalizeAnimId_
: ( spellCastAnimId_ ? spellCastAnimId_ : anim : : SPELL ) ;
if ( characterRenderer - > getAnimationState ( characterInstanceId , curId , curT , curDur ) ) {
if ( curId ! = expectedAnim | | ( curDur > 0.1f & & curT > = curDur - 0.05f ) ) {
// Finalization complete → return to idle
spellPrecastAnimId_ = 0 ;
spellCastAnimId_ = 0 ;
spellFinalizeAnimId_ = 0 ;
newState = inCombat_ ? CharAnimState : : COMBAT_IDLE : CharAnimState : : IDLE ;
}
}
}
break ;
}
case CharAnimState : : HIT_REACTION :
// One-shot reaction: exit when animation finishes
if ( swim ) {
hitReactionAnimId_ = 0 ;
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
} else if ( moving ) {
hitReactionAnimId_ = 0 ;
newState = sprinting ? CharAnimState : : RUN : CharAnimState : : WALK ;
} else if ( characterRenderer & & characterInstanceId > 0 ) {
uint32_t curId = 0 ; float curT = 0.0f , curDur = 0.0f ;
uint32_t expectedHitAnim = hitReactionAnimId_ ? hitReactionAnimId_ : anim : : COMBAT_WOUND ;
if ( characterRenderer - > getAnimationState ( characterInstanceId , curId , curT , curDur ) ) {
// Renderer auto-returns one-shots to STAND — detect that OR normal completion
if ( curId ! = expectedHitAnim | | ( curDur > 0.1f & & curT > = curDur - 0.05f ) ) {
hitReactionAnimId_ = 0 ;
newState = inCombat_ ? CharAnimState : : COMBAT_IDLE : CharAnimState : : IDLE ;
}
}
}
break ;
case CharAnimState : : STUNNED :
// Stun holds until setStunned(false) is called.
// Only swim can break it (physics override).
if ( swim ) {
stunned_ = false ;
newState = moving ? CharAnimState : : SWIM : CharAnimState : : SWIM_IDLE ;
}
break ;
}
// Stun overrides melee/charge (can't act while stunned)
if ( stunned_ & & newState ! = CharAnimState : : SWIM & & newState ! = CharAnimState : : SWIM_IDLE
& & newState ! = CharAnimState : : STUNNED ) {
newState = CharAnimState : : STUNNED ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
if ( forceMelee & & ! stunned_ ) {
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
newState = CharAnimState : : MELEE_SWING ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
spellPrecastAnimId_ = 0 ;
spellCastAnimId_ = 0 ;
spellFinalizeAnimId_ = 0 ;
hitReactionAnimId_ = 0 ;
}
if ( forceRanged & & ! stunned_ & & ! forceMelee ) {
newState = CharAnimState : : RANGED_SHOOT ;
spellPrecastAnimId_ = 0 ;
spellCastAnimId_ = 0 ;
spellFinalizeAnimId_ = 0 ;
hitReactionAnimId_ = 0 ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
if ( charging_ & & ! stunned_ ) {
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
newState = CharAnimState : : CHARGE ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
spellPrecastAnimId_ = 0 ;
spellCastAnimId_ = 0 ;
spellFinalizeAnimId_ = 0 ;
hitReactionAnimId_ = 0 ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
if ( newState ! = charAnimState_ ) {
charAnimState_ = newState ;
}
auto pickFirstAvailable = [ & ] ( std : : initializer_list < uint32_t > candidates , uint32_t fallback ) - > uint32_t {
for ( uint32_t id : candidates ) {
if ( characterRenderer - > hasAnimation ( characterInstanceId , id ) ) {
return id ;
}
}
return fallback ;
} ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
uint32_t animId = anim : : STAND ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
bool loop = true ;
switch ( charAnimState_ ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
case CharAnimState : : IDLE :
if ( lowHealth_ & & characterRenderer - > hasAnimation ( characterInstanceId , anim : : STAND_WOUND ) ) {
animId = anim : : STAND_WOUND ;
} else {
animId = anim : : STAND ;
}
loop = true ;
break ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
case CharAnimState : : WALK :
if ( movingBackward ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
animId = pickFirstAvailable ( { anim : : WALK_BACKWARDS } , anim : : WALK ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
} else if ( anyStrafeLeft ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
animId = pickFirstAvailable ( { anim : : SHUFFLE_LEFT , anim : : RUN_LEFT } , anim : : WALK ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
} else if ( anyStrafeRight ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
animId = pickFirstAvailable ( { anim : : SHUFFLE_RIGHT , anim : : RUN_RIGHT } , anim : : WALK ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
} else {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
animId = pickFirstAvailable ( { anim : : WALK , anim : : RUN } , anim : : STAND ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
loop = true ;
break ;
case CharAnimState : : RUN :
if ( movingBackward ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
animId = pickFirstAvailable ( { anim : : WALK_BACKWARDS } , anim : : WALK ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
} else if ( anyStrafeLeft ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
animId = pickFirstAvailable ( { anim : : RUN_LEFT } , anim : : RUN ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
} else if ( anyStrafeRight ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
animId = pickFirstAvailable ( { anim : : RUN_RIGHT } , anim : : RUN ) ;
} else if ( sprintAuraActive_ ) {
animId = pickFirstAvailable ( { anim : : SPRINT , anim : : RUN , anim : : WALK } , anim : : STAND ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
} else {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
animId = pickFirstAvailable ( { anim : : RUN , anim : : WALK } , anim : : STAND ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
loop = true ;
break ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
case CharAnimState : : JUMP_START : animId = anim : : JUMP_START ; loop = false ; break ;
case CharAnimState : : JUMP_MID : animId = anim : : JUMP ; loop = false ; break ;
case CharAnimState : : JUMP_END : animId = anim : : JUMP_END ; loop = false ; break ;
case CharAnimState : : SIT_DOWN :
animId = sitDownAnim_ ? sitDownAnim_ : anim : : SIT_GROUND_DOWN ;
loop = false ;
break ;
case CharAnimState : : SITTING :
animId = sitLoopAnim_ ? sitLoopAnim_ : anim : : SITTING ;
loop = true ;
break ;
case CharAnimState : : SIT_UP :
animId = sitUpAnim_ ? sitUpAnim_ : anim : : SIT_GROUND_UP ;
loop = false ;
break ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
case CharAnimState : : EMOTE : animId = emoteAnimId_ ; loop = emoteLoop_ ; break ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
case CharAnimState : : LOOTING : animId = anim : : LOOT ; loop = true ; break ;
case CharAnimState : : SWIM_IDLE : animId = anim : : SWIM_IDLE ; loop = true ; break ;
case CharAnimState : : SWIM :
if ( movingBackward ) {
animId = pickFirstAvailable ( { anim : : SWIM_BACKWARDS } , anim : : SWIM ) ;
} else if ( anyStrafeLeft ) {
animId = pickFirstAvailable ( { anim : : SWIM_LEFT } , anim : : SWIM ) ;
} else if ( anyStrafeRight ) {
animId = pickFirstAvailable ( { anim : : SWIM_RIGHT } , anim : : SWIM ) ;
} else {
animId = anim : : SWIM ;
}
loop = true ;
break ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
case CharAnimState : : MELEE_SWING :
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
if ( specialAttackAnimId_ ! = 0 ) {
animId = specialAttackAnimId_ ;
} else {
animId = resolveMeleeAnimId ( ) ;
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
if ( animId = = 0 ) {
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
animId = anim : : STAND ;
}
loop = false ;
break ;
case CharAnimState : : RANGED_SHOOT :
animId = rangedAnimId_ ? rangedAnimId_ : anim : : ATTACK_BOW ;
loop = false ;
break ;
case CharAnimState : : RANGED_LOAD :
switch ( equippedRangedType_ ) {
case RangedWeaponType : : BOW :
animId = pickFirstAvailable ( { anim : : LOAD_BOW } , anim : : STAND ) ; break ;
case RangedWeaponType : : GUN :
animId = pickFirstAvailable ( { anim : : LOAD_RIFLE } , anim : : STAND ) ; break ;
case RangedWeaponType : : CROSSBOW :
animId = pickFirstAvailable ( { anim : : LOAD_BOW } , anim : : STAND ) ; break ;
default :
animId = anim : : STAND ; break ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
}
loop = false ;
break ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
case CharAnimState : : MOUNT : animId = anim : : MOUNT ; loop = true ; break ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
case CharAnimState : : COMBAT_IDLE :
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
// Wounded idle overrides combat stance when HP < 20%
if ( lowHealth_ & & characterRenderer - > hasAnimation ( characterInstanceId , anim : : STAND_WOUND ) ) {
animId = anim : : STAND_WOUND ;
} else if ( equippedRangedType_ = = RangedWeaponType : : BOW ) {
animId = pickFirstAvailable (
{ anim : : READY_BOW , anim : : READY_1H , anim : : READY_UNARMED } ,
anim : : STAND ) ;
} else if ( equippedRangedType_ = = RangedWeaponType : : GUN ) {
animId = pickFirstAvailable (
{ anim : : READY_RIFLE , anim : : READY_BOW , anim : : READY_1H , anim : : READY_UNARMED } ,
anim : : STAND ) ;
} else if ( equippedRangedType_ = = RangedWeaponType : : CROSSBOW ) {
animId = pickFirstAvailable (
{ anim : : READY_CROSSBOW , anim : : READY_BOW , anim : : READY_1H , anim : : READY_UNARMED } ,
anim : : STAND ) ;
} else if ( equippedRangedType_ = = RangedWeaponType : : THROWN ) {
animId = pickFirstAvailable (
{ anim : : READY_THROWN , anim : : READY_1H , anim : : READY_UNARMED } ,
anim : : STAND ) ;
} else if ( equippedIs2HLoose_ ) {
animId = pickFirstAvailable (
{ anim : : READY_2H_LOOSE , anim : : READY_2H , anim : : READY_1H , anim : : READY_UNARMED } ,
anim : : STAND ) ;
} else if ( equippedWeaponInvType_ = = game : : InvType : : TWO_HAND ) {
animId = pickFirstAvailable (
{ anim : : READY_2H , anim : : READY_2H_LOOSE , anim : : READY_1H , anim : : READY_UNARMED } ,
anim : : STAND ) ;
} else if ( equippedIsFist_ ) {
animId = pickFirstAvailable (
{ anim : : READY_FIST_1H , anim : : READY_FIST , anim : : READY_1H , anim : : READY_UNARMED } ,
anim : : STAND ) ;
} else if ( equippedWeaponInvType_ = = game : : InvType : : NON_EQUIP ) {
animId = pickFirstAvailable (
{ anim : : READY_UNARMED , anim : : READY_1H , anim : : READY_FIST } ,
anim : : STAND ) ;
} else {
// 1H (inventoryType 13, 21, etc.)
animId = pickFirstAvailable (
{ anim : : READY_1H , anim : : READY_2H , anim : : READY_UNARMED } ,
anim : : STAND ) ;
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
loop = true ;
break ;
case CharAnimState : : CHARGE :
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
animId = anim : : RUN ;
loop = true ;
break ;
case CharAnimState : : UNSHEATHE :
animId = anim : : UNSHEATHE ;
loop = false ;
break ;
case CharAnimState : : SHEATHE :
animId = pickFirstAvailable ( { anim : : SHEATHE , anim : : HIP_SHEATHE } , anim : : SHEATHE ) ;
loop = false ;
break ;
case CharAnimState : : SPELL_PRECAST :
animId = spellPrecastAnimId_ ? spellPrecastAnimId_ : anim : : SPELL_PRECAST ;
loop = false ; // One-shot wind-up
break ;
case CharAnimState : : SPELL_CASTING :
animId = spellCastAnimId_ ? spellCastAnimId_ : anim : : SPELL ;
loop = spellCastLoop_ ;
break ;
case CharAnimState : : SPELL_FINALIZE :
// Play finalization anim if set, otherwise let the cast anim finish as one-shot
animId = spellFinalizeAnimId_ ? spellFinalizeAnimId_
: ( spellCastAnimId_ ? spellCastAnimId_ : anim : : SPELL ) ;
loop = false ; // One-shot release
break ;
case CharAnimState : : HIT_REACTION :
animId = hitReactionAnimId_ ? hitReactionAnimId_ : anim : : COMBAT_WOUND ;
loop = false ;
break ;
case CharAnimState : : STUNNED :
animId = anim : : STUN ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
loop = true ;
break ;
}
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
// Stealth animation substitution: override idle/walk/run with stealth variants
if ( stealthed_ ) {
if ( charAnimState_ = = CharAnimState : : IDLE | | charAnimState_ = = CharAnimState : : COMBAT_IDLE ) {
animId = pickFirstAvailable ( { anim : : STEALTH_STAND } , animId ) ;
} else if ( charAnimState_ = = CharAnimState : : WALK ) {
animId = pickFirstAvailable ( { anim : : STEALTH_WALK } , animId ) ;
} else if ( charAnimState_ = = CharAnimState : : RUN ) {
animId = pickFirstAvailable ( { anim : : STEALTH_RUN , anim : : STEALTH_WALK } , animId ) ;
}
}
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
uint32_t currentAnimId = 0 ;
float currentAnimTimeMs = 0.0f ;
float currentAnimDurationMs = 0.0f ;
bool haveState = characterRenderer - > getAnimationState ( characterInstanceId , currentAnimId , currentAnimTimeMs , currentAnimDurationMs ) ;
const bool requestChanged = ( lastPlayerAnimRequest_ ! = animId ) | | ( lastPlayerAnimLoopRequest_ ! = loop ) ;
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
// requestChanged alone is sufficient: covers both anim ID changes AND loop-mode
// changes on the same anim (e.g. spell cast loop → finalization one-shot).
// The currentAnimId check handles engine drift (fallback anim playing instead).
const bool shouldPlay = requestChanged | | ( haveState & & currentAnimId ! = animId ) ;
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
if ( shouldPlay ) {
characterRenderer - > playAnimation ( characterInstanceId , animId , loop ) ;
lastPlayerAnimRequest_ = animId ;
lastPlayerAnimLoopRequest_ = loop ;
}
}
// ── Footstep event detection ─────────────────────────────────────────────────
bool AnimationController : : shouldTriggerFootstepEvent ( uint32_t animationId , float animationTimeMs , float animationDurationMs ) {
if ( animationDurationMs < = 1.0f ) {
footstepNormInitialized_ = false ;
return false ;
}
float wrappedTime = animationTimeMs ;
while ( wrappedTime > = animationDurationMs ) {
wrappedTime - = animationDurationMs ;
}
if ( wrappedTime < 0.0f ) wrappedTime + = animationDurationMs ;
float norm = wrappedTime / animationDurationMs ;
if ( animationId ! = footstepLastAnimationId_ ) {
footstepLastAnimationId_ = animationId ;
footstepLastNormTime_ = norm ;
footstepNormInitialized_ = true ;
return false ;
}
if ( ! footstepNormInitialized_ ) {
footstepNormInitialized_ = true ;
footstepLastNormTime_ = norm ;
return false ;
}
auto crossed = [ & ] ( float eventNorm ) {
if ( footstepLastNormTime_ < = norm ) {
return footstepLastNormTime_ < eventNorm & & eventNorm < = norm ;
}
return footstepLastNormTime_ < eventNorm | | eventNorm < = norm ;
} ;
bool trigger = crossed ( 0.22f ) | | crossed ( 0.72f ) ;
footstepLastNormTime_ = norm ;
return trigger ;
}
audio : : FootstepSurface AnimationController : : resolveFootstepSurface ( ) const {
auto * cameraController = renderer_ - > getCameraController ( ) ;
if ( ! cameraController | | ! cameraController - > isThirdPerson ( ) ) {
return audio : : FootstepSurface : : STONE ;
}
const glm : : vec3 & p = renderer_ - > getCharacterPosition ( ) ;
float distSq = glm : : dot ( p - cachedFootstepPosition_ , p - cachedFootstepPosition_ ) ;
if ( distSq < 2.25f & & cachedFootstepUpdateTimer_ < 0.5f ) {
return cachedFootstepSurface_ ;
}
cachedFootstepPosition_ = p ;
cachedFootstepUpdateTimer_ = 0.0f ;
if ( cameraController - > isSwimming ( ) ) {
cachedFootstepSurface_ = audio : : FootstepSurface : : WATER ;
return audio : : FootstepSurface : : WATER ;
}
auto * waterRenderer = renderer_ - > getWaterRenderer ( ) ;
if ( waterRenderer ) {
auto waterH = waterRenderer - > getWaterHeightAt ( p . x , p . y ) ;
if ( waterH & & p . z < ( * waterH + 0.25f ) ) {
cachedFootstepSurface_ = audio : : FootstepSurface : : WATER ;
return audio : : FootstepSurface : : WATER ;
}
}
auto * wmoRenderer = renderer_ - > getWMORenderer ( ) ;
auto * terrainManager = renderer_ - > getTerrainManager ( ) ;
if ( wmoRenderer ) {
auto wmoFloor = wmoRenderer - > getFloorHeight ( p . x , p . y , p . z + 1.5f ) ;
auto terrainFloor = terrainManager ? terrainManager - > getHeightAt ( p . x , p . y ) : std : : nullopt ;
if ( wmoFloor & & ( ! terrainFloor | | * wmoFloor > = * terrainFloor - 0.1f ) ) {
cachedFootstepSurface_ = audio : : FootstepSurface : : STONE ;
return audio : : FootstepSurface : : STONE ;
}
}
audio : : FootstepSurface surface = audio : : FootstepSurface : : STONE ;
if ( terrainManager ) {
auto texture = terrainManager - > getDominantTextureAt ( p . x , p . y ) ;
if ( texture ) {
std : : string t = * texture ;
for ( char & c : t ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
if ( t . find ( " snow " ) ! = std : : string : : npos | | t . find ( " ice " ) ! = std : : string : : npos ) surface = audio : : FootstepSurface : : SNOW ;
else if ( t . find ( " grass " ) ! = std : : string : : npos | | t . find ( " moss " ) ! = std : : string : : npos | | t . find ( " leaf " ) ! = std : : string : : npos ) surface = audio : : FootstepSurface : : GRASS ;
else if ( t . find ( " sand " ) ! = std : : string : : npos | | t . find ( " dirt " ) ! = std : : string : : npos | | t . find ( " mud " ) ! = std : : string : : npos ) surface = audio : : FootstepSurface : : DIRT ;
else if ( t . find ( " wood " ) ! = std : : string : : npos | | t . find ( " timber " ) ! = std : : string : : npos ) surface = audio : : FootstepSurface : : WOOD ;
else if ( t . find ( " metal " ) ! = std : : string : : npos | | t . find ( " iron " ) ! = std : : string : : npos ) surface = audio : : FootstepSurface : : METAL ;
else if ( t . find ( " stone " ) ! = std : : string : : npos | | t . find ( " rock " ) ! = std : : string : : npos | | t . find ( " cobble " ) ! = std : : string : : npos | | t . find ( " brick " ) ! = std : : string : : npos ) surface = audio : : FootstepSurface : : STONE ;
}
}
cachedFootstepSurface_ = surface ;
return surface ;
}
// ── Footstep update (called from Renderer::update) ──────────────────────────
void AnimationController : : updateFootsteps ( float deltaTime ) {
auto * footstepManager = renderer_ - > getAudioCoordinator ( ) - > getFootstepManager ( ) ;
if ( ! footstepManager ) return ;
auto * characterRenderer = renderer_ - > getCharacterRenderer ( ) ;
auto * cameraController = renderer_ - > getCameraController ( ) ;
uint32_t characterInstanceId = renderer_ - > getCharacterInstanceId ( ) ;
footstepManager - > update ( deltaTime ) ;
cachedFootstepUpdateTimer_ + = deltaTime ;
bool canPlayFootsteps = characterRenderer & & characterInstanceId > 0 & &
cameraController & & cameraController - > isThirdPerson ( ) & &
cameraController - > isGrounded ( ) & & ! cameraController - > isSwimming ( ) ;
if ( canPlayFootsteps & & isMounted ( ) & & mountInstanceId_ > 0 & & ! taxiFlight_ ) {
// Mount footsteps: use mount's animation for timing
uint32_t animId = 0 ;
float animTimeMs = 0.0f , animDurationMs = 0.0f ;
if ( characterRenderer - > getAnimationState ( mountInstanceId_ , animId , animTimeMs , animDurationMs ) & &
animDurationMs > 1.0f & & cameraController - > isMoving ( ) ) {
float wrappedTime = animTimeMs ;
while ( wrappedTime > = animDurationMs ) {
wrappedTime - = animDurationMs ;
}
if ( wrappedTime < 0.0f ) wrappedTime + = animDurationMs ;
float norm = wrappedTime / animDurationMs ;
if ( animId ! = mountFootstepLastAnimId_ ) {
mountFootstepLastAnimId_ = animId ;
mountFootstepLastNormTime_ = norm ;
mountFootstepNormInitialized_ = true ;
} else if ( ! mountFootstepNormInitialized_ ) {
mountFootstepNormInitialized_ = true ;
mountFootstepLastNormTime_ = norm ;
} else {
auto crossed = [ & ] ( float eventNorm ) {
if ( mountFootstepLastNormTime_ < = norm ) {
return mountFootstepLastNormTime_ < eventNorm & & eventNorm < = norm ;
}
return mountFootstepLastNormTime_ < eventNorm | | eventNorm < = norm ;
} ;
if ( crossed ( 0.25f ) | | crossed ( 0.75f ) ) {
footstepManager - > playFootstep ( resolveFootstepSurface ( ) , true ) ;
}
mountFootstepLastNormTime_ = norm ;
}
} else {
mountFootstepNormInitialized_ = false ;
}
footstepNormInitialized_ = false ;
} else if ( canPlayFootsteps & & isFootstepAnimationState ( ) ) {
uint32_t animId = 0 ;
float animTimeMs = 0.0f ;
float animDurationMs = 0.0f ;
if ( characterRenderer - > getAnimationState ( characterInstanceId , animId , animTimeMs , animDurationMs ) & &
shouldTriggerFootstepEvent ( animId , animTimeMs , animDurationMs ) ) {
auto surface = resolveFootstepSurface ( ) ;
footstepManager - > playFootstep ( surface , cameraController - > isSprinting ( ) ) ;
if ( surface = = audio : : FootstepSurface : : WATER ) {
if ( renderer_ - > getAudioCoordinator ( ) - > getMovementSoundManager ( ) ) {
renderer_ - > getAudioCoordinator ( ) - > getMovementSoundManager ( ) - > playWaterFootstep ( audio : : MovementSoundManager : : CharacterSize : : MEDIUM ) ;
}
auto * swimEffects = renderer_ - > getSwimEffects ( ) ;
auto * waterRenderer = renderer_ - > getWaterRenderer ( ) ;
if ( swimEffects & & waterRenderer ) {
const glm : : vec3 & characterPosition = renderer_ - > getCharacterPosition ( ) ;
auto wh = waterRenderer - > getWaterHeightAt ( characterPosition . x , characterPosition . y ) ;
if ( wh ) {
swimEffects - > spawnFootSplash ( characterPosition , * wh ) ;
}
}
}
}
mountFootstepNormInitialized_ = false ;
} else {
footstepNormInitialized_ = false ;
mountFootstepNormInitialized_ = false ;
}
}
// ── Activity SFX state tracking ──────────────────────────────────────────────
void AnimationController : : updateSfxState ( float deltaTime ) {
auto * activitySoundManager = renderer_ - > getAudioCoordinator ( ) - > getActivitySoundManager ( ) ;
if ( ! activitySoundManager ) return ;
auto * cameraController = renderer_ - > getCameraController ( ) ;
activitySoundManager - > update ( deltaTime ) ;
if ( cameraController & & cameraController - > isThirdPerson ( ) ) {
bool grounded = cameraController - > isGrounded ( ) ;
bool jumping = cameraController - > isJumping ( ) ;
bool falling = cameraController - > isFalling ( ) ;
bool swimming = cameraController - > isSwimming ( ) ;
bool moving = cameraController - > isMoving ( ) ;
if ( ! sfxStateInitialized_ ) {
sfxPrevGrounded_ = grounded ;
sfxPrevJumping_ = jumping ;
sfxPrevFalling_ = falling ;
sfxPrevSwimming_ = swimming ;
sfxStateInitialized_ = true ;
}
if ( jumping & & ! sfxPrevJumping_ & & ! swimming ) {
activitySoundManager - > playJump ( ) ;
}
if ( grounded & & ! sfxPrevGrounded_ ) {
bool hardLanding = sfxPrevFalling_ ;
activitySoundManager - > playLanding ( resolveFootstepSurface ( ) , hardLanding ) ;
}
if ( swimming & & ! sfxPrevSwimming_ ) {
activitySoundManager - > playWaterEnter ( ) ;
} else if ( ! swimming & & sfxPrevSwimming_ ) {
activitySoundManager - > playWaterExit ( ) ;
}
activitySoundManager - > setSwimmingState ( swimming , moving ) ;
if ( renderer_ - > getAudioCoordinator ( ) - > getMusicManager ( ) ) {
renderer_ - > getAudioCoordinator ( ) - > getMusicManager ( ) - > setUnderwaterMode ( swimming ) ;
}
sfxPrevGrounded_ = grounded ;
sfxPrevJumping_ = jumping ;
sfxPrevFalling_ = falling ;
sfxPrevSwimming_ = swimming ;
} else {
activitySoundManager - > setSwimmingState ( false , false ) ;
if ( renderer_ - > getAudioCoordinator ( ) - > getMusicManager ( ) ) {
renderer_ - > getAudioCoordinator ( ) - > getMusicManager ( ) - > setUnderwaterMode ( false ) ;
}
sfxStateInitialized_ = false ;
}
// Mount ambient sounds
if ( renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) ) {
renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) - > update ( deltaTime ) ;
if ( cameraController & & isMounted ( ) ) {
bool isMoving = cameraController - > isMoving ( ) ;
bool flying = taxiFlight_ | | ! cameraController - > isGrounded ( ) ;
renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) - > setMoving ( isMoving ) ;
renderer_ - > getAudioCoordinator ( ) - > getMountSoundManager ( ) - > setFlying ( flying ) ;
}
}
}
} // namespace rendering
} // namespace wowee