From e51b215f85768706bb9d00f30385e545f803cb84 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 13 Mar 2026 12:03:07 -0700 Subject: [PATCH] feat: add DISPEL and INTERRUPT combat log entries for dispel/spellsteal/interrupt events --- include/game/spell_defines.hpp | 3 +- src/game/game_handler.cpp | 69 ++++++++++++++++++++++------------ src/ui/game_screen.cpp | 22 +++++++++++ 3 files changed, 70 insertions(+), 24 deletions(-) diff --git a/include/game/spell_defines.hpp b/include/game/spell_defines.hpp index d8f8c1df..a3944a0e 100644 --- a/include/game/spell_defines.hpp +++ b/include/game/spell_defines.hpp @@ -52,7 +52,8 @@ struct CombatTextEntry { enum Type : uint8_t { MELEE_DAMAGE, SPELL_DAMAGE, HEAL, MISS, DODGE, PARRY, BLOCK, CRIT_DAMAGE, CRIT_HEAL, PERIODIC_DAMAGE, PERIODIC_HEAL, ENVIRONMENTAL, - ENERGIZE, XP_GAIN, IMMUNE, ABSORB, RESIST, PROC_TRIGGER + ENERGIZE, XP_GAIN, IMMUNE, ABSORB, RESIST, PROC_TRIGGER, + DISPEL, INTERRUPT }; Type type; int32_t amount = 0; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 8403aae8..13e6171b 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -6160,20 +6160,22 @@ void GameHandler::handlePacket(network::Packet& packet) { /*uint32_t dispelSpell =*/ packet.readUInt32(); uint8_t isStolen = packet.readUInt8(); uint32_t count = packet.readUInt32(); + // Collect first dispelled spell id/name; process all entries for combat log + // Each entry: uint32 spellId + uint8 isPositive (5 bytes in WotLK/TBC/Classic) + uint32_t firstDispelledId = 0; + std::string firstSpellName; + for (uint32_t i = 0; i < count && packet.getSize() - packet.getReadPos() >= 5; ++i) { + uint32_t dispelledId = packet.readUInt32(); + /*uint8_t isPositive =*/ packet.readUInt8(); + if (i == 0) { + firstDispelledId = dispelledId; + const std::string& nm = getSpellName(dispelledId); + firstSpellName = nm.empty() ? ("spell " + std::to_string(dispelledId)) : nm; + } + } // Show system message if player was victim or caster if (victimGuid == playerGuid || casterGuid == playerGuid) { const char* verb = isStolen ? "stolen" : "dispelled"; - // Collect first dispelled spell name for the message - // Each entry: uint32 spellId + uint8 isPositive (5 bytes in WotLK/TBC/Classic) - std::string firstSpellName; - for (uint32_t i = 0; i < count && packet.getSize() - packet.getReadPos() >= 5; ++i) { - uint32_t dispelledId = packet.readUInt32(); - /*uint8_t isPositive =*/ packet.readUInt8(); - if (i == 0) { - const std::string& nm = getSpellName(dispelledId); - firstSpellName = nm.empty() ? ("spell " + std::to_string(dispelledId)) : nm; - } - } if (!firstSpellName.empty()) { char buf[256]; if (victimGuid == playerGuid && casterGuid != playerGuid) @@ -6184,6 +6186,12 @@ void GameHandler::handlePacket(network::Packet& packet) { std::snprintf(buf, sizeof(buf), "%s %s.", firstSpellName.c_str(), verb); addSystemChatMessage(buf); } + // Add dispel event to combat log + if (firstDispelledId != 0) { + bool isPlayerCaster = (casterGuid == playerGuid); + addCombatText(CombatTextEntry::DISPEL, 0, firstDispelledId, isPlayerCaster, 0, + casterGuid, victimGuid); + } } packet.setReadPos(packet.getSize()); break; @@ -6198,7 +6206,7 @@ void GameHandler::handlePacket(network::Packet& packet) { if (packet.getSize() - packet.getReadPos() < (stealTbcLike ? 8u : 2u)) { packet.setReadPos(packet.getSize()); break; } - /*uint64_t stealVictim =*/ stealTbcLike + uint64_t stealVictim = stealTbcLike ? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet); if (packet.getSize() - packet.getReadPos() < (stealTbcLike ? 8u : 2u)) { packet.setReadPos(packet.getSize()); break; @@ -6211,22 +6219,33 @@ void GameHandler::handlePacket(network::Packet& packet) { /*uint32_t stealSpellId =*/ packet.readUInt32(); /*uint8_t isStolen =*/ packet.readUInt8(); uint32_t stealCount = packet.readUInt32(); - // Show feedback only when we are the caster (we stole something) - if (stealCaster == playerGuid) { - std::string stolenName; - for (uint32_t i = 0; i < stealCount && packet.getSize() - packet.getReadPos() >= 5; ++i) { - uint32_t stolenId = packet.readUInt32(); - /*uint8_t isPos =*/ packet.readUInt8(); - if (i == 0) { - const std::string& nm = getSpellName(stolenId); - stolenName = nm.empty() ? ("spell " + std::to_string(stolenId)) : nm; - } + // Collect stolen spell info; show feedback when we are caster or victim + uint32_t firstStolenId = 0; + std::string stolenName; + for (uint32_t i = 0; i < stealCount && packet.getSize() - packet.getReadPos() >= 5; ++i) { + uint32_t stolenId = packet.readUInt32(); + /*uint8_t isPos =*/ packet.readUInt8(); + if (i == 0) { + firstStolenId = stolenId; + const std::string& nm = getSpellName(stolenId); + stolenName = nm.empty() ? ("spell " + std::to_string(stolenId)) : nm; } + } + if (stealCaster == playerGuid || stealVictim == playerGuid) { if (!stolenName.empty()) { char buf[256]; - std::snprintf(buf, sizeof(buf), "You stole %s.", stolenName.c_str()); + if (stealCaster == playerGuid) + std::snprintf(buf, sizeof(buf), "You stole %s.", stolenName.c_str()); + else + std::snprintf(buf, sizeof(buf), "%s was stolen.", stolenName.c_str()); addSystemChatMessage(buf); } + // Add dispel/steal to combat log using DISPEL type (isStolen=true for steals) + if (firstStolenId != 0) { + bool isPlayerCaster = (stealCaster == playerGuid); + addCombatText(CombatTextEntry::DISPEL, 0, firstStolenId, isPlayerCaster, 0, + stealCaster, stealVictim); + } } packet.setReadPos(packet.getSize()); break; @@ -6383,6 +6402,10 @@ void GameHandler::handlePacket(network::Packet& packet) { uint32_t icSpellId = packet.readUInt32(); // Clear the interrupted unit's cast bar immediately unitCastStates_.erase(icTarget); + // Record interrupt in combat log when player is involved + if (isPlayerCaster || icTarget == playerGuid) + addCombatText(CombatTextEntry::INTERRUPT, 0, icSpellId, isPlayerCaster, 0, + exeCaster, icTarget); LOG_DEBUG("SMSG_SPELLLOGEXECUTE INTERRUPT_CAST: spell=", exeSpellId, " interrupted=", icSpellId, " target=0x", std::hex, icTarget, std::dec); } diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 2c4fff48..2f30ee64 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -20265,6 +20265,28 @@ void GameScreen::renderCombatLog(game::GameHandler& gameHandler) { snprintf(desc, sizeof(desc), "Proc triggered"); color = ImVec4(1.0f, 0.85f, 0.3f, 1.0f); break; + case T::DISPEL: + if (spell && e.isPlayerSource) + snprintf(desc, sizeof(desc), "You dispel %s from %s", spell, tgt); + else if (spell) + snprintf(desc, sizeof(desc), "%s dispels %s from %s", src, spell, tgt); + else if (e.isPlayerSource) + snprintf(desc, sizeof(desc), "You dispel from %s", tgt); + else + snprintf(desc, sizeof(desc), "%s dispels from %s", src, tgt); + color = ImVec4(0.6f, 0.9f, 1.0f, 1.0f); + break; + case T::INTERRUPT: + if (spell && e.isPlayerSource) + snprintf(desc, sizeof(desc), "You interrupt %s's %s", tgt, spell); + else if (spell) + snprintf(desc, sizeof(desc), "%s interrupts %s's %s", src, tgt, spell); + else if (e.isPlayerSource) + snprintf(desc, sizeof(desc), "You interrupt %s", tgt); + else + snprintf(desc, sizeof(desc), "%s interrupted", tgt); + color = ImVec4(1.0f, 0.6f, 0.9f, 1.0f); + break; default: snprintf(desc, sizeof(desc), "Combat event (type %d, amount %d)", (int)e.type, e.amount); color = ImVec4(0.7f, 0.7f, 0.7f, 1.0f);