Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/game/Object/Camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,14 +162,17 @@ void Camera::ResetView(bool update_far_sight_field /*= true*/)

/**
* @brief Handles the camera being added to the world.
*
* @param batch Shared initial-login data for the owner camera, or null for an
* ordinary visibility rebuild.
*/
void Camera::Event_AddedToWorld()
void Camera::Event_AddedToWorld(InitialWorldUpdateBatch* batch)
{
GridType* grid = m_source->GetViewPoint().m_grid;
MANGOS_ASSERT(grid);
grid->AddWorldObject(this);

UpdateVisibilityForOwner();
UpdateVisibilityForOwnerInBatch(batch);
}

/**
Expand Down Expand Up @@ -221,6 +224,15 @@ void Camera::UpdateVisibilityOf(WorldObject* target, UpdateData& data, std::set<
* @brief Rebuilds visibility for the camera owner around the current source.
*/
void Camera::UpdateVisibilityForOwner()
{
UpdateVisibilityForOwnerInBatch(NULL);
}

/**
* Rebuilds owner visibility while optionally appending create blocks to the
* initial self/transport batch instead of sending a second update packet.
*/
void Camera::UpdateVisibilityForOwnerInBatch(InitialWorldUpdateBatch* batch)
{
// Honor a per-viewpoint visibility distance override (e.g. the cinematic
// flyover body widens the populate radius); otherwise use the map default.
Expand All @@ -230,7 +242,7 @@ void Camera::UpdateVisibilityForOwner()
visibilityDistance = m_source->GetMap()->GetVisibilityDistance();
}

MaNGOS::VisibleNotifier notifier(*this);
MaNGOS::VisibleNotifier notifier(*this, batch);
Cell::VisitAllObjects(m_source, notifier, visibilityDistance, false);

// The other side of a vessel's boundary. A deck and the shore it sails past are two
Expand Down
15 changes: 12 additions & 3 deletions src/game/Object/Camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class WorldObject;
class UpdateData;
class WorldPacket;
class Player;
class InitialWorldUpdateBatch;

/// Camera - object-receiver. Receives broadcast packets from nearby worldobjects, object visibility changes and sends them to client
class Camera
Expand Down Expand Up @@ -74,7 +75,7 @@ class Camera

private:
// called when viewpoint changes visibility state
void Event_AddedToWorld();
void Event_AddedToWorld(InitialWorldUpdateBatch* batch);
void Event_RemovedFromWorld();
void Event_Moved();
void Event_ViewPointVisibilityChanged();
Expand All @@ -83,6 +84,7 @@ class Camera
WorldObject* m_source;

void UpdateForCurrentViewPoint();
void UpdateVisibilityForOwnerInBatch(InitialWorldUpdateBatch* batch);

public:
GridReference<Camera>& GetGridRef()
Expand Down Expand Up @@ -128,10 +130,17 @@ class ViewPoint
bool hasViewers() const { return !m_cameras.empty(); }

// these events are called when viewpoint changes visibility state
void Event_AddedToWorld(GridType* grid)
void Event_AddedToWorld(GridType* grid, Player* batchOwner = NULL,
InitialWorldUpdateBatch* batch = NULL)
{
m_grid = grid;
CameraCall(&Camera::Event_AddedToWorld);
// A viewpoint may have several cameras. Only the logging-in
// player's own camera may consume the shared initial batch.
for (CameraList::iterator itr = m_cameras.begin(); itr != m_cameras.end();)
{
Camera* c = *(itr++);
c->Event_AddedToWorld(c->GetOwner() == batchOwner ? batch : NULL);
}
}

void Event_RemovedFromWorld()
Expand Down
184 changes: 163 additions & 21 deletions src/game/Object/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "Utilities/MathDefines.h"
#include "Utilities/PackedValues.h"
#include "Player.h"
#include "LoginEffectPackets.h"
#include "Language.h"
#include "Database/DatabaseEnv.h"
#include "Log.h"
Expand Down Expand Up @@ -88,6 +89,67 @@

#include <cmath>

namespace
{
// Spell 836 is deliberately split around the initial object batch. The
// event emits START after presentation and GO on the next eligible tick.
class LoginEffectEvent final : public BasicEvent
{
public:
explicit LoginEffectEvent(Player& player) : m_player(player)
{
}

bool Execute(uint64 eTime, uint32) override
{
std::optional<LoginEffectPhase> phase =
m_state.TakeNext(m_player.IsInWorld());
if (!phase)
{
return true;
}

WorldPacket packet = *phase == LoginEffectPhase::Start ?
LoginEffectPackets::BuildStart(
m_player.GetObjectGuid().GetRawValue()) :
LoginEffectPackets::BuildGo(
m_player.GetObjectGuid().GetRawValue());
m_player.SendMessageToSet(&packet, true);

if (*phase == LoginEffectPhase::Start)
{
m_player.m_Events.AddEvent(this,
eTime + LoginEffectDelayBefore(LoginEffectPhase::Go),
false);
return false;
}
return true;
}

private:
Player& m_player;
LoginEffectSequenceState m_state;
};

class LoginCinematicRootTimeoutEvent final : public BasicEvent
{
public:
explicit LoginCinematicRootTimeoutEvent(Player& player)
: m_player(player)
{
}

bool Execute(uint64, uint32) override
{
m_player.ReleaseLoginCinematicRoot();
return true;
}

private:
Player& m_player;
};
}

#define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS)

#define PLAYER_SKILL_INDEX(x) (PLAYER_SKILL_INFO_1_1 + ((x)*3))
Expand Down Expand Up @@ -698,6 +760,10 @@ Player::~Player()
*/
void Player::CleanupsBeforeDelete()
{
// Event teardown destroys the timers; clear their independent root token
// before any later cleanup can attempt to release it.
m_loginCinematicRootOwnership.Clear();

// Stop cinematic flyover if active (must happen before camera dtor)
if (m_cinematicFlyover && m_cinematicFlyover->IsActive())
{
Expand Down Expand Up @@ -3869,10 +3935,19 @@ void Player::SendUpdateWorldState(uint32 Field, uint32 Value)
* @param zoneid The zone identifier used to select world states.
*/
void Player::SendInitWorldStates(uint32 zoneid)
{
SendInitWorldStates(GetMapId(), zoneid);
}

/**
* Sends initial world states for an explicit client-visible map anchor.
* Transport passengers live on a deck map internally while the client still
* renders the world map sailed by the vessel.
*/
void Player::SendInitWorldStates(uint32 mapid, uint32 zoneid)
{
// data depends on zoneid/mapid...
BattleGround* bg = GetBattleGround();
uint32 mapid = GetMapId();

DEBUG_LOG("Sending SMSG_INIT_WORLD_STATES to Map:%u, Zone: %u", mapid, zoneid);

Expand Down Expand Up @@ -5317,8 +5392,13 @@ void Player::SetComboPoints()



/* Called by WorldSession::HandlePlayerLogin */
void Player::SendInitialPacketsBeforeAddToMap()
/**
* Sends the map-independent login preamble.
*
* @param deferLoginTimeSpeed Keep time/speed for the post-admission retail
* ordering instead of sending it from this legacy position.
*/
void Player::SendInitialPacketsBeforeAddToMap(bool deferLoginTimeSpeed)
{
/** This packet seems useless...
* TODO: Work out if we need SMSG_SET_REST_START */
Expand All @@ -5344,12 +5424,10 @@ void Player::SendInitialPacketsBeforeAddToMap()
/* Update player's honour information (does not send anything) */
UpdateHonor();

const float game_time = 0.01666667f; // Game speed

data.Initialize(SMSG_LOGIN_SETTIMESPEED, 4 + 4);
data << uint32(secsToTimeBitFields(sWorld.GetGameTime()));
data << game_time; // Float is 4 bytes here
GetSession()->SendPacket(&data);
if (!deferLoginTimeSpeed)
{
SendLoginTimeSpeed();
}

// Set fly flag if player is on a taxi to avoid falling to the ground
if (IsTaxiFlying())
Expand All @@ -5361,9 +5439,54 @@ void Player::SendInitialPacketsBeforeAddToMap()
SetMover(this);
}

/**
* @brief Sends map-dependent initialization packets after the player is added to the world.
*/
/** Isolates SMSG_LOGIN_SETTIMESPEED so entry ordering can defer it unchanged. */
void Player::SendLoginTimeSpeed()
{
WorldPacket data(SMSG_LOGIN_SETTIMESPEED, 8);
data << uint32(secsToTimeBitFields(sWorld.GetGameTime()));
data << float(0.01666667f);
GetSession()->SendPacket(&data);
}

/** Queues the visible START/GO half after the initial object batch is sent. */
void Player::ScheduleLoginEffect()
{
m_Events.AddEvent(new LoginEffectEvent(*this),
m_Events.CalculateTime(
LoginEffectDelayBefore(LoginEffectPhase::Start)));
}

void Player::BeginLoginCinematicRoot()
{
if (!m_loginCinematicRootOwnership.Claim())
{
return;
}

// Normal cinematic completion releases first; this timer is the bounded
// failsafe for clients that never send completion.
m_Events.AddEvent(new LoginCinematicRootTimeoutEvent(*this),
m_Events.CalculateTime(LOGIN_CINEMATIC_ROOT_TIMEOUT_MS));
SetRoot(true);
}

void Player::ReleaseLoginCinematicRoot()
{
// Retain the token while out of world; consuming it there would lose the
// only later opportunity to send the matching unroot.
if (!m_loginCinematicRootOwnership.ReleaseOnce(IsInWorld()))
{
return;
}

if (!HasAuraType(SPELL_AURA_MOD_STUN) &&
!HasAuraType(SPELL_AURA_MOD_ROOT))
{
// This path owns only the cinematic root; active aura roots win.
SetRoot(false);
}
}

/**
* @brief Where this player is, for the questions the WORLD answers: a graveyard, an area
* trigger, anything looked up against terrain the client shipped.
Expand Down Expand Up @@ -5511,15 +5634,36 @@ void Player::UpdateLiftMinions()
CONTROLLED_PET | CONTROLLED_MINIPET | CONTROLLED_GUARDIANS);
}

void Player::SendInitialPacketsAfterAddToMap()
/**
* Sends map-dependent initialization after committed world entry.
*
* A non-null context means the entry hook already sent world states and
* time/speed before the object batch. Null preserves the legacy teleport path.
*/
void Player::SendInitialPacketsAfterAddToMap(InitialWorldEntryContext const* initialEntry)
{
/* Update players zone */
uint32 newzone, newarea;
GetTerrain()->GetZoneAndAreaId(newzone, newarea, Where().X(), Where().Y(), Where().Z());
UpdateZone(newzone, newarea); // This calls SendInitWorldStates
if (initialEntry)
{
UpdateZone(initialEntry->zoneId, initialEntry->areaId,
!initialEntry->initialWorldStatesSent);
if (initialEntry->cinematicStarted)
{
BeginLoginCinematicRoot();
}
// CAST_FAILED was part of the pre-batch hook; START/GO are intentionally
// deferred until the client has received its initial object world.
ScheduleLoginEffect();
}
else
{
/* Update players zone */
uint32 newzone, newarea;
GetTerrain()->GetZoneAndAreaId(newzone, newarea, Where().X(), Where().Y(), Where().Z());
UpdateZone(newzone, newarea); // This calls SendInitWorldStates

/* Login effect spell */
CastSpell(this, 836, true); // LOGINEFFECT
/* Login effect spell */
CastSpell(this, 836, true); // LOGINEFFECT
}

/** Sets aura effects that need to be sent after the player is added to the map
* We use SendMessageToSet so that it's sent to everyone, including the player
Expand Down Expand Up @@ -6936,5 +7080,3 @@ void Player::KnockBackFrom(Unit* target, float horizontalSpeed, float verticalSp
float angle = this == target ? Where().Facing() + M_PI_F : target->Where().BearingTo(this->Where());
GetSession()->SendKnockBack(angle, horizontalSpeed, verticalSpeed);
}


18 changes: 15 additions & 3 deletions src/game/Object/Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
#include "PetMgr.h" // held by value on Player; owns stable-slot count + temp-unsummon pet number
#include "BattleGround.h"
#include "DBCStores.h"
#include "InitialWorldEntry.h"
#include "SharedDefines.h"
#include "Chat.h"
#include "GMTicketMgr.h"
Expand Down Expand Up @@ -1189,8 +1190,11 @@ class Player : public Unit
return Where().Z() < m_lastFallZ;
}

void SendInitialPacketsBeforeAddToMap(); // Send initial packets before adding the player to the map
void SendInitialPacketsAfterAddToMap(); // Send initial packets after adding the player to the map
// A context is supplied only for the initial login lifecycle; null keeps
// ordinary teleport callers on their established packet sequence.
void SendInitialPacketsBeforeAddToMap(bool deferLoginTimeSpeed = false);
void SendInitialPacketsAfterAddToMap(InitialWorldEntryContext const* initialEntry = nullptr);
void SendLoginTimeSpeed();
void SendInstanceResetWarning(uint32 mapid, uint32 time); // Send instance reset warning

// Get the NPC if the player can interact with it
Expand Down Expand Up @@ -2498,7 +2502,7 @@ class Player : public Unit
void SetFFAPvP(bool state);

// Update the player's zone
void UpdateZone(uint32 newZone, uint32 newArea);
void UpdateZone(uint32 newZone, uint32 newArea, bool sendInitialWorldStates = true);

// Update the player's area
void UpdateArea(uint32 newArea);
Expand Down Expand Up @@ -3178,6 +3182,7 @@ class Player : public Unit
void CastItemUseSpell(Item* item, SpellCastTargets const& targets);

void SendInitWorldStates(uint32 zone);
void SendInitWorldStates(uint32 mapId, uint32 zone);
void SendUpdateWorldState(uint32 Field, uint32 Value);

// Send a direct message to the client
Expand Down Expand Up @@ -3551,6 +3556,11 @@ class Player : public Unit
// Set the cinematic flyover manager
void SetCinematicFlyover(std::unique_ptr<CinematicFlyover> flyover) { m_cinematicFlyover = std::move(flyover); }

// Initial-login presentation state; unrelated spell roots are not owned here.
void ScheduleLoginEffect();
void BeginLoginCinematicRoot();
void ReleaseLoginCinematicRoot();

// Forced speed changes
uint8 m_forced_speed_changes[MAX_MOVE_TYPE];

Expand Down Expand Up @@ -4060,6 +4070,8 @@ class Player : public Unit
// Cinematic flyover manager (optional, for first-login intro visibility)
std::unique_ptr<CinematicFlyover> m_cinematicFlyover;

LoginCinematicRootOwnership m_loginCinematicRootOwnership;

// Countdown (ms) for the periodic observer-side visibility sweep
uint32 m_visibilityObserverSweepTimer;

Expand Down
Loading
Loading