diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index a65fd4aa..affb44ad 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -1766,6 +1766,11 @@ public: }; /** SMSG_SPELL_GO data (simplified) */ +struct SpellGoMissEntry { + uint64_t targetGuid = 0; + uint8_t missType = 0; // 0=MISS 1=DODGE 2=PARRY 3=BLOCK 4=EVADE 5=IMMUNE 6=DEFLECT 7=ABSORB 8=RESIST +}; + struct SpellGoData { uint64_t casterGuid = 0; uint64_t casterUnit = 0; @@ -1773,8 +1778,9 @@ struct SpellGoData { uint32_t spellId = 0; uint32_t castFlags = 0; uint8_t hitCount = 0; - std::vector hitTargets; + std::vector hitTargets; uint8_t missCount = 0; + std::vector missTargets; bool isValid() const { return spellId != 0; } }; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index b309891f..759e4ffb 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -12713,6 +12713,26 @@ void GameHandler::handleSpellGo(network::Packet& packet) { castTimeRemaining = 0.0f; } + // Show miss/dodge/parry/etc combat text when player's spells miss targets + if (data.casterUnit == playerGuid && !data.missTargets.empty()) { + static const CombatTextEntry::Type missTypes[] = { + CombatTextEntry::MISS, // 0=MISS + CombatTextEntry::DODGE, // 1=DODGE + CombatTextEntry::PARRY, // 2=PARRY + CombatTextEntry::BLOCK, // 3=BLOCK + CombatTextEntry::MISS, // 4=EVADE → show as MISS + CombatTextEntry::MISS, // 5=IMMUNE → show as MISS + CombatTextEntry::MISS, // 6=DEFLECT + CombatTextEntry::MISS, // 7=ABSORB + CombatTextEntry::MISS, // 8=RESIST + }; + // Show text for each miss (usually just 1 target per spell go) + for (const auto& m : data.missTargets) { + CombatTextEntry::Type ct = (m.missType < 9) ? missTypes[m.missType] : CombatTextEntry::MISS; + addCombatText(ct, 0, 0, true); + } + } + // Play impact sound when player is hit by any spell (from self or others) bool playerIsHit = false; for (const auto& tgt : data.hitTargets) { diff --git a/src/game/packet_parsers_tbc.cpp b/src/game/packet_parsers_tbc.cpp index f65de114..db9d007b 100644 --- a/src/game/packet_parsers_tbc.cpp +++ b/src/game/packet_parsers_tbc.cpp @@ -1051,6 +1051,13 @@ bool TbcPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& data) if (packet.getReadPos() < packet.getSize()) { data.missCount = packet.readUInt8(); + data.missTargets.reserve(data.missCount); + for (uint8_t i = 0; i < data.missCount && packet.getReadPos() + 9 <= packet.getSize(); ++i) { + SpellGoMissEntry m; + m.targetGuid = packet.readUInt64(); // full GUID in TBC + m.missType = packet.readUInt8(); + data.missTargets.push_back(m); + } } LOG_DEBUG("[TBC] Spell go: spell=", data.spellId, " hits=", (int)data.hitCount, diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 46fdcde4..9ef9a71a 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -2987,7 +2987,13 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) { } data.missCount = packet.readUInt8(); - // Skip miss details for now + data.missTargets.reserve(data.missCount); + for (uint8_t i = 0; i < data.missCount && packet.getReadPos() + 2 <= packet.getSize(); ++i) { + SpellGoMissEntry m; + m.targetGuid = UpdateObjectParser::readPackedGuid(packet); // packed GUID in WotLK + m.missType = (packet.getReadPos() < packet.getSize()) ? packet.readUInt8() : 0; + data.missTargets.push_back(m); + } LOG_DEBUG("Spell go: spell=", data.spellId, " hits=", (int)data.hitCount, " misses=", (int)data.missCount); diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 4f7e2d0c..b079f50a 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -1185,7 +1185,7 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) { " tris, grid ", gpuModel.collision.gridCellsX, "x", gpuModel.collision.gridCellsY); } - // Flag smoke models for UV scroll animation (particle emitters not implemented) + // Flag smoke models for UV scroll animation (in addition to particle emitters) { std::string smokeName = model.name; std::transform(smokeName.begin(), smokeName.end(), smokeName.begin(), @@ -2709,7 +2709,6 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const uint16_t targetLOD = desiredLOD; if (desiredLOD > 0 && !(model.availableLODs & (1u << desiredLOD))) targetLOD = 0; - const bool foliageLikeModel = model.isFoliageLike; const bool particleDominantEffect = model.isSpellEffect && !model.particleEmitters.empty() && model.batches.size() <= 2;