From 785f03a5996af8ac7c721f75a5c8c326ff70c989 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 29 Mar 2026 22:36:30 -0700 Subject: [PATCH] fix: stale GO interaction guard broke future casts; premature LOOT interfered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two remaining GO interaction bugs: 1. pendingGameObjectInteractGuid_ was never cleared after SMSG_SPELL_GO or SMSG_CAST_FAILED, leaving it stale. This suppressed CMSG_CANCEL_CAST for ALL subsequent spell casts (not just GO casts), causing the server to think the player was still casting when they weren't. 2. For chest-like GOs, CMSG_LOOT was sent simultaneously with CMSG_GAMEOBJ_USE. If the server starts a timed cast ("Opening"), the GO isn't lootable until the cast completes — the premature LOOT gets an empty response or is dropped, potentially corrupting the server's loot state. Now defers LOOT to handleSpellGo which sends it after the cast completes (via lastInteractedGoGuid_). --- src/game/game_handler.cpp | 11 +++++++---- src/game/spell_handler.cpp | 7 ++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 66a1d438..8bc19474 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -6226,10 +6226,13 @@ void GameHandler::performGameObjectInteractionNow(uint64_t guid) { lastInteractedGoGuid_ = guid; if (chestLike) { - // Chest-like GOs also need a CMSG_LOOT to open the loot window. - // Sent in the same frame: USE transitions the GO to lootable state, - // then LOOT requests the contents. - lootTarget(guid); + // Don't send CMSG_LOOT immediately — the server may start a timed cast + // (e.g., "Opening") and the GO isn't lootable until the cast finishes. + // Sending LOOT prematurely gets an empty response or is silently dropped, + // which can interfere with the server's loot state machine. + // Instead, handleSpellGo will send LOOT after the cast completes + // (using lastInteractedGoGuid_ set above). For instant-open chests + // (no cast), the server sends SMSG_LOOT_RESPONSE directly after USE. } else if (isMailbox) { LOG_INFO("Mailbox interaction: opening mail UI and requesting mail list"); mailboxGuid_ = guid; diff --git a/src/game/spell_handler.cpp b/src/game/spell_handler.cpp index d02b24be..c87e5089 100644 --- a/src/game/spell_handler.cpp +++ b/src/game/spell_handler.cpp @@ -794,6 +794,7 @@ void SpellHandler::handleCastFailed(network::Packet& packet) { currentCastSpellId_ = 0; castTimeRemaining_ = 0.0f; owner_.lastInteractedGoGuid_ = 0; + owner_.pendingGameObjectInteractGuid_ = 0; craftQueueSpellId_ = 0; craftQueueRemaining_ = 0; queuedSpellId_ = 0; @@ -952,11 +953,15 @@ void SpellHandler::handleSpellGo(network::Packet& packet) { currentCastSpellId_ = 0; castTimeRemaining_ = 0.0f; - // Gather node looting + // Gather node looting: re-send CMSG_LOOT now that the cast completed. if (wasInTimedCast && owner_.lastInteractedGoGuid_ != 0) { owner_.lootTarget(owner_.lastInteractedGoGuid_); owner_.lastInteractedGoGuid_ = 0; } + // Clear the GO interaction guard so future cancelCast() calls work + // normally. Without this, pendingGameObjectInteractGuid_ stays stale + // and suppresses CMSG_CANCEL_CAST for ALL subsequent spell casts. + owner_.pendingGameObjectInteractGuid_ = 0; if (owner_.spellCastAnimCallback_) { owner_.spellCastAnimCallback_(owner_.playerGuid, false, false);