diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 1ca12c72..b8f66903 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -513,6 +513,16 @@ 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; + } + float getTargetCastTimeRemaining() const { return targetCastTimeRemaining_; } + // Talents uint8_t getActiveTalentSpec() const { return activeTalentSpec_; } uint8_t getUnspentTalentPoints() const { return unspentTalentPoints_[activeTalentSpec_]; } @@ -1754,6 +1764,11 @@ 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; uint64_t pendingGameObjectInteractGuid_ = 0; // Talents (dual-spec support) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 759e4ffb..9b4b697f 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -759,6 +759,16 @@ 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; + } + } + // Update spell cooldowns (Phase 3) for (auto it = spellCooldowns.begin(); it != spellCooldowns.end(); ) { it->second -= deltaTime; @@ -8546,6 +8556,11 @@ void GameHandler::setTarget(uint64_t guid) { targetGuid = guid; + // Clear target cast bar when target changes + targetCasting_ = false; + targetCastSpellId_ = 0; + targetCastTimeRemaining_ = 0.0f; + // Inform server of target selection (Phase 1) if (state == WorldState::IN_WORLD && socket) { auto packet = SetSelectionPacket::build(guid); @@ -12641,6 +12656,14 @@ 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_; + } + // If this is the player's own cast, start cast bar if (data.casterUnit == playerGuid && data.castTime > 0) { casting = true; @@ -12713,6 +12736,13 @@ 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; + } + // 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[] = { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 86a46464..5a890112 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -2093,6 +2093,22 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) { } } + // Target cast bar — shown when the target is casting + if (gameHandler.isTargetCasting()) { + float castPct = gameHandler.getTargetCastProgress(); + float castLeft = gameHandler.getTargetCastTimeRemaining(); + uint32_t tspell = gameHandler.getTargetCastSpellId(); + const std::string& castName = (tspell != 0) ? gameHandler.getSpellName(tspell) : ""; + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.9f, 0.3f, 0.2f, 1.0f)); + char castLabel[72]; + if (!castName.empty()) + snprintf(castLabel, sizeof(castLabel), "%s (%.1fs)", castName.c_str(), castLeft); + else + snprintf(castLabel, sizeof(castLabel), "Casting... (%.1fs)", castLeft); + ImGui::ProgressBar(castPct, ImVec2(-1, 14), castLabel); + ImGui::PopStyleColor(); + } + // Distance const auto& movement = gameHandler.getMovementInfo(); float dx = target->getX() - movement.x;