diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 944c1e63..457f9870 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -637,6 +637,10 @@ public: using SpellCastAnimCallback = std::function; void setSpellCastAnimCallback(SpellCastAnimCallback cb) { spellCastAnimCallback_ = std::move(cb); } + // Unit animation hint: signal jump (animId=38) or swim (animId=42) for other players/NPCs + using UnitAnimHintCallback = std::function; + void setUnitAnimHintCallback(UnitAnimHintCallback cb) { unitAnimHintCallback_ = std::move(cb); } + // NPC swing callback (plays attack animation on NPC) using NpcSwingCallback = std::function; void setNpcSwingCallback(NpcSwingCallback cb) { npcSwingCallback_ = std::move(cb); } @@ -2263,6 +2267,7 @@ private: GhostStateCallback ghostStateCallback_; MeleeSwingCallback meleeSwingCallback_; SpellCastAnimCallback spellCastAnimCallback_; + UnitAnimHintCallback unitAnimHintCallback_; NpcSwingCallback npcSwingCallback_; NpcGreetingCallback npcGreetingCallback_; NpcFarewellCallback npcFarewellCallback_; diff --git a/src/core/application.cpp b/src/core/application.cpp index 00d4ef81..cb311c27 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -2771,6 +2771,29 @@ void Application::setupUICallbacks() { } }); + // Unit animation hint callback — play jump (38) or swim (42) on other players/NPCs + // when MSG_MOVE_JUMP or MSG_MOVE_START_SWIM arrives. The per-frame sync handles the + // return to Stand/Run once the unit lands or exits water. + gameHandler->setUnitAnimHintCallback([this](uint64_t guid, uint32_t animId) { + if (!renderer) return; + auto* cr = renderer->getCharacterRenderer(); + if (!cr) return; + uint32_t instanceId = 0; + { + auto it = playerInstances_.find(guid); + if (it != playerInstances_.end()) instanceId = it->second; + } + if (instanceId == 0) { + auto it = creatureInstances_.find(guid); + if (it != creatureInstances_.end()) instanceId = it->second; + } + if (instanceId == 0) return; + // Don't override Death animation (1) + uint32_t curAnim = 0; float curT = 0.0f, curDur = 0.0f; + if (cr->getAnimationState(instanceId, curAnim, curT, curDur) && curAnim == 1) return; + cr->playAnimation(instanceId, animId, /*loop=*/true); + }); + // Emote animation callback — play server-driven emote animations on NPCs and other players gameHandler->setEmoteAnimCallback([this](uint64_t guid, uint32_t emoteAnim) { if (!renderer || emoteAnim == 0) return; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 13a2f708..a1098fc8 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -12165,11 +12165,34 @@ void GameHandler::handleOtherPlayerMovement(network::Packet& packet) { } otherPlayerMoveTimeMs_[moverGuid] = info.time; - entity->startMoveTo(canonical.x, canonical.y, canonical.z, canYaw, durationMs / 1000.0f); + // Classify the opcode so we can drive the correct entity update and animation. + const uint16_t wireOp = packet.getOpcode(); + const bool isStopOpcode = + (wireOp == wireOpcode(Opcode::MSG_MOVE_STOP)) || + (wireOp == wireOpcode(Opcode::MSG_MOVE_STOP_STRAFE)) || + (wireOp == wireOpcode(Opcode::MSG_MOVE_STOP_TURN)) || + (wireOp == wireOpcode(Opcode::MSG_MOVE_STOP_SWIM)) || + (wireOp == wireOpcode(Opcode::MSG_MOVE_FALL_LAND)); + const bool isJumpOpcode = (wireOp == wireOpcode(Opcode::MSG_MOVE_JUMP)); + const bool isSwimOpcode = (wireOp == wireOpcode(Opcode::MSG_MOVE_START_SWIM)); - // Notify renderer + // For stop opcodes snap the entity position (duration=0) so it doesn't keep interpolating, + // and pass durationMs=0 to the renderer so the Run-anim flash is suppressed. + // The per-frame sync will detect no movement and play Stand on the next frame. + const float entityDuration = isStopOpcode ? 0.0f : (durationMs / 1000.0f); + entity->startMoveTo(canonical.x, canonical.y, canonical.z, canYaw, entityDuration); + + // Notify renderer of position change if (creatureMoveCallback_) { - creatureMoveCallback_(moverGuid, canonical.x, canonical.y, canonical.z, durationMs); + const uint32_t notifyDuration = isStopOpcode ? 0u : durationMs; + creatureMoveCallback_(moverGuid, canonical.x, canonical.y, canonical.z, notifyDuration); + } + + // Signal specific animation transitions that the per-frame sync can't detect reliably. + // WoW M2 animation IDs: 38=JumpMid (loops during airborne), 42=Swim + if (unitAnimHintCallback_) { + if (isJumpOpcode) unitAnimHintCallback_(moverGuid, 38u); + else if (isSwimOpcode) unitAnimHintCallback_(moverGuid, 42u); } }