From 5e83d04f4a2e5a607971c9f27a169211bc007d9b Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 29 Mar 2026 22:58:49 -0700 Subject: [PATCH] fix: GO cast timer fallback cleared state before SMSG_SPELL_GO arrived The client-side cast timer expires ~50-200ms before the server sends SMSG_SPELL_GO (float precision + frame timing). Previously the fallback called resetCastState() which set casting_=false and currentCastSpellId_ =0. When SMSG_SPELL_GO arrived moments later, wasInTimedCast evaluated to false (false && spellId==0), so the loot path (CMSG_LOOT via lastInteractedGoGuid_) was never taken. Quest chests never opened. Now the fallback skips resetCastState() for GO interaction casts, letting the cast bar sit at 100% until SMSG_SPELL_GO arrives and handles cleanup properly with wasInTimedCast=true. --- src/game/game_handler.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 46c76133..35faf216 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -1362,17 +1362,21 @@ void GameHandler::update(float deltaTime) { addSystemChatMessage("Interrupted."); } // Check if client-side cast timer expired (tick-down is in SpellHandler::updateTimers). - // For GO interaction casts, do NOT re-send CMSG_GAMEOBJ_USE — the server - // drives the interaction and sends SMSG_SPELL_GO + SMSG_LOOT_RESPONSE when - // the cast completes. Re-sending USE here sent a duplicate packet that - // confused the server's GO state machine, and resetCastState() then cleared - // lastInteractedGoGuid_ so the subsequent SMSG_SPELL_GO couldn't trigger loot. + // Two paths depending on whether this is a GO interaction cast: if (spellHandler_ && spellHandler_->casting_ && spellHandler_->castTimeRemaining_ <= 0.0f) { if (pendingGameObjectInteractGuid_ != 0) { - // Let the server finish — just clear the pending flag. + // GO interaction cast: do NOT call resetCastState() here. The server + // sends SMSG_SPELL_GO when the cast completes server-side (~50-200ms + // after the client timer expires due to float precision/frame timing). + // handleSpellGo checks `wasInTimedCast = casting_ && spellId == currentCastSpellId_` + // — if we clear those fields now, wasInTimedCast is false and the loot + // path (CMSG_LOOT via lastInteractedGoGuid_) never fires. + // Let the cast bar sit at 100% until SMSG_SPELL_GO arrives to clean up. pendingGameObjectInteractGuid_ = 0; + } else { + // Regular cast with no GO pending: clean up immediately. + spellHandler_->resetCastState(); } - spellHandler_->resetCastState(); } // Unit cast states and spell cooldowns are ticked by SpellHandler::updateTimers()