diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index b8f66903..260e4c74 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -513,15 +513,33 @@ public: float getCastProgress() const { return castTimeTotal > 0 ? (castTimeTotal - castTimeRemaining) / castTimeTotal : 0.0f; } float getCastTimeRemaining() const { return castTimeRemaining; } - // Target cast bar (shows when the current target is casting) - bool isTargetCasting() const { return targetCasting_; } - uint32_t getTargetCastSpellId() const { return targetCastSpellId_; } - float getTargetCastProgress() const { - return targetCastTimeTotal_ > 0.0f - ? (targetCastTimeTotal_ - targetCastTimeRemaining_) / targetCastTimeTotal_ - : 0.0f; + // Unit cast state (tracked per GUID for target frame + boss frames) + struct UnitCastState { + bool casting = false; + uint32_t spellId = 0; + float timeRemaining = 0.0f; + float timeTotal = 0.0f; + }; + // Returns cast state for any unit by GUID (empty/non-casting if not found) + const UnitCastState* getUnitCastState(uint64_t guid) const { + auto it = unitCastStates_.find(guid); + return (it != unitCastStates_.end() && it->second.casting) ? &it->second : nullptr; + } + // Convenience helpers for the current target + bool isTargetCasting() const { return getUnitCastState(targetGuid) != nullptr; } + uint32_t getTargetCastSpellId() const { + auto* s = getUnitCastState(targetGuid); + return s ? s->spellId : 0; + } + float getTargetCastProgress() const { + auto* s = getUnitCastState(targetGuid); + return (s && s->timeTotal > 0.0f) + ? (s->timeTotal - s->timeRemaining) / s->timeTotal : 0.0f; + } + float getTargetCastTimeRemaining() const { + auto* s = getUnitCastState(targetGuid); + return s ? s->timeRemaining : 0.0f; } - float getTargetCastTimeRemaining() const { return targetCastTimeRemaining_; } // Talents uint8_t getActiveTalentSpec() const { return activeTalentSpec_; } @@ -1764,11 +1782,8 @@ private: bool casting = false; uint32_t currentCastSpellId = 0; float castTimeRemaining = 0.0f; - // Target cast bar state (populated from SMSG_SPELL_START for the current target) - bool targetCasting_ = false; - uint32_t targetCastSpellId_ = 0; - float targetCastTimeRemaining_= 0.0f; - float targetCastTimeTotal_ = 0.0f; + // Per-unit cast state (keyed by GUID, populated from SMSG_SPELL_START) + std::unordered_map unitCastStates_; uint64_t pendingGameObjectInteractGuid_ = 0; // Talents (dual-spec support) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 9b4b697f..9d35d7af 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -759,14 +759,17 @@ void GameHandler::update(float deltaTime) { } } - // Tick down target cast bar - if (targetCasting_ && targetCastTimeRemaining_ > 0.0f) { - targetCastTimeRemaining_ -= deltaTime; - if (targetCastTimeRemaining_ <= 0.0f) { - targetCasting_ = false; - targetCastSpellId_ = 0; - targetCastTimeRemaining_ = 0.0f; + // Tick down all tracked unit cast bars + for (auto it = unitCastStates_.begin(); it != unitCastStates_.end(); ) { + auto& s = it->second; + if (s.casting && s.timeRemaining > 0.0f) { + s.timeRemaining -= deltaTime; + if (s.timeRemaining <= 0.0f) { + it = unitCastStates_.erase(it); + continue; + } } + ++it; } // Update spell cooldowns (Phase 3) @@ -5700,6 +5703,7 @@ void GameHandler::selectCharacter(uint64_t characterGuid) { actionBar = {}; playerAuras.clear(); targetAuras.clear(); + unitCastStates_.clear(); petGuid_ = 0; playerXp_ = 0; playerNextLevelXp_ = 0; @@ -8556,10 +8560,8 @@ void GameHandler::setTarget(uint64_t guid) { targetGuid = guid; - // Clear target cast bar when target changes - targetCasting_ = false; - targetCastSpellId_ = 0; - targetCastTimeRemaining_ = 0.0f; + // Clear previous target's cast bar on target change + // (the new target's cast state is naturally fetched from unitCastStates_ by GUID) // Inform server of target selection (Phase 1) if (state == WorldState::IN_WORLD && socket) { @@ -12656,12 +12658,13 @@ void GameHandler::handleSpellStart(network::Packet& packet) { SpellStartData data; if (!packetParsers_->parseSpellStart(packet, data)) return; - // Track cast bar for the current target (for interrupt awareness) - if (data.casterUnit == targetGuid && data.castTime > 0) { - targetCasting_ = true; - targetCastSpellId_ = data.spellId; - targetCastTimeTotal_ = data.castTime / 1000.0f; - targetCastTimeRemaining_ = targetCastTimeTotal_; + // Track cast bar for any non-player caster (target frame + boss frames) + if (data.casterUnit != playerGuid && data.castTime > 0) { + auto& s = unitCastStates_[data.casterUnit]; + s.casting = true; + s.spellId = data.spellId; + s.timeTotal = data.castTime / 1000.0f; + s.timeRemaining = s.timeTotal; } // If this is the player's own cast, start cast bar @@ -12736,12 +12739,8 @@ void GameHandler::handleSpellGo(network::Packet& packet) { castTimeRemaining = 0.0f; } - // Clear target cast bar when the target's spell lands - if (data.casterUnit == targetGuid) { - targetCasting_ = false; - targetCastSpellId_ = 0; - targetCastTimeRemaining_ = 0.0f; - } + // Clear unit cast bar when the spell lands (for any tracked unit) + unitCastStates_.erase(data.casterUnit); // Show miss/dodge/parry/etc combat text when player's spells miss targets if (data.casterUnit == playerGuid && !data.missTargets.empty()) { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 1d103bcd..8a609dd3 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -5010,6 +5010,24 @@ void GameScreen::renderBossFrames(game::GameHandler& gameHandler) { ImGui::PopStyleColor(); } + // Boss cast bar — shown when the boss is casting (critical for interrupt) + if (auto* cs = gameHandler.getUnitCastState(bs.guid)) { + float castPct = (cs->timeTotal > 0.0f) + ? (cs->timeTotal - cs->timeRemaining) / cs->timeTotal : 0.0f; + uint32_t bspell = cs->spellId; + const std::string& bcastName = (bspell != 0) + ? gameHandler.getSpellName(bspell) : ""; + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.9f, 0.3f, 0.2f, 1.0f)); + char bcastLabel[72]; + if (!bcastName.empty()) + snprintf(bcastLabel, sizeof(bcastLabel), "%s (%.1fs)", + bcastName.c_str(), cs->timeRemaining); + else + snprintf(bcastLabel, sizeof(bcastLabel), "Casting... (%.1fs)", cs->timeRemaining); + ImGui::ProgressBar(castPct, ImVec2(-1, 12), bcastLabel); + ImGui::PopStyleColor(); + } + ImGui::PopID(); ImGui::Spacing(); }