From 151303a20a6869f4aefea8edb6e2aa36294bbe9e Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Mar 2026 20:15:34 -0700 Subject: [PATCH] Implement SMSG_SPELLDAMAGESHIELD, SMSG_SPELLORDAMAGE_IMMUNE; route MSG_MOVE in SMSG_MULTIPLE_MOVES - SMSG_SPELLDAMAGESHIELD: parse victim/caster/damage fields and show SPELL_DAMAGE combat text for player-relevant events (damage shields like Thorns) - SMSG_SPELLORDAMAGE_IMMUNE: parse packed caster/victim guids and show new IMMUNE combat text type when player is involved in an immunity event - Add CombatTextEntry::IMMUNE type to spell_defines.hpp and render it as white "Immune!" in the combat text overlay - handleCompressedMoves: add MSG_MOVE_* routing so SMSG_MULTIPLE_MOVES sub-packets (player movement batches) are dispatched to handleOtherPlayerMovement instead of logged as unhandled; fix runtime-opcode lookup (non-static array) --- include/game/spell_defines.hpp | 2 +- src/game/game_handler.cpp | 65 ++++++++++++++++++++++++++++++++-- src/ui/game_screen.cpp | 4 +++ 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/include/game/spell_defines.hpp b/include/game/spell_defines.hpp index 3d1e871f..041b44f6 100644 --- a/include/game/spell_defines.hpp +++ b/include/game/spell_defines.hpp @@ -51,7 +51,7 @@ 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 + ENERGIZE, XP_GAIN, IMMUNE }; Type type; int32_t amount = 0; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index f4dd5f8a..b5ad4772 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -4529,11 +4529,48 @@ void GameHandler::handlePacket(network::Packet& packet) { // ---- Spell combat logs (consume) ---- case Opcode::SMSG_AURACASTLOG: case Opcode::SMSG_SPELLBREAKLOG: - case Opcode::SMSG_SPELLDAMAGESHIELD: + case Opcode::SMSG_SPELLDAMAGESHIELD: { + // victimGuid(8) + casterGuid(8) + spellId(4) + damage(4) + schoolMask(4) + if (packet.getSize() - packet.getReadPos() < 24) { + packet.setReadPos(packet.getSize()); break; + } + uint64_t victimGuid = packet.readUInt64(); + uint64_t casterGuid = packet.readUInt64(); + /*uint32_t spellId =*/ packet.readUInt32(); + uint32_t damage = packet.readUInt32(); + /*uint32_t school =*/ packet.readUInt32(); + // Show combat text: damage shield reflect + if (casterGuid == playerGuid) { + // We have a damage shield that reflected damage + addCombatText(CombatTextEntry::SPELL_DAMAGE, static_cast(damage), 0, true); + } else if (victimGuid == playerGuid) { + // A damage shield hit us (e.g. target's Thorns) + addCombatText(CombatTextEntry::SPELL_DAMAGE, static_cast(damage), 0, false); + } + break; + } + case Opcode::SMSG_SPELLORDAMAGE_IMMUNE: { + // casterGuid(packed) + victimGuid(packed) + uint32 spellId + uint8 saveType + if (packet.getSize() - packet.getReadPos() < 2) { + packet.setReadPos(packet.getSize()); break; + } + uint64_t casterGuid = UpdateObjectParser::readPackedGuid(packet); + if (packet.getSize() - packet.getReadPos() < 2) break; + uint64_t victimGuid = UpdateObjectParser::readPackedGuid(packet); + if (packet.getSize() - packet.getReadPos() < 5) break; + /*uint32_t spellId =*/ packet.readUInt32(); + /*uint8_t saveType =*/ packet.readUInt8(); + // Show IMMUNE text when the player is the caster (we hit an immune target) + // or the victim (we are immune) + if (casterGuid == playerGuid || victimGuid == playerGuid) { + addCombatText(CombatTextEntry::IMMUNE, 0, 0, + casterGuid == playerGuid); + } + break; + } case Opcode::SMSG_SPELLDISPELLOG: case Opcode::SMSG_SPELLINSTAKILLLOG: case Opcode::SMSG_SPELLLOGEXECUTE: - case Opcode::SMSG_SPELLORDAMAGE_IMMUNE: case Opcode::SMSG_SPELLSTEALLOG: case Opcode::SMSG_SPELL_CHANCE_PROC_LOG: case Opcode::SMSG_SPELL_CHANCE_RESIST_PUSHBACK: @@ -11496,6 +11533,26 @@ void GameHandler::handleCompressedMoves(network::Packet& packet) { uint16_t monsterMoveWire = wireOpcode(Opcode::SMSG_MONSTER_MOVE); uint16_t monsterMoveTransportWire = wireOpcode(Opcode::SMSG_MONSTER_MOVE_TRANSPORT); + // Player movement sub-opcodes (SMSG_MULTIPLE_MOVES carries MSG_MOVE_*) + // Not static — wireOpcode() depends on runtime active opcode table. + const std::array kMoveOpcodes = { + wireOpcode(Opcode::MSG_MOVE_START_FORWARD), + wireOpcode(Opcode::MSG_MOVE_START_BACKWARD), + wireOpcode(Opcode::MSG_MOVE_STOP), + wireOpcode(Opcode::MSG_MOVE_START_STRAFE_LEFT), + wireOpcode(Opcode::MSG_MOVE_START_STRAFE_RIGHT), + wireOpcode(Opcode::MSG_MOVE_STOP_STRAFE), + wireOpcode(Opcode::MSG_MOVE_JUMP), + wireOpcode(Opcode::MSG_MOVE_START_TURN_LEFT), + wireOpcode(Opcode::MSG_MOVE_START_TURN_RIGHT), + wireOpcode(Opcode::MSG_MOVE_STOP_TURN), + wireOpcode(Opcode::MSG_MOVE_SET_FACING), + wireOpcode(Opcode::MSG_MOVE_FALL_LAND), + wireOpcode(Opcode::MSG_MOVE_HEARTBEAT), + wireOpcode(Opcode::MSG_MOVE_START_SWIM), + wireOpcode(Opcode::MSG_MOVE_STOP_SWIM), + }; + // Track unhandled sub-opcodes once per compressed packet (avoid log spam) std::unordered_set unhandledSeen; @@ -11521,6 +11578,10 @@ void GameHandler::handleCompressedMoves(network::Packet& packet) { handleMonsterMove(subPacket); } else if (subOpcode == monsterMoveTransportWire) { handleMonsterMoveTransport(subPacket); + } else if (state == WorldState::IN_WORLD && + std::find(kMoveOpcodes.begin(), kMoveOpcodes.end(), subOpcode) != kMoveOpcodes.end()) { + // Player/NPC movement update packed in SMSG_MULTIPLE_MOVES + handleOtherPlayerMovement(subPacket); } else { if (unhandledSeen.insert(subOpcode).second) { LOG_INFO("SMSG_COMPRESSED_MOVES: unhandled sub-opcode 0x", diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 504b609e..f5ab928e 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -4671,6 +4671,10 @@ void GameScreen::renderCombatText(game::GameHandler& gameHandler) { snprintf(text, sizeof(text), "+%d XP", entry.amount); color = ImVec4(0.7f, 0.3f, 1.0f, alpha); // Purple for XP break; + case game::CombatTextEntry::IMMUNE: + snprintf(text, sizeof(text), "Immune!"); + color = ImVec4(0.9f, 0.9f, 0.9f, alpha); // White for immune + break; default: snprintf(text, sizeof(text), "%d", entry.amount); color = ImVec4(1.0f, 1.0f, 1.0f, alpha);