diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 17b66b75..227a27d2 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -705,6 +705,11 @@ public: // Auras const std::vector& getPlayerAuras() const { return playerAuras; } const std::vector& getTargetAuras() const { return targetAuras; } + // Per-unit aura cache (populated for party members and any unit we receive updates for) + const std::vector* getUnitAuras(uint64_t guid) const { + auto it = unitAurasCache_.find(guid); + return (it != unitAurasCache_.end()) ? &it->second : nullptr; + } // Completed quests (populated from SMSG_QUERY_QUESTS_COMPLETED_RESPONSE) bool isQuestCompleted(uint32_t questId) const { return completedQuests_.count(questId) > 0; } @@ -2247,6 +2252,7 @@ private: std::array actionBar{}; std::vector playerAuras; std::vector targetAuras; + std::unordered_map> unitAurasCache_; // per-unit aura cache uint64_t petGuid_ = 0; uint32_t petActionSlots_[10] = {}; // SMSG_PET_SPELLS action bar (10 slots) uint8_t petCommand_ = 1; // 0=stay,1=follow,2=attack,3=dismiss diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index ccc620fc..6f59d00a 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -6708,6 +6708,7 @@ void GameHandler::selectCharacter(uint64_t characterGuid) { actionBar = {}; playerAuras.clear(); targetAuras.clear(); + unitAurasCache_.clear(); unitCastStates_.clear(); petGuid_ = 0; playerXp_ = 0; @@ -14595,6 +14596,10 @@ void GameHandler::handleAuraUpdate(network::Packet& packet, bool isAll) { } else if (data.guid == targetGuid) { auraList = &targetAuras; } + // Also maintain a per-unit cache for any unit (party members, etc.) + if (data.guid != 0 && data.guid != playerGuid && data.guid != targetGuid) { + auraList = &unitAurasCache_[data.guid]; + } if (auraList) { if (isAll) { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index c841cd0d..1e106f35 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -8379,6 +8379,57 @@ void GameScreen::renderPartyFrames(game::GameHandler& gameHandler) { ImGui::PopStyleColor(); } + // Dispellable debuff indicators — small colored dots for party member debuffs + // Only show magic/curse/disease/poison (types 1-4); skip non-dispellable + if (!memberDead && !memberOffline) { + const std::vector* unitAuras = nullptr; + if (member.guid == gameHandler.getPlayerGuid()) + unitAuras = &gameHandler.getPlayerAuras(); + else if (member.guid == gameHandler.getTargetGuid()) + unitAuras = &gameHandler.getTargetAuras(); + else + unitAuras = gameHandler.getUnitAuras(member.guid); + + if (unitAuras) { + bool anyDebuff = false; + for (const auto& aura : *unitAuras) { + if (aura.isEmpty()) continue; + if ((aura.flags & 0x80) == 0) continue; // only debuffs + uint8_t dt = gameHandler.getSpellDispelType(aura.spellId); + if (dt == 0) continue; // skip non-dispellable + anyDebuff = true; + break; + } + if (anyDebuff) { + // Render one dot per unique dispel type present + bool shown[5] = {}; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2.0f, 1.0f)); + for (const auto& aura : *unitAuras) { + if (aura.isEmpty()) continue; + if ((aura.flags & 0x80) == 0) continue; + uint8_t dt = gameHandler.getSpellDispelType(aura.spellId); + if (dt == 0 || dt > 4 || shown[dt]) continue; + shown[dt] = true; + ImVec4 dotCol; + switch (dt) { + case 1: dotCol = ImVec4(0.25f, 0.50f, 1.00f, 1.0f); break; // Magic: blue + case 2: dotCol = ImVec4(0.70f, 0.15f, 0.90f, 1.0f); break; // Curse: purple + case 3: dotCol = ImVec4(0.65f, 0.45f, 0.10f, 1.0f); break; // Disease: brown + case 4: dotCol = ImVec4(0.10f, 0.75f, 0.10f, 1.0f); break; // Poison: green + default: break; + } + ImGui::PushStyleColor(ImGuiCol_Button, dotCol); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, dotCol); + ImGui::Button("##d", ImVec2(8.0f, 8.0f)); + ImGui::PopStyleColor(2); + ImGui::SameLine(); + } + ImGui::NewLine(); + ImGui::PopStyleVar(); + } + } + } + // Party member cast bar — shows when the party member is casting if (auto* cs = gameHandler.getUnitCastState(member.guid)) { float castPct = (cs->timeTotal > 0.0f)