diff --git a/src/map/battlefield.cpp b/src/map/battlefield.cpp index 5f8570c37ae..e66d41048c7 100644 --- a/src/map/battlefield.cpp +++ b/src/map/battlefield.cpp @@ -595,6 +595,17 @@ bool CBattlefield::RemoveEntity(CBaseEntity* PEntity, uint8 leavecode) m_EnteredPlayers.erase(PEntity->id); + // A player who leaves under their own power is back outside at the burning circle, so the + // battlefield recovery marker is no longer needed. Every legitimate exit (running out, + // and exiting after a win or loss) routes through EXIT, so clearing it here prevents a + // later relog from wrongly ejecting a player who already left. The disconnect teardown + // path uses WARPDC and deliberately keeps the marker, so a player who drops during the + // post-win loot window can still be recovered on relog. + if (leavecode == BATTLEFIELD_LEAVE_CODE_EXIT) + { + PChar->setCharVar("battlefieldRecoveryId", 0); + } + if (leavecode != 255) { // todo: probably shouldnt hardcode this @@ -1031,6 +1042,16 @@ void CBattlefield::setPlayerEntered(CCharEntity* PChar, bool entered) } effect->SetTier(entered ? 1 : 0); + + // Persist the battlefield id when the player enters the arena. The Battlefield status + // effect is removed during the win/cleanup sequence, so if a player disconnects during + // the post-win loot window the effect no longer exists on relog and the normal eject + // logic in CZone::CharZoneIn cannot detect them. This charvar survives the relog and is + // used there to send them to the exit. It is cleared on any normal (non-disconnect) leave. + if (entered) + { + PChar->setCharVar("battlefieldRecoveryId", effect->GetPower()); + } } bool CBattlefield::hasPlayerEntered(CCharEntity* PChar) diff --git a/src/map/zone.cpp b/src/map/zone.cpp index 21827700952..debf5f1c749 100644 --- a/src/map/zone.cpp +++ b/src/map/zone.cpp @@ -1091,6 +1091,25 @@ void CZone::CharZoneIn(CCharEntity* PChar) if (m_BattlefieldHandler) { + // A player who disconnects inside a battlefield arena (for example during the post-win + // loot window) loses their Battlefield status effect during the cleanup sequence, so on + // relog the orphan handling below cannot detect them and they are left stuck in the arena. + // The recovery charvar set on entry survives the relog; restore a minimal Battlefield + // effect from it so the existing orphan-eject path can send them to the exit, then consume + // the marker. + if (!PChar->StatusEffectContainer->HasStatusEffectByFlag(xi::StatusEffectFlag::Confrontation) && + PChar->getCharVar("battlefieldRecoveryId") != 0) + { + const auto battlefieldId = static_cast(PChar->getCharVar("battlefieldRecoveryId")); + PChar->StatusEffectContainer->AddStatusEffectSilent( + xi::StatusEffect::Battlefield, static_cast(xi::StatusEffect::Battlefield), battlefieldId, 0s, 0s, 0, 0); + if (auto* effect = PChar->StatusEffectContainer->GetStatusEffect(xi::StatusEffect::Battlefield)) + { + effect->SetTier(1); + } + PChar->setCharVar("battlefieldRecoveryId", 0); + } + auto* PBattlefield = m_BattlefieldHandler->GetBattlefield(PChar, true); if (PBattlefield != nullptr && PChar->StatusEffectContainer->HasStatusEffectByFlag(xi::StatusEffectFlag::Confrontation)) { @@ -1174,6 +1193,17 @@ void CZone::CharZoneOut(CCharEntity* PChar) { TracyZoneScoped; + // Clear the battlefield relog-recovery marker when the player genuinely zones out + // (shuttingDown == 2 is a normal zone transition). On a disconnect/logout (shuttingDown + // == 1) we keep it, so a player stranded inside a battlefield arena can be ejected on + // their next zone-in (see CZone::CharZoneIn). This is the only signal that reliably + // separates a proper exit from a disconnect - the battlefield leavecode does not, since + // the WIN cleanup runs for both. + if (PChar->PSession != nullptr && PChar->PSession->shuttingDown == 2 && PChar->getCharVar("battlefieldRecoveryId") != 0) + { + PChar->setCharVar("battlefieldRecoveryId", 0); + } + for (const auto& triggerArea : m_triggerAreaList) { if (PChar->isInTriggerArea(triggerArea->getTriggerAreaID()))