From 9f3c236c48c868a6dd961b164284d03e860ee4f4 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 09:46:46 -0700 Subject: [PATCH] game/rendering: drive player stand-state animation from SMSG_STANDSTATE_UPDATE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add StandStateCallback to GameHandler, fired when the server confirms a stand state change (SMSG_STANDSTATE_UPDATE). Connect in Application to map the WoW stand state (0-8) to M2 animation IDs on the player character model: - 0 = Stand → anim 0 (Stand) - 1-6 = Sit variants → anim 27 (SitGround) - 7 = Dead → anim 1 (Death) - 8 = Kneel → anim 72 (Kneel) Sit and Kneel animations are looped so the held-pose frame stays visible; Death stays on the final frame. --- include/game/game_handler.hpp | 6 ++++++ src/core/application.cpp | 24 ++++++++++++++++++++++++ src/game/game_handler.cpp | 3 +++ 3 files changed, 33 insertions(+) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index d9e69992..3824dbf9 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -619,6 +619,11 @@ public: using NpcRespawnCallback = std::function; void setNpcRespawnCallback(NpcRespawnCallback cb) { npcRespawnCallback_ = std::move(cb); } + // Stand state animation callback — fired when SMSG_STANDSTATE_UPDATE confirms a new state + // standState: 0=stand, 1-6=sit variants, 7=dead, 8=kneel + using StandStateCallback = std::function; + void setStandStateCallback(StandStateCallback cb) { standStateCallback_ = std::move(cb); } + // Melee swing callback (for driving animation/SFX) using MeleeSwingCallback = std::function; void setMeleeSwingCallback(MeleeSwingCallback cb) { meleeSwingCallback_ = std::move(cb); } @@ -2250,6 +2255,7 @@ private: NpcDeathCallback npcDeathCallback_; NpcAggroCallback npcAggroCallback_; NpcRespawnCallback npcRespawnCallback_; + StandStateCallback standStateCallback_; MeleeSwingCallback meleeSwingCallback_; SpellCastAnimCallback spellCastAnimCallback_; NpcSwingCallback npcSwingCallback_; diff --git a/src/core/application.cpp b/src/core/application.cpp index a710d7e8..a3c12e98 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -2794,6 +2794,30 @@ void Application::setupUICallbacks() { } }); + // Stand state animation callback — map server stand state to M2 animation on player + gameHandler->setStandStateCallback([this](uint8_t standState) { + if (!renderer) return; + auto* cr = renderer->getCharacterRenderer(); + if (!cr) return; + uint32_t charInstId = renderer->getCharacterInstanceId(); + if (charInstId == 0) return; + // WoW stand state → M2 animation ID mapping + // 0=Stand→0, 1-6=Sit variants→27 (SitGround), 7=Dead→1, 8=Kneel→72 + uint32_t animId = 0; + if (standState == 0) { + animId = 0; // Stand + } else if (standState >= 1 && standState <= 6) { + animId = 27; // SitGround (covers sit-chair too; correct visual differs by chair height) + } else if (standState == 7) { + animId = 1; // Death + } else if (standState == 8) { + animId = 72; // Kneel + } + // Non-looping sit/kneel looks wrong frozen; loop them so the held-pose frame shows + const bool loop = (animId != 1); + cr->playAnimation(charInstId, animId, loop); + }); + // NPC greeting callback - play voice line gameHandler->setNpcGreetingCallback([this](uint64_t guid, const glm::vec3& position) { if (renderer && renderer->getNpcVoiceManager()) { diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 5aaeb021..eb459ebd 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -4253,6 +4253,9 @@ void GameHandler::handlePacket(network::Packet& packet) { LOG_INFO("Stand state updated: ", static_cast(standState_), " (", standState_ == 0 ? "stand" : standState_ == 1 ? "sit" : standState_ == 7 ? "dead" : standState_ == 8 ? "kneel" : "other", ")"); + if (standStateCallback_) { + standStateCallback_(standState_); + } } break; case Opcode::SMSG_NEW_TAXI_PATH: