From 90843ea98963855626e82ac470c0ca6f176a866e Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 18 Mar 2026 05:35:23 -0700 Subject: [PATCH] fix: don't set releasedSpirit_ optimistically in releaseSpirit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Setting releasedSpirit_=true immediately on CMSG_REPOP_REQUEST raced with PLAYER_FLAGS field updates that arrive from the server before it processes the repop: the PLAYER_FLAGS handler saw wasGhost=true / nowGhost=false and fired the 'ghost cleared' path, wiping corpseMapId_ and corpseGuid_ — so the minimap skull marker and the Resurrect from Corpse dialog never appeared. Ghost state is now driven entirely by the server-confirmed PLAYER_FLAGS GHOST bit (and the login-as-ghost path), eliminating the race. --- src/game/game_handler.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 915de132..465e6c5c 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -14050,7 +14050,11 @@ void GameHandler::releaseSpirit() { } auto packet = RepopRequestPacket::build(); socket->send(packet); - releasedSpirit_ = true; + // Do NOT set releasedSpirit_ = true here. Setting it optimistically races + // with PLAYER_FLAGS field updates that arrive before the server processes + // CMSG_REPOP_REQUEST: the PLAYER_FLAGS handler sees wasGhost=true/nowGhost=false + // and fires the "ghost cleared" path, wiping corpseMapId_/corpseGuid_. + // Let the server drive ghost state via PLAYER_FLAGS_GHOST (field update path). selfResAvailable_ = false; // self-res window closes when spirit is released repopPending_ = true; lastRepopRequestMs_ = static_cast(now);