Core/Players: Add more granular teleport state tracking, fixes delayed cross map teleports

Closes #31782
Closes #31788
This commit is contained in:
Shauren
2026-04-12 15:06:38 +02:00
parent fa57c0325e
commit d538abf047
3 changed files with 53 additions and 48 deletions

View File

@@ -210,12 +210,9 @@ Player::Player(WorldSession* session) : Unit(true), m_sceneMgr(this)
m_atLoginFlags = AT_LOGIN_NONE;
mSemaphoreTeleport_Near = false;
mSemaphoreTeleport_Far = false;
m_DelayedOperations = 0;
m_bCanDelayTeleport = false;
m_bHasDelayedTeleport = false;
m_teleport_state = TeleportState::NotTeleporting;
m_teleport_options = TELE_TO_NONE;
m_teleportSpellId = 0;
m_newWorldCounter = 0;
@@ -1121,8 +1118,11 @@ void Player::Update(uint32 p_time)
//we should execute delayed teleports only for alive(!) players
//because we don't want player's ghost teleported from graveyard
if (IsHasDelayedTeleport() && IsAlive())
if ((GetTeleportState() == TeleportState::DelayedTeleport || GetTeleportState() == TeleportState::DelayedWorldPort) && IsAlive())
{
SetTeleportState(TeleportState::NotTeleporting); // skip state check inside TeleportTo
TeleportTo(m_teleport_dest, m_teleport_options, m_teleportSpellId);
}
}
void Player::Heartbeat()
@@ -1249,6 +1249,9 @@ bool Player::TeleportTo(TeleportLocation const& teleportLocation, TeleportToOpti
return false;
}
if (GetTeleportState() != TeleportState::NotTeleporting)
return false;
// preparing unsummon pet if lost (we must get pet before teleportation or will not find it later)
Pet* pet = GetPet();
@@ -1300,15 +1303,12 @@ bool Player::TeleportTo(TeleportLocation const& teleportLocation, TeleportToOpti
if (GetMapId() == teleportLocation.Location.GetMapId() && (!teleportLocation.InstanceId || GetInstanceId() == teleportLocation.InstanceId))
{
//lets reset far teleport flag if it wasn't reset during chained teleport
SetSemaphoreTeleportFar(false);
//setup delayed teleport flag
SetDelayedTeleportFlag(IsCanDelayTeleport());
SetTeleportState(TeleportState::Initiated);
//if teleport spell is cast in Unit::Update() func
//then we need to delay it until update process will be finished
if (IsHasDelayedTeleport())
if (IsCanDelayTeleport())
{
SetSemaphoreTeleportNear(true);
SetTeleportState(TeleportState::DelayedTeleport);
//lets save teleport destination for player
m_teleport_dest = teleportLocation;
m_teleport_options = options;
@@ -1337,16 +1337,13 @@ bool Player::TeleportTo(TeleportLocation const& teleportLocation, TeleportToOpti
// code for finish transfer called in WorldSession::HandleMovementOpcodes()
// at client packet CMSG_MOVE_TELEPORT_ACK
SetSemaphoreTeleportNear(true);
SetTeleportState(TeleportState::WaitingForTeleportAck);
// near teleport, triggering send CMSG_MOVE_TELEPORT_ACK from client at landing
if (!GetSession()->PlayerLogout())
SendTeleportPacket(m_teleport_dest);
}
else
{
if (IsBeingTeleportedFar())
return false;
if (GetClass() == CLASS_DEATH_KNIGHT && GetMapId() == 609 && !IsGameMaster() && !HasSpell(50977))
{
SendTransferAborted(teleportLocation.Location.GetMapId(), TRANSFER_ABORT_UNIQUE_MESSAGE, 1);
@@ -1371,16 +1368,13 @@ bool Player::TeleportTo(TeleportLocation const& teleportLocation, TeleportToOpti
!((oldmap->GetEntry()->CosmeticParentMapID != -1) ^ (oldmap->GetEntry()->CosmeticParentMapID != mEntry->CosmeticParentMapID))))
options &= ~TELE_TO_SEAMLESS;
//lets reset near teleport flag if it wasn't reset during chained teleports
SetSemaphoreTeleportNear(false);
//setup delayed teleport flag
SetDelayedTeleportFlag(IsCanDelayTeleport());
SetSemaphoreTeleportFar(true);
SetTeleportState(TeleportState::Initiated);
//if teleport spell is cast in Unit::Update() func
//then we need to delay it until update process will be finished
if (IsHasDelayedTeleport())
if (IsCanDelayTeleport())
{
//lets save teleport destination for player
SetTeleportState(TeleportState::DelayedWorldPort);
m_teleport_dest = teleportLocation;
m_teleport_options = options;
m_teleportSpellId = teleportSpellId;
@@ -1466,6 +1460,8 @@ bool Player::TeleportTo(TeleportLocation const& teleportLocation, TeleportToOpti
// if the player is saved before worldportack (at logout for example)
// this will be used instead of the current location in SaveToDB
SetTeleportState(TeleportState::WaitingForSuspendTokenResponse);
if (!GetSession()->PlayerLogout())
{
++m_newWorldCounter;
@@ -1475,10 +1471,6 @@ bool Player::TeleportTo(TeleportLocation const& teleportLocation, TeleportToOpti
suspendToken.Reason = options & TELE_TO_SEAMLESS ? 2 : 1;
SendDirectMessage(suspendToken.Write());
}
// move packet sent by client always after far teleport
// code for finish transfer to new map called in WorldSession::HandleMoveWorldportAckOpcode at client packet
SetSemaphoreTeleportFar(true);
}
return true;
}

View File

@@ -904,6 +904,19 @@ enum ArenaTeamInfoType
ARENA_TEAM_END = 7
};
enum class TeleportState
{
NotTeleporting,
Initiated,
// destination is on same map and instance
DelayedTeleport,
WaitingForTeleportAck,
// destination is on different map or different instance of the same map
DelayedWorldPort,
WaitingForSuspendTokenResponse,
WaitingForWorldPortAck
};
enum TeleportToOptions
{
TELE_TO_NONE = 0x00,
@@ -2381,14 +2394,16 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
void SetSkillPermBonus(uint32 pos, uint16 bonus) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::Skill).ModifyValue(&UF::SkillInfo::SkillPermBonus, pos), bonus); }
TeleportLocation& GetTeleportDest() { return m_teleport_dest; }
uint32 GetTeleportOptions() const { return m_teleport_options; }
TeleportState GetTeleportState() const { return m_teleport_state; }
void SetTeleportState(TeleportState state) { m_teleport_state = state; }
EnumFlag<TeleportToOptions> GetTeleportOptions() const { return m_teleport_options; }
int32 GetNewWorldCounter() const { return m_newWorldCounter; }
bool IsBeingTeleported() const { return IsBeingTeleportedNear() || IsBeingTeleportedFar(); }
bool IsBeingTeleportedNear() const { return mSemaphoreTeleport_Near; }
bool IsBeingTeleportedFar() const { return mSemaphoreTeleport_Far; }
bool IsBeingTeleportedSeamlessly() const { return IsBeingTeleportedFar() && m_teleport_options & TELE_TO_SEAMLESS; }
void SetSemaphoreTeleportNear(bool semphsetting) { mSemaphoreTeleport_Near = semphsetting; }
void SetSemaphoreTeleportFar(bool semphsetting) { mSemaphoreTeleport_Far = semphsetting; }
bool IsBeingTeleported() const { return m_teleport_state != TeleportState::NotTeleporting; }
bool IsBeingTeleportedNear() const { return m_teleport_state == TeleportState::DelayedTeleport
|| m_teleport_state == TeleportState::WaitingForTeleportAck; }
bool IsBeingTeleportedFar() const { return m_teleport_state == TeleportState::DelayedWorldPort
|| m_teleport_state == TeleportState::WaitingForSuspendTokenResponse
|| m_teleport_state == TeleportState::WaitingForWorldPortAck; }
void ProcessDelayedOperations();
void CheckAreaExplore();
@@ -3346,8 +3361,6 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
bool IsCanDelayTeleport() const { return m_bCanDelayTeleport; }
void SetCanDelayTeleport(bool setting) { m_bCanDelayTeleport = setting; }
bool IsHasDelayedTeleport() const { return m_bHasDelayedTeleport; }
void SetDelayedTeleportFlag(bool setting) { m_bHasDelayedTeleport = setting; }
void ScheduleDelayedOperation(uint32 operation) { if (operation < DELAYED_END) m_DelayedOperations |= operation; }
bool IsInstanceLoginGameMasterException() const;
@@ -3363,15 +3376,13 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
// Current teleport data
TeleportLocation m_teleport_dest;
TeleportState m_teleport_state;
TeleportToOptions m_teleport_options;
uint32 m_teleportSpellId;
int32 m_newWorldCounter;
bool mSemaphoreTeleport_Near;
bool mSemaphoreTeleport_Far;
uint32 m_DelayedOperations;
bool m_bCanDelayTeleport;
bool m_bHasDelayedTeleport;
std::unique_ptr<PetStable> m_petStable;

View File

@@ -47,6 +47,9 @@
void WorldSession::HandleMoveWorldportAckOpcode(WorldPackets::Movement::WorldPortResponse& /*packet*/)
{
if (_player->GetTeleportState() != TeleportState::WaitingForWorldPortAck)
return;
HandleMoveWorldportAck();
}
@@ -54,11 +57,8 @@ void WorldSession::HandleMoveWorldportAck()
{
Player* player = GetPlayer();
// ignore unexpected far teleports
if (!player->IsBeingTeleportedFar())
return;
bool seamlessTeleport = player->IsBeingTeleportedSeamlessly();
player->SetSemaphoreTeleportFar(false);
bool seamlessTeleport = player->GetTeleportOptions().HasFlag(TELE_TO_SEAMLESS);
player->SetTeleportState(TeleportState::NotTeleporting);
// get the teleport destination
TeleportLocation const& loc = player->GetTeleportDest();
@@ -188,7 +188,7 @@ void WorldSession::HandleMoveWorldportAck()
player->FinishTaxiFlight();
}
if (!player->IsAlive() && player->GetTeleportOptions() & TELE_REVIVE_AT_TELEPORT)
if (!player->IsAlive() && player->GetTeleportOptions().HasFlag(TELE_REVIVE_AT_TELEPORT))
player->ResurrectPlayer(0.5f);
// resurrect character at enter into instance where his corpse exist after add to map
@@ -252,7 +252,7 @@ void WorldSession::HandleMoveWorldportAck()
void WorldSession::HandleSuspendTokenResponse(WorldPackets::Movement::SuspendTokenResponse& /*suspendTokenResponse*/)
{
if (!_player->IsBeingTeleportedFar())
if (_player->GetTeleportState() != TeleportState::WaitingForSuspendTokenResponse)
return;
TeleportLocation const& loc = GetPlayer()->GetTeleportDest();
@@ -267,11 +267,13 @@ void WorldSession::HandleSuspendTokenResponse(WorldPackets::Movement::SuspendTok
WorldPackets::Movement::NewWorld packet;
packet.MapID = loc.Location.GetMapId();
packet.Loc.Pos = loc.Location;
packet.Reason = !_player->IsBeingTeleportedSeamlessly() ? NEW_WORLD_NORMAL : NEW_WORLD_SEAMLESS;
packet.Reason = !_player->GetTeleportOptions().HasFlag(TELE_TO_SEAMLESS) ? NEW_WORLD_NORMAL : NEW_WORLD_SEAMLESS;
packet.Counter = _player->GetNewWorldCounter();
SendPacket(packet.Write());
if (_player->IsBeingTeleportedSeamlessly())
_player->SetTeleportState(TeleportState::WaitingForWorldPortAck);
if (_player->GetTeleportOptions().HasFlag(TELE_TO_SEAMLESS))
HandleMoveWorldportAck();
}
@@ -281,13 +283,13 @@ void WorldSession::HandleMoveTeleportAck(WorldPackets::Movement::MoveTeleportAck
Player* plMover = _player->GetUnitBeingMoved()->ToPlayer();
if (!plMover || !plMover->IsBeingTeleportedNear())
if (!plMover || plMover->GetTeleportState() != TeleportState::WaitingForTeleportAck)
return;
if (packet.MoverGUID != plMover->GetGUID())
return;
plMover->SetSemaphoreTeleportNear(false);
plMover->SetTeleportState(TeleportState::NotTeleporting);
uint32 old_zone = plMover->GetZoneId();