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);