From c20db42479124d82db99d4fce78fcc8f4405a0d8 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 21 Mar 2026 02:10:09 -0700 Subject: [PATCH] feat: fire UNIT_SPELLCAST_SENT and UNIT_SPELLCAST_STOP events Fire UNIT_SPELLCAST_SENT when the player initiates a spell cast (before server confirms), enabling cast bar addons like Quartz to show latency. Includes target name and spell ID as arguments. Fire UNIT_SPELLCAST_STOP whenever a cast bar should disappear: - On successful cast completion (SMSG_SPELL_GO) - On cast failure (SMSG_CAST_RESULT with error) - On spell interrupt (SMSG_SPELL_FAILURE/SMSG_SPELL_FAILED_OTHER) - On manual cast cancel These events are essential for cast bar replacement addons to properly track when casts begin and end. --- src/game/game_handler.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index d26a673a..00b6ac48 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -2304,8 +2304,10 @@ void GameHandler::handlePacket(network::Packet& packet) { : ("Spell cast failed (error " + std::to_string(castResult) + ")"); addUIError(errMsg); if (spellCastFailedCallback_) spellCastFailedCallback_(castResultSpellId); - if (addonEventCallback_) + if (addonEventCallback_) { addonEventCallback_("UNIT_SPELLCAST_FAILED", {"player", std::to_string(castResultSpellId)}); + addonEventCallback_("UNIT_SPELLCAST_STOP", {"player", std::to_string(castResultSpellId)}); + } MessageChatData msg; msg.type = ChatType::SYSTEM; msg.language = ChatLanguage::UNIVERSAL; @@ -3418,8 +3420,10 @@ void GameHandler::handlePacket(network::Packet& packet) { if (failGuid == playerGuid || failGuid == 0) unitId = "player"; else if (failGuid == targetGuid) unitId = "target"; else if (failGuid == focusGuid) unitId = "focus"; - if (!unitId.empty()) + if (!unitId.empty()) { addonEventCallback_("UNIT_SPELLCAST_INTERRUPTED", {unitId}); + addonEventCallback_("UNIT_SPELLCAST_STOP", {unitId}); + } } if (failGuid == playerGuid || failGuid == 0) { // Player's own cast failed — clear gather-node loot target so the @@ -18728,6 +18732,13 @@ void GameHandler::castSpell(uint32_t spellId, uint64_t targetGuid) { socket->send(packet); LOG_INFO("Casting spell: ", spellId, " on 0x", std::hex, target, std::dec); + // Fire UNIT_SPELLCAST_SENT for cast bar addons (fires on client intent, before server confirms) + if (addonEventCallback_) { + std::string targetName; + if (target != 0) targetName = lookupName(target); + addonEventCallback_("UNIT_SPELLCAST_SENT", {"player", targetName, std::to_string(spellId)}); + } + // Optimistically start GCD immediately on cast, but do not restart it while // already active (prevents timeout animation reset on repeated key presses). if (!isGCDActive()) { @@ -18756,6 +18767,8 @@ void GameHandler::cancelCast() { craftQueueRemaining_ = 0; queuedSpellId_ = 0; queuedSpellTarget_ = 0; + if (addonEventCallback_) + addonEventCallback_("UNIT_SPELLCAST_STOP", {"player"}); } void GameHandler::startCraftQueue(uint32_t spellId, int count) { @@ -19255,6 +19268,10 @@ void GameHandler::handleSpellGo(network::Packet& packet) { spellCastAnimCallback_(playerGuid, false, false); } + // Fire UNIT_SPELLCAST_STOP — cast bar should disappear + if (addonEventCallback_) + addonEventCallback_("UNIT_SPELLCAST_STOP", {"player", std::to_string(data.spellId)}); + // Spell queue: fire the next queued spell now that casting has ended if (queuedSpellId_ != 0) { uint32_t nextSpell = queuedSpellId_;