From 419af5796e26d5dc9e831cfe1b626308097a80bc Mon Sep 17 00:00:00 2001 From: Andrew <47818697+Nyeriah@users.noreply.github.com> Date: Thu, 28 May 2026 21:18:24 -0300 Subject: [PATCH] feat(Core/Battlefield): grace period for mid-war logout/relog (#26013) Co-authored-by: Yaki Khadafi Co-authored-by: Xfurry Co-authored-by: Claude Opus 4.7 --- src/server/game/Battlefield/Battlefield.cpp | 48 ++++++++++++++++++++- src/server/game/Battlefield/Battlefield.h | 9 ++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/server/game/Battlefield/Battlefield.cpp b/src/server/game/Battlefield/Battlefield.cpp index c4b6d46c3c..0492d9e0a2 100644 --- a/src/server/game/Battlefield/Battlefield.cpp +++ b/src/server/game/Battlefield/Battlefield.cpp @@ -93,6 +93,8 @@ void Battlefield::HandlePlayerEnterZone(Player* player, uint32 /*zone*/) // any team-based battlefield containers (such as player lists or queues) are updated. sScriptMgr->OnBattlefieldPlayerEnterZone(this, player); + TryRejoinAfterLogout(player); // relog: auto-rejoin, skip invite below + // Xinef: do not invite players on taxi if (!player->IsInFlight()) { @@ -124,12 +126,20 @@ void Battlefield::HandlePlayerEnterZone(Player* player, uint32 /*zone*/) void Battlefield::HandlePlayerLeaveZone(Player* player, uint32 /*zone*/) { + // Logout still runs full leave-war cleanup, but marks the player for grace-window auto-rejoin. + bool const isLogout = player->GetSession() && player->GetSession()->PlayerLogout(); + if (IsWarTime()) { // If the player is participating to the battle if (PlayersInWar[player->GetTeamId()].erase(player->GetGUID())) { - player->GetSession()->SendBfLeaveMessage(BattleId); + if (isLogout) + LogoutGracePlayers[player->GetTeamId()][player->GetGUID()] = + GameTime::GetGameTime().count() + LOGOUT_GRACE_SECONDS; + else + player->GetSession()->SendBfLeaveMessage(BattleId); + if (Group* group = player->GetGroup()) // Remove the player from the raid group if (group->isBFGroup()) group->RemoveMember(player->GetGUID()); @@ -321,6 +331,7 @@ void Battlefield::StartBattle() { PlayersInWar[team].clear(); Groups[team].clear(); + LogoutGracePlayers[team].clear(); } Timer = BattleTime; @@ -588,6 +599,41 @@ bool Battlefield::AddOrSetPlayerToCorrectBfGroup(Player* player) return true; } +void Battlefield::TryRejoinAfterLogout(Player* player) +{ + ObjectGuid const guid = player->GetGUID(); + time_t const now = GameTime::GetGameTime().count(); + + // Consume the marker and honor its grace window (check both teams; team may have changed). + bool pending = false; + for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) + if (auto itr = LogoutGracePlayers[team].find(guid); itr != LogoutGracePlayers[team].end()) + { + pending = itr->second > now; + LogoutGracePlayers[team].erase(itr); + } + + // Vacancy gate mirrors HandlePlayerEnterZone (full team -> queue path). Pre-hook: + // we can't abort after JoinWar, which may already have mutated module state. + if (!pending || !IsWarTime() || !HasWarVacancy(player->GetTeamId())) + return; + + if (Group* current = player->GetGroup()) + if (current->isBGGroup() || current->isBFGroup()) + return; + + // Rejoin via the normal join path: firing JoinWar lets modules rebuild + // per-session (Player*-keyed) state and pick the team before the raid bind. + sScriptMgr->OnBattlefieldPlayerJoinWar(this, player); + + if (AddOrSetPlayerToCorrectBfGroup(player)) + { + player->GetSession()->SendBfEntered(BattleId); + PlayersInWar[player->GetTeamId()].insert(guid); + OnPlayerJoinWar(player); + } +} + BfGraveyard* Battlefield::GetGraveyardById(uint32 id) const { if (id < GraveyardList.size()) diff --git a/src/server/game/Battlefield/Battlefield.h b/src/server/game/Battlefield/Battlefield.h index c7cd9a9fee..f8f5c0c931 100644 --- a/src/server/game/Battlefield/Battlefield.h +++ b/src/server/game/Battlefield/Battlefield.h @@ -278,6 +278,11 @@ public: /// Force player to join a battlefield group bool AddOrSetPlayerToCorrectBfGroup(Player* player); + /// Auto-rejoin a player who relogged within the grace window after a mid-war + /// logout, via the normal join hooks. No-op without a pending logout marker. + /// Called from HandlePlayerEnterZone (which fires on the post-login zone set). + void TryRejoinAfterLogout(Player* player); + // Graveyard methods // Find which graveyard the player must be teleported to to be resurrected by spiritguide GraveyardStruct const* GetClosestGraveyard(Player* player); @@ -381,6 +386,10 @@ protected: GuidUnorderedSet PlayersInWar[PVP_TEAMS_COUNT]; // Players in WG combat PlayerTimerMap InvitedPlayers[PVP_TEAMS_COUNT]; PlayerTimerMap PlayersWillBeKick[PVP_TEAMS_COUNT]; + // Mid-war logouts: GUID -> timestamp until which a relog auto-rejoins the war. + PlayerTimerMap LogoutGracePlayers[PVP_TEAMS_COUNT]; + + static constexpr uint32 LOGOUT_GRACE_SECONDS = 120; // relog auto-rejoin window // Variables that must exist for each battlefield uint32 TypeId; // See enum BattlefieldTypes