From 100d66d18b0111d061ce3509b38b42afe1dab114 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 18 Mar 2026 08:43:19 -0700 Subject: [PATCH] fix: play death/attack animations for online players, not just NPCs Death, respawn, and melee swing callbacks only checked creatureInstances_, so online players never played death animation when killed, never returned to idle on resurrect, and never showed attack swings. Extended all three callbacks to also check playerInstances_. Also extended the game_handler death/respawn callback triggers to fire for PLAYER entities (not just UNIT), and added spawn-time death detection for players that are already dead when first seen. --- src/core/application.cpp | 39 ++++++++++++++++++++++++++++++--------- src/game/game_handler.cpp | 15 ++++++++++++--- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/core/application.cpp b/src/core/application.cpp index 45a11e95..78af06ab 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -2999,29 +2999,50 @@ void Application::setupUICallbacks() { } }); - // NPC death callback (online mode) - play death animation + // NPC/player death callback (online mode) - play death animation gameHandler->setNpcDeathCallback([this](uint64_t guid) { deadCreatureGuids_.insert(guid); + if (!renderer || !renderer->getCharacterRenderer()) return; + uint32_t instanceId = 0; auto it = creatureInstances_.find(guid); - if (it != creatureInstances_.end() && renderer && renderer->getCharacterRenderer()) { - renderer->getCharacterRenderer()->playAnimation(it->second, 1, false); // Death + if (it != creatureInstances_.end()) instanceId = it->second; + else { + auto pit = playerInstances_.find(guid); + if (pit != playerInstances_.end()) instanceId = pit->second; + } + if (instanceId != 0) { + renderer->getCharacterRenderer()->playAnimation(instanceId, 1, false); // Death } }); - // NPC respawn callback (online mode) - reset to idle animation + // NPC/player respawn callback (online mode) - reset to idle animation gameHandler->setNpcRespawnCallback([this](uint64_t guid) { deadCreatureGuids_.erase(guid); + if (!renderer || !renderer->getCharacterRenderer()) return; + uint32_t instanceId = 0; auto it = creatureInstances_.find(guid); - if (it != creatureInstances_.end() && renderer && renderer->getCharacterRenderer()) { - renderer->getCharacterRenderer()->playAnimation(it->second, 0, true); // Idle + if (it != creatureInstances_.end()) instanceId = it->second; + else { + auto pit = playerInstances_.find(guid); + if (pit != playerInstances_.end()) instanceId = pit->second; + } + if (instanceId != 0) { + renderer->getCharacterRenderer()->playAnimation(instanceId, 0, true); // Idle } }); - // NPC swing callback (online mode) - play attack animation + // NPC/player swing callback (online mode) - play attack animation gameHandler->setNpcSwingCallback([this](uint64_t guid) { + if (!renderer || !renderer->getCharacterRenderer()) return; + uint32_t instanceId = 0; auto it = creatureInstances_.find(guid); - if (it != creatureInstances_.end() && renderer && renderer->getCharacterRenderer()) { - renderer->getCharacterRenderer()->playAnimation(it->second, 16, false); // Attack + if (it != creatureInstances_.end()) instanceId = it->second; + else { + auto pit = playerInstances_.find(guid); + if (pit != playerInstances_.end()) instanceId = pit->second; + } + if (instanceId != 0) { + renderer->getCharacterRenderer()->playAnimation(instanceId, 16, false); // Attack } }); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 6db0e1fc..6e2452d5 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -11562,6 +11562,9 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem " displayId=", unit->getDisplayId(), " appearance extraction failed — model will not render"); } } + if (unitInitiallyDead && npcDeathCallback_) { + npcDeathCallback_(block.guid); + } } else if (creatureSpawnCallback_) { LOG_DEBUG("[Spawn] UNIT guid=0x", std::hex, block.guid, std::dec, " displayId=", unit->getDisplayId(), " at (", @@ -11908,7 +11911,7 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem corpseX_, ",", corpseY_, ",", corpseZ_, ") map=", corpseMapId_); } - if (entity->getType() == ObjectType::UNIT && npcDeathCallback_) { + if ((entity->getType() == ObjectType::UNIT || entity->getType() == ObjectType::PLAYER) && npcDeathCallback_) { npcDeathCallback_(block.guid); npcDeathNotified = true; } @@ -11921,7 +11924,7 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem LOG_INFO("Player entered ghost form"); } } - if (entity->getType() == ObjectType::UNIT && npcRespawnCallback_) { + if ((entity->getType() == ObjectType::UNIT || entity->getType() == ObjectType::PLAYER) && npcRespawnCallback_) { npcRespawnCallback_(block.guid); npcRespawnNotified = true; } @@ -11952,7 +11955,7 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem selfResAvailable_ = false; LOG_INFO("Player resurrected (dynamic flags)"); } - } else if (entity->getType() == ObjectType::UNIT) { + } else if (entity->getType() == ObjectType::UNIT || entity->getType() == ObjectType::PLAYER) { bool wasDead = (oldDyn & UNIT_DYNFLAG_DEAD) != 0; bool nowDead = (val & UNIT_DYNFLAG_DEAD) != 0; if (!wasDead && nowDead) { @@ -12088,6 +12091,12 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem " displayId=", unit->getDisplayId(), " appearance extraction failed (VALUES update) — model will not render"); } } + bool isDeadNow = (unit->getHealth() == 0) || + ((unit->getDynamicFlags() & (UNIT_DYNFLAG_DEAD | UNIT_DYNFLAG_LOOTABLE)) != 0); + if (isDeadNow && !npcDeathNotified && npcDeathCallback_) { + npcDeathCallback_(block.guid); + npcDeathNotified = true; + } } else if (creatureSpawnCallback_) { float unitScale2 = 1.0f; {