From c89dc50b6cb2671be64b76b8778ca1128a4893a8 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 12 Mar 2026 00:43:29 -0700 Subject: [PATCH] Distinguish channeled spells in cast bar with blue color and draining animation Adds castIsChannel flag set on MSG_CHANNEL_START, cleared on all cast resets. Cast bar now drains right-to-left in blue for channels vs gold fill for casts. --- include/game/game_handler.hpp | 2 ++ src/game/game_handler.cpp | 14 ++++++++++++++ src/ui/game_screen.cpp | 17 +++++++++++++---- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index d6df5254..2001e4eb 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -553,6 +553,7 @@ public: } bool isCasting() const { return casting; } + bool isChanneling() const { return casting && castIsChannel; } bool isGameObjectInteractionCasting() const { return casting && currentCastSpellId == 0 && pendingGameObjectInteractGuid_ != 0; } @@ -2046,6 +2047,7 @@ private: std::vector minimapPings_; uint8_t castCount = 0; bool casting = false; + bool castIsChannel = false; uint32_t currentCastSpellId = 0; float castTimeRemaining = 0.0f; // Per-unit cast state (keyed by GUID, populated from SMSG_SPELL_START) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index b2b51503..5bda0189 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -904,6 +904,7 @@ void GameHandler::update(float deltaTime) { (autoAttacking || autoAttackRequested_)) { pendingGameObjectInteractGuid_ = 0; casting = false; + castIsChannel = false; currentCastSpellId = 0; castTimeRemaining = 0.0f; addSystemChatMessage("Interrupted."); @@ -917,6 +918,7 @@ void GameHandler::update(float deltaTime) { performGameObjectInteractionNow(interactGuid); } casting = false; + castIsChannel = false; currentCastSpellId = 0; castTimeRemaining = 0.0f; } @@ -1947,6 +1949,7 @@ void GameHandler::handlePacket(network::Packet& packet) { if (packetParsers_->parseCastResult(packet, castResultSpellId, castResult)) { if (castResult != 0) { casting = false; + castIsChannel = false; currentCastSpellId = 0; castTimeRemaining = 0.0f; // Pass player's power type so result 85 says "Not enough rage/energy/etc." @@ -2837,6 +2840,7 @@ void GameHandler::handlePacket(network::Packet& packet) { if (failGuid == playerGuid || failGuid == 0) { // Player's own cast failed casting = false; + castIsChannel = false; currentCastSpellId = 0; if (auto* renderer = core::Application::getInstance().getRenderer()) { if (auto* ssm = renderer->getSpellSoundManager()) { @@ -5528,6 +5532,7 @@ void GameHandler::handlePacket(network::Packet& packet) { if (totalMs > 0) { if (caster == playerGuid) { casting = true; + castIsChannel = false; currentCastSpellId = spellId; castTimeTotal = totalMs / 1000.0f; castTimeRemaining = remainMs / 1000.0f; @@ -5556,6 +5561,7 @@ void GameHandler::handlePacket(network::Packet& packet) { if (chanTotalMs > 0 && chanCaster != 0) { if (chanCaster == playerGuid) { casting = true; + castIsChannel = true; currentCastSpellId = chanSpellId; castTimeTotal = chanTotalMs / 1000.0f; castTimeRemaining = castTimeTotal; @@ -5583,6 +5589,7 @@ void GameHandler::handlePacket(network::Packet& packet) { castTimeRemaining = chanRemainMs / 1000.0f; if (chanRemainMs == 0) { casting = false; + castIsChannel = false; currentCastSpellId = 0; } } else if (chanCaster2 != 0) { @@ -6585,6 +6592,7 @@ void GameHandler::selectCharacter(uint64_t characterGuid) { autoAttacking = false; autoAttackTarget = 0; casting = false; + castIsChannel = false; currentCastSpellId = 0; pendingGameObjectInteractGuid_ = 0; castTimeRemaining = 0.0f; @@ -10633,6 +10641,7 @@ void GameHandler::stopCasting() { // Reset casting state casting = false; + castIsChannel = false; currentCastSpellId = 0; pendingGameObjectInteractGuid_ = 0; castTimeRemaining = 0.0f; @@ -13937,6 +13946,7 @@ void GameHandler::cancelCast() { } pendingGameObjectInteractGuid_ = 0; casting = false; + castIsChannel = false; currentCastSpellId = 0; castTimeRemaining = 0.0f; } @@ -14075,6 +14085,7 @@ void GameHandler::handleCastFailed(network::Packet& packet) { if (!ok) return; casting = false; + castIsChannel = false; currentCastSpellId = 0; castTimeRemaining = 0.0f; @@ -14133,6 +14144,7 @@ void GameHandler::handleSpellStart(network::Packet& packet) { // If this is the player's own cast, start cast bar if (data.casterUnit == playerGuid && data.castTime > 0) { casting = true; + castIsChannel = false; currentCastSpellId = data.spellId; castTimeTotal = data.castTime / 1000.0f; castTimeRemaining = castTimeTotal; @@ -14203,6 +14215,7 @@ void GameHandler::handleSpellGo(network::Packet& packet) { } casting = false; + castIsChannel = false; currentCastSpellId = 0; castTimeRemaining = 0.0f; @@ -17206,6 +17219,7 @@ void GameHandler::handleNewWorld(network::Packet& packet) { areaTriggerSuppressFirst_ = true; // first check just marks active triggers, doesn't fire stopAutoAttack(); casting = false; + castIsChannel = false; currentCastSpellId = 0; pendingGameObjectInteractGuid_ = 0; castTimeRemaining = 0.0f; diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 959fb6a1..c8759cbf 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -5462,19 +5462,28 @@ void GameScreen::renderCastBar(game::GameHandler& gameHandler) { ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.1f, 0.1f, 0.1f, 0.9f)); if (ImGui::Begin("##CastBar", nullptr, flags)) { - float progress = gameHandler.getCastProgress(); - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.8f, 0.6f, 0.2f, 1.0f)); + const bool channeling = gameHandler.isChanneling(); + // Channels drain right-to-left; regular casts fill left-to-right + float progress = channeling + ? (1.0f - gameHandler.getCastProgress()) + : gameHandler.getCastProgress(); + + ImVec4 barColor = channeling + ? ImVec4(0.3f, 0.6f, 0.9f, 1.0f) // blue for channels + : ImVec4(0.8f, 0.6f, 0.2f, 1.0f); // gold for casts + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, barColor); char overlay[64]; uint32_t currentSpellId = gameHandler.getCurrentCastSpellId(); - if (gameHandler.getCurrentCastSpellId() == 0) { + if (currentSpellId == 0) { snprintf(overlay, sizeof(overlay), "Opening... (%.1fs)", gameHandler.getCastTimeRemaining()); } else { const std::string& spellName = gameHandler.getSpellName(currentSpellId); + const char* verb = channeling ? "Channeling" : "Casting"; if (!spellName.empty()) snprintf(overlay, sizeof(overlay), "%s (%.1fs)", spellName.c_str(), gameHandler.getCastTimeRemaining()); else - snprintf(overlay, sizeof(overlay), "Casting... (%.1fs)", gameHandler.getCastTimeRemaining()); + snprintf(overlay, sizeof(overlay), "%s... (%.1fs)", verb, gameHandler.getCastTimeRemaining()); } ImGui::ProgressBar(progress, ImVec2(-1, 20), overlay); ImGui::PopStyleColor();