summaryrefslogtreecommitdiffstats
path: root/src/Entities
diff options
context:
space:
mode:
authorTiger Wang <ziwei.tiger@hotmail.co.uk>2016-12-18 19:31:44 +0100
committerTiger Wang <ziwei.tiger@hotmail.co.uk>2016-12-18 19:31:44 +0100
commit1f109febbaae43612c8f47058055ce3b101b3b12 (patch)
tree92ff2e9a36cc0c78bed7f7ec038bfa24fb5df433 /src/Entities
parentAdded cWorld:SetSpawn() API and Lua binding (#3316) (diff)
downloadcuberite-1f109febbaae43612c8f47058055ce3b101b3b12.tar
cuberite-1f109febbaae43612c8f47058055ce3b101b3b12.tar.gz
cuberite-1f109febbaae43612c8f47058055ce3b101b3b12.tar.bz2
cuberite-1f109febbaae43612c8f47058055ce3b101b3b12.tar.lz
cuberite-1f109febbaae43612c8f47058055ce3b101b3b12.tar.xz
cuberite-1f109febbaae43612c8f47058055ce3b101b3b12.tar.zst
cuberite-1f109febbaae43612c8f47058055ce3b101b3b12.zip
Diffstat (limited to '')
-rw-r--r--src/Entities/Entity.cpp302
-rw-r--r--src/Entities/Entity.h53
-rw-r--r--src/Entities/Pawn.cpp2
-rw-r--r--src/Entities/Pawn.h2
-rw-r--r--src/Entities/Player.cpp544
-rw-r--r--src/Entities/Player.h81
6 files changed, 495 insertions, 489 deletions
diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp
index 2adbc3142..8d74ee99a 100644
--- a/src/Entities/Entity.cpp
+++ b/src/Entities/Entity.cpp
@@ -41,7 +41,6 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d
m_LastPosition(a_X, a_Y, a_Z),
m_EntityType(a_EntityType),
m_World(nullptr),
- m_IsWorldChangeScheduled(false),
m_IsFireproof(false),
m_TicksSinceLastBurnDamage(0),
m_TicksSinceLastLavaDamage(0),
@@ -52,6 +51,7 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d
m_IsSubmerged(false),
m_AirLevel(0),
m_AirTickTimer(0),
+ m_PortalCooldownData({0, false, true}),
m_TicksAlive(0),
m_IsTicking(false),
m_ParentChunk(nullptr),
@@ -77,9 +77,6 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d
cEntity::~cEntity()
{
-
- // Before deleting, the entity needs to have been removed from the world, if ever added
- ASSERT((m_World == nullptr) || !m_World->HasEntity(m_UniqueID));
ASSERT(!IsTicking());
/*
@@ -201,7 +198,7 @@ void cEntity::SetParentChunk(cChunk * a_Chunk)
-cChunk * cEntity::GetParentChunk()
+cChunk * cEntity::GetParentChunk() const
{
return m_ParentChunk;
}
@@ -1369,36 +1366,13 @@ void cEntity::DetectCacti(void)
-void cEntity::ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_SetPortalCooldown)
-{
- m_NewWorld = a_World;
- m_NewWorldPosition = a_NewPosition;
- m_IsWorldChangeScheduled = true;
- m_WorldChangeSetPortalCooldown = a_SetPortalCooldown;
-}
-
-
-
-
bool cEntity::DetectPortal()
{
- // If somebody scheduled a world change with ScheduleMoveToWorld, change worlds now.
- if (m_IsWorldChangeScheduled)
+ if (!m_PortalCooldownData.m_PositionValid)
{
- m_IsWorldChangeScheduled = false;
-
- if (m_WorldChangeSetPortalCooldown)
- {
- // Delay the portal check.
- m_PortalCooldownData.m_TicksDelayed = 0;
- m_PortalCooldownData.m_ShouldPreventTeleportation = true;
- }
-
- MoveToWorld(m_NewWorld, false, m_NewWorldPosition);
- return true;
+ return false;
}
-
- if (GetWorld()->GetDimension() == dimOverworld)
+ else if (GetWorld()->GetDimension() == dimOverworld)
{
if (GetWorld()->GetLinkedNetherWorldName().empty() && GetWorld()->GetLinkedEndWorldName().empty())
{
@@ -1412,7 +1386,7 @@ bool cEntity::DetectPortal()
return false;
}
- int X = POSX_TOINT, Y = POSY_TOINT, Z = POSZ_TOINT;
+ const int X = POSX_TOINT, Y = POSY_TOINT, Z = POSZ_TOINT;
if ((Y > 0) && (Y < cChunkDef::Height))
{
switch (GetWorld()->GetBlock(X, Y, Z))
@@ -1425,62 +1399,56 @@ bool cEntity::DetectPortal()
return false;
}
- if (IsPlayer() && !(reinterpret_cast<cPlayer *>(this))->IsGameModeCreative() && (m_PortalCooldownData.m_TicksDelayed != 80))
+ if (
+ IsPlayer() &&
+ // !reinterpret_cast<cPlayer *>(this)->IsGameModeCreative() && // TODO: fix portal travel prevention - client sends outdated position data throwing off checks
+ (m_PortalCooldownData.m_TicksDelayed != 80)
+ )
{
// Delay teleportation for four seconds if the entity is a non-creative player
m_PortalCooldownData.m_TicksDelayed++;
return false;
}
- m_PortalCooldownData.m_TicksDelayed = 0;
+
+ m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn
+ m_PortalCooldownData.m_PositionValid = false;
if (GetWorld()->GetDimension() == dimNether)
{
- if (GetWorld()->GetLinkedOverworldName().empty())
- {
- return false;
- }
-
- m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn
+ cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
+ ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
- if (IsPlayer())
+ if (GetWorld()->GetLinkedOverworldName().empty() || !OnPreWorldTravel(*TargetWorld))
{
- // Send a respawn packet before world is loaded / generated so the client isn't left in limbo
- (reinterpret_cast<cPlayer *>(this))->GetClientHandle()->SendRespawn(dimOverworld);
+ return false;
}
Vector3d TargetPos = GetPosition();
TargetPos.x *= 8.0;
TargetPos.z *= 8.0;
- cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
- ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
LOGD("Jumping nether -> overworld");
- new cNetherPortalScanner(this, TargetWorld, TargetPos, 256);
+ new cNetherPortalScanner(GetUniqueID(), GetWorld()->GetDimension(), TargetWorld, TargetPos, 256);
+
return true;
}
else
{
- if (GetWorld()->GetLinkedNetherWorldName().empty())
- {
- return false;
- }
-
- m_PortalCooldownData.m_ShouldPreventTeleportation = true;
+ cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName());
+ ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
- if (IsPlayer())
+ if (GetWorld()->GetLinkedNetherWorldName().empty() || !OnPreWorldTravel(*TargetWorld))
{
- reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterPortal);
- reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(dimNether);
+ return false;
}
Vector3d TargetPos = GetPosition();
TargetPos.x /= 8.0;
TargetPos.z /= 8.0;
- cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedNetherWorldName());
- ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
LOGD("Jumping overworld -> nether");
- new cNetherPortalScanner(this, TargetWorld, TargetPos, 128);
+ new cNetherPortalScanner(GetUniqueID(), GetWorld()->GetDimension(), TargetWorld, TargetPos, 128);
+
return true;
}
}
@@ -1491,6 +1459,9 @@ bool cEntity::DetectPortal()
return false;
}
+ m_PortalCooldownData.m_ShouldPreventTeleportation = true;
+ m_PortalCooldownData.m_PositionValid = false;
+
if (GetWorld()->GetDimension() == dimEnd)
{
@@ -1499,18 +1470,10 @@ bool cEntity::DetectPortal()
return false;
}
- m_PortalCooldownData.m_ShouldPreventTeleportation = true;
-
- if (IsPlayer())
- {
- cPlayer * Player = reinterpret_cast<cPlayer *>(this);
- Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z);
- Player->GetClientHandle()->SendRespawn(dimOverworld);
- }
-
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedOverworldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
- return MoveToWorld(TargetWorld, false);
+
+ return MoveToWorld(*TargetWorld, Vector3d(TargetWorld->GetSpawnX(), TargetWorld->GetSpawnY(), TargetWorld->GetSpawnZ()));
}
else
{
@@ -1519,27 +1482,18 @@ bool cEntity::DetectPortal()
return false;
}
- m_PortalCooldownData.m_ShouldPreventTeleportation = true;
-
- if (IsPlayer())
- {
- reinterpret_cast<cPlayer *>(this)->AwardAchievement(achEnterTheEnd);
- reinterpret_cast<cPlayer *>(this)->GetClientHandle()->SendRespawn(dimEnd);
- }
-
cWorld * TargetWorld = cRoot::Get()->GetWorld(GetWorld()->GetLinkedEndWorldName());
ASSERT(TargetWorld != nullptr); // The linkage checker should have prevented this at startup. See cWorld::start()
- return MoveToWorld(TargetWorld, false);
- }
+ return MoveToWorld(*TargetWorld, GetPosition());
+ }
}
default: break;
}
}
// Allow portals to work again
- m_PortalCooldownData.m_ShouldPreventTeleportation = false;
- m_PortalCooldownData.m_TicksDelayed = 0;
+ m_PortalCooldownData = { 0, false, true };
return false;
}
@@ -1547,99 +1501,6 @@ bool cEntity::DetectPortal()
-bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition)
-{
- UNUSED(a_ShouldSendRespawn);
- ASSERT(a_World != nullptr);
- ASSERT(IsTicking());
-
- if (GetWorld() == a_World)
- {
- // Don't move to same world
- return false;
- }
-
- // Ask the plugins if the entity is allowed to changing the world
- if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World))
- {
- // A Plugin doesn't allow the entity to changing the world
- return false;
- }
-
- // Stop ticking, in preperation for detaching from this world.
- SetIsTicking(false);
-
- // Tell others we are gone
- GetWorld()->BroadcastDestroyEntity(*this);
-
- // Set position to the new position
- SetPosition(a_NewPosition);
-
- // Stop all mobs from targeting this entity
- // Stop this entity from targeting other mobs
- if (this->IsMob())
- {
- cMonster * Monster = static_cast<cMonster*>(this);
- Monster->SetTarget(nullptr);
- Monster->StopEveryoneFromTargetingMe();
- }
-
- // Queue add to new world and removal from the old one
- cWorld * OldWorld = GetWorld();
- cChunk * ParentChunk = GetParentChunk();
- SetWorld(a_World); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value
- OldWorld->QueueTask([this, ParentChunk, a_World](cWorld & a_OldWorld)
- {
- LOGD("Warping entity #%i (%s) from world \"%s\" to \"%s\". Source chunk: (%d, %d) ",
- this->GetUniqueID(), this->GetClass(),
- a_OldWorld.GetName().c_str(), a_World->GetName().c_str(),
- ParentChunk->GetPosX(), ParentChunk->GetPosZ()
- );
- ParentChunk->RemoveEntity(this);
- a_World->AddEntity(this);
- cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*this, a_OldWorld);
- });
- return true;
-}
-
-
-
-
-
-bool cEntity::MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition)
-{
- return DoMoveToWorld(a_World, a_ShouldSendRespawn, a_NewPosition);
-}
-
-
-
-
-
-bool cEntity::MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn)
-{
- return MoveToWorld(a_World, a_ShouldSendRespawn, Vector3d(a_World->GetSpawnX(), a_World->GetSpawnY(), a_World->GetSpawnZ()));
-}
-
-
-
-
-
-bool cEntity::MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn)
-{
- cWorld * World = cRoot::Get()->GetWorld(a_WorldName);
- if (World == nullptr)
- {
- LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName.c_str());
- return false;
- }
-
- return DoMoveToWorld(World, a_ShouldSendRespawn, Vector3d(World->GetSpawnX(), World->GetSpawnY(), World->GetSpawnZ()));
-}
-
-
-
-
-
void cEntity::SetSwimState(cChunk & a_Chunk)
{
int RelY = FloorC(GetPosY() + 0.1);
@@ -1836,19 +1697,19 @@ void cEntity::StopBurning(void)
void cEntity::TeleportToEntity(cEntity & a_Entity)
{
- TeleportToCoords(a_Entity.GetPosX(), a_Entity.GetPosY(), a_Entity.GetPosZ());
+ TeleportToCoords(a_Entity.GetPosition());
}
-void cEntity::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
+void cEntity::TeleportToCoords(const Vector3d & a_Position)
{
- // ask the plugins to allow teleport to the new position.
- if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, Vector3d(a_PosX, a_PosY, a_PosZ)))
+ // Ask the plugins to allow teleport to the new position.
+ if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, a_Position))
{
- SetPosition(a_PosX, a_PosY, a_PosZ);
+ SetPosition(a_Position);
m_World->BroadcastTeleportEntity(*this);
}
}
@@ -1857,6 +1718,91 @@ void cEntity::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
+bool cEntity::MoveToWorld(cWorld & a_NewWorld, const Vector3d & a_NewPosition)
+{
+ auto PreviousDimension = GetWorld()->GetDimension();
+ if (OnPreWorldTravel(a_NewWorld))
+ {
+ OnPostWorldTravel(PreviousDimension, a_NewPosition);
+ SetPosition(a_NewPosition); // Just in case :)
+ return true;
+ }
+ return false;
+}
+
+
+
+
+
+bool cEntity::MoveToWorld(const AString & a_WorldName, const Vector3d & a_NewPosition)
+{
+ auto World = cRoot::Get()->GetWorld(a_WorldName);
+ if (World == nullptr)
+ {
+ LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName.c_str());
+ return false;
+ }
+
+ return MoveToWorld(*World, a_NewPosition);
+}
+
+
+
+
+
+bool cEntity::OnPreWorldTravel(cWorld & a_NewWorld)
+{
+ ASSERT(IsTicking());
+
+ if (GetWorld() == &a_NewWorld)
+ {
+ // Don't move to same world
+ return false;
+ }
+
+ // Ask the plugins if the player is allowed to changing the world
+ if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, a_NewWorld))
+ {
+ // A Plugin doesn't allow the player to changing the world
+ return false;
+ }
+
+ // Prevent further ticking in this world
+ SetIsTicking(false);
+
+ auto Entity = GetParentChunk()->AcquireAssociatedEntityPtr(*this);
+
+ // Broadcast for other people that the player is gone.
+ GetWorld()->BroadcastDestroyEntity(*this);
+
+ cpp14::move_on_copy_wrapper<decltype(Entity)> EntityPtr(std::move(Entity));
+ a_NewWorld.QueueTask(
+ [EntityPtr](cWorld & a_DestinationWorld) mutable
+ {
+ // Entity changed world, call the hook
+ cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*EntityPtr.value, *EntityPtr.value->GetWorld());
+
+ EntityPtr.value->Initialize(std::move(EntityPtr.value), a_DestinationWorld);
+ }
+ );
+
+ return true;
+}
+
+
+
+
+
+void cEntity::OnPostWorldTravel(eDimension a_PreviousDimension, const Vector3d & a_RecommendedPosition)
+{
+ SetPosition(a_RecommendedPosition);
+ m_PortalCooldownData.m_PositionValid = true;
+}
+
+
+
+
+
void cEntity::BroadcastMovementUpdate(const cClientHandle * a_Exclude)
{
// Process packet sending every two ticks
diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h
index c543fd9c1..25f6e76bf 100644
--- a/src/Entities/Entity.h
+++ b/src/Entities/Entity.h
@@ -215,7 +215,7 @@ public:
void SetPosY (double a_PosY) { SetPosition({m_Position.x, a_PosY, m_Position.z}); }
void SetPosZ (double a_PosZ) { SetPosition({m_Position.x, m_Position.y, a_PosZ}); }
void SetPosition(double a_PosX, double a_PosY, double a_PosZ) { SetPosition({a_PosX, a_PosY, a_PosZ}); }
- void SetPosition(const Vector3d & a_Position);
+ virtual void SetPosition(const Vector3d & a_Position);
void SetYaw (double a_Yaw); // In degrees, normalizes to [-180, +180)
void SetPitch (double a_Pitch); // In degrees, normalizes to [-180, +180)
void SetRoll (double a_Roll); // In degrees, normalizes to [-180, +180)
@@ -300,6 +300,9 @@ public:
If it returns false, the entity hasn't receive any damage. */
virtual bool DoTakeDamage(TakeDamageInfo & a_TDI);
+ /** Returns the position stored before the last call to SetPosition. */
+ Vector3d GetLastPosition(void) const { return m_LastPosition; }
+
// tolua_begin
/** Returns the hitpoints that this pawn can deal to a_Receiver using its equipped items */
@@ -405,22 +408,29 @@ public:
virtual void TeleportToEntity(cEntity & a_Entity);
/** Teleports to the coordinates specified */
- virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ);
-
- /** Schedules a MoveToWorld call to occur on the next Tick of the entity */
- void ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_ShouldSetPortalCooldown = false);
-
- bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition);
+ virtual void TeleportToCoords(const Vector3d & a_Position);
- /** Moves entity to specified world, taking a world pointer */
- bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn = true);
+ /** Moves entity to the specified world reference and to the specified position.
+ Returns if the entity changed worlds. */
+ bool MoveToWorld(cWorld & a_NewWorld, const Vector3d & a_NewPosition);
- /** Moves entity to specified world, taking a world name */
- bool MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn = true);
+ /** Moves entity to the specified world name and to the specified position.
+ Returns if the entity changed worlds. */
+ bool MoveToWorld(const AString & a_WorldName, const Vector3d & a_NewPosition);
// tolua_end
- virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition);
+ /** Event called on initial world change request.
+ The recommended coordinates have not been calculated, and the destination spawn area may not have been prepared yet.
+ Descendants MAY overload to modify the specifics of their world change behaviour. */
+ virtual bool OnPreWorldTravel(cWorld & a_NewWorld);
+
+ /** Event called after world change request has been fully processed.
+ The suggested spawn coordinates have been calculated.
+ Descendants MAY ignore the recommended position.
+ This function MUST be called within the context of the destination world's tick thread:
+ we may safely ignore the originating world as the entity is guaranteed to no longer be accessed / ticked from that thread. */
+ virtual void OnPostWorldTravel(eDimension a_PreviousDimension, const Vector3d & a_RecommendedPosition);
/** Updates clients of changes in the entity. */
virtual void BroadcastMovementUpdate(const cClientHandle * a_Exclude = nullptr);
@@ -490,14 +500,17 @@ public:
}
/** Sets the internal world pointer to a new cWorld, doesn't update anything else. */
- void SetWorld(cWorld * a_World) { m_World = a_World; }
+ void SetWorld(cWorld * a_World)
+ {
+ m_World = a_World;
+ }
/** Sets the parent chunk, which is the chunk responsible for ticking this entity.
Only cChunk::AddEntity and cChunk::RemoveEntity cChunk::~cChunk should ever call this. */
void SetParentChunk(cChunk * a_Chunk);
/** Returns the chunk responsible for ticking this entity. */
- cChunk * GetParentChunk();
+ cChunk * GetParentChunk() const;
/** Set the entity's status to either ticking or not ticking. */
void SetIsTicking(bool a_IsTicking);
@@ -512,6 +525,10 @@ protected:
/** Whether the entity has just exited the portal, and should therefore not be teleported again.
This prevents teleportation loops, and is reset when the entity has moved out of the portal. */
bool m_ShouldPreventTeleportation;
+
+ /** Whether the entity's position has been calculated and set.
+ Processing does not occur until this is true. */
+ bool m_PositionValid;
};
static cCriticalSection m_CSCount;
@@ -563,12 +580,6 @@ protected:
cWorld * m_World;
- /** State variables for ScheduleMoveToWorld. */
- bool m_IsWorldChangeScheduled;
- bool m_WorldChangeSetPortalCooldown;
- cWorld * m_NewWorld;
- Vector3d m_NewWorldPosition;
-
/** Whether the entity is capable of taking fire or lava damage. */
bool m_IsFireproof;
@@ -656,8 +667,6 @@ private:
int m_InvulnerableTicks;
} ; // tolua_export
-typedef std::list<cEntity *> cEntityList;
-
diff --git a/src/Entities/Pawn.cpp b/src/Entities/Pawn.cpp
index 04425dd51..c83e7f3cb 100644
--- a/src/Entities/Pawn.cpp
+++ b/src/Entities/Pawn.cpp
@@ -28,8 +28,10 @@ cPawn::cPawn(eEntityType a_EntityType, double a_Width, double a_Height) :
cPawn::~cPawn()
+bool cPawn::OnPreWorldTravel(cWorld & a_NewWorld)
{
ASSERT(m_TargetingMe.size() == 0);
+ return super::OnPreWorldTravel(a_NewWorld);
}
diff --git a/src/Entities/Pawn.h b/src/Entities/Pawn.h
index 6a7035ee6..74bccc4a3 100644
--- a/src/Entities/Pawn.h
+++ b/src/Entities/Pawn.h
@@ -23,6 +23,8 @@ public:
cPawn(eEntityType a_EntityType, double a_Width, double a_Height);
~cPawn();
+
+ virtual bool OnPreWorldTravel(cWorld & a_NewWorld) override;
virtual void Destroyed() override;
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override;
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index e4c0fdaa5..babe31978 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -36,6 +36,14 @@
// 1000 = once per second
#define PLAYER_LIST_TIME_MS std::chrono::milliseconds(1000)
+#define DO_WITH_VALID_CLIENTHANDLE(Command) \
+ auto ClientHandle = m_ClientHandle.lock(); \
+ if (ClientHandle != nullptr) \
+ { \
+ ClientHandle->Command \
+ }
+
+
@@ -50,7 +58,7 @@ const int cPlayer::EATING_TICKS = 30;
-cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) :
+cPlayer::cPlayer(std::weak_ptr<cClientHandle> a_Client, const AString & a_PlayerName) :
super(etPlayer, 0.6, 1.8),
m_bVisible(true),
m_FoodLevel(MAX_FOOD_LEVEL),
@@ -85,8 +93,8 @@ cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) :
m_FloaterID(cEntity::INVALID_ID),
m_Team(nullptr),
m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL),
- m_bIsTeleporting(false),
- m_UUID((a_Client != nullptr) ? a_Client->GetUUID() : ""),
+ m_IsChangingWorlds(false),
+ m_UUID(a_Client.lock()->GetUUID()),
m_CustomName("")
{
ASSERT(a_PlayerName.length() <= 16); // Otherwise this player could crash many clients...
@@ -148,25 +156,6 @@ cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) :
-bool cPlayer::Initialize(cWorld & a_World)
-{
- UNUSED(a_World);
- ASSERT(GetWorld() != nullptr);
- ASSERT(GetParentChunk() == nullptr);
- GetWorld()->AddPlayer(this);
-
- cPluginManager::Get()->CallHookSpawnedEntity(*GetWorld(), *this);
-
- // Spawn the entity on the clients:
- GetWorld()->BroadcastSpawnEntity(*this);
-
- return true;
-}
-
-
-
-
-
cPlayer::~cPlayer(void)
{
if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this))
@@ -180,10 +169,6 @@ cPlayer::~cPlayer(void)
// Notify the server that the player is being destroyed
cRoot::Get()->GetServer()->PlayerDestroying(this);
- SaveToDisk();
-
- m_ClientHandle = nullptr;
-
delete m_InventoryWindow;
m_InventoryWindow = nullptr;
@@ -196,6 +181,7 @@ cPlayer::~cPlayer(void)
void cPlayer::Destroyed()
{
+ SaveToDisk();
CloseWindow(false);
super::Destroyed();
}
@@ -206,7 +192,7 @@ void cPlayer::Destroyed()
void cPlayer::SpawnOn(cClientHandle & a_Client)
{
- if (!m_bVisible || (m_ClientHandle.get() == (&a_Client)))
+ if (!m_bVisible || (m_ClientHandle.lock().get() == &a_Client))
{
return;
}
@@ -225,28 +211,22 @@ void cPlayer::SpawnOn(cClientHandle & a_Client)
void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
- if (m_ClientHandle != nullptr)
+ auto ClientHandle = m_ClientHandle.lock();
+ if ((ClientHandle == nullptr) || ClientHandle->IsDestroyed())
{
- if (m_ClientHandle->IsDestroyed())
- {
- // This should not happen, because destroying a client will remove it from the world, but just in case
- ASSERT(!"Player ticked whilst in the process of destruction!");
- m_ClientHandle = nullptr;
- return;
- }
+ // If the ClientHandle was deleted or destroyed, the client has disconnected
+ // In theory, if it was only destroyed, we may continue ticking (just nothing would be sent over the network)
+ // For performance however, simply bail out
- if (!m_ClientHandle->IsPlaying())
- {
- // We're not yet in the game, ignore everything
- return;
- }
+ return;
}
- else
+
+ if (!ClientHandle->IsPlaying())
{
- ASSERT(!"Player ticked whilst in the process of destruction!");
+ // We're not yet in the game, ignore everything
+ return;
}
-
m_Stats.AddValue(statMinutesPlayed, 1);
// Handle a frozen player
@@ -255,9 +235,8 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
return;
}
- ASSERT((GetParentChunk() != nullptr) && (GetParentChunk()->IsValid()));
- ASSERT(a_Chunk.IsValid());
+ ASSERT(GetParentChunk() != nullptr);
super::Tick(a_Dt, a_Chunk);
@@ -273,7 +252,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
SendExperience();
}
- BroadcastMovementUpdate(m_ClientHandle.get());
+ BroadcastMovementUpdate(ClientHandle.get());
if (m_Health > 0) // make sure player is alive
{
@@ -319,55 +298,16 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
void cPlayer::TickFreezeCode()
{
- if (m_IsFrozen)
+ if (m_IsFrozen && (GetWorld()->GetWorldAge() % 100 == 0))
{
- if ((!m_IsManuallyFrozen) && (GetClientHandle()->IsPlayerChunkSent()))
- {
- // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded and sent
- Unfreeze();
+ // Despite the client side freeze, the player may be able to move a little by
+ // Jumping or canceling flight. Re-freeze every now and then
+ Freeze(GetPosition());
- // Pull the player out of any solids that might have loaded on them.
- PREPARE_REL_AND_CHUNK(GetPosition(), *(GetParentChunk()));
- if (RelSuccess)
- {
- int NewY = Rel.y;
- if (NewY < 0)
- {
- NewY = 0;
- }
- while (NewY < cChunkDef::Height - 2)
- {
- // If we find a position with enough space for the player
- if (
- (Chunk->GetBlock(Rel.x, NewY, Rel.z) == E_BLOCK_AIR) &&
- (Chunk->GetBlock(Rel.x, NewY + 1, Rel.z) == E_BLOCK_AIR)
- )
- {
- // If the found position is not the same as the original
- if (NewY != Rel.y)
- {
- SetPosition(GetPosition().x, NewY, GetPosition().z);
- GetClientHandle()->SendPlayerPosition();
- }
- break;
- }
- ++NewY;
- }
- }
- }
- else if (GetWorld()->GetWorldAge() % 100 == 0)
- {
- // Despite the client side freeze, the player may be able to move a little by
- // Jumping or canceling flight. Re-freeze every now and then
- FreezeInternal(GetPosition(), m_IsManuallyFrozen);
- }
- }
- else
- {
- if (!GetClientHandle()->IsPlayerChunkSent() || (!GetParentChunk()->IsValid()))
- {
- FreezeInternal(GetPosition(), false);
- }
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendPlayerPosition();
+ BroadcastMovementUpdate(ClientHandle.get());
+ )
}
}
@@ -499,7 +439,7 @@ void cPlayer::StartChargingBow(void)
LOGD("Player \"%s\" started charging their bow", GetName().c_str());
m_IsChargingBow = true;
m_BowCharge = 0;
- m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
+ m_World->BroadcastEntityMetadata(*this, m_ClientHandle.lock().get());
}
@@ -512,7 +452,7 @@ int cPlayer::FinishChargingBow(void)
int res = m_BowCharge;
m_IsChargingBow = false;
m_BowCharge = 0;
- m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
+ m_World->BroadcastEntityMetadata(*this, m_ClientHandle.lock().get());
return res;
}
@@ -526,7 +466,7 @@ void cPlayer::CancelChargingBow(void)
LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", GetName().c_str(), m_BowCharge);
m_IsChargingBow = false;
m_BowCharge = 0;
- m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
+ m_World->BroadcastEntityMetadata(*this, m_ClientHandle.lock().get());
}
@@ -654,7 +594,11 @@ void cPlayer::FinishEating(void)
m_EatingFinishTick = -1;
// Send the packets:
- m_ClientHandle->SendEntityStatus(*this, esPlayerEatingAccepted);
+ auto ClientHandle = m_ClientHandle.lock();
+ if (ClientHandle == nullptr)
+ {
+ ClientHandle->SendEntityStatus(*this, esPlayerEatingAccepted);
+ }
m_World->BroadcastEntityMetadata(*this);
// consume the item:
@@ -684,10 +628,9 @@ void cPlayer::AbortEating(void)
void cPlayer::SendHealth(void)
{
- if (m_ClientHandle != nullptr)
- {
- m_ClientHandle->SendHealth();
- }
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendHealth();
+ )
}
@@ -696,11 +639,10 @@ void cPlayer::SendHealth(void)
void cPlayer::SendExperience(void)
{
- if (m_ClientHandle != nullptr)
- {
- m_ClientHandle->SendExperience();
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendExperience();
m_bDirtyExperience = false;
- }
+ )
}
@@ -762,8 +704,9 @@ void cPlayer::SetNormalMaxSpeed(double a_Speed)
m_NormalMaxSpeed = a_Speed;
if (!m_IsSprinting && !m_IsFlying && !m_IsFrozen)
{
- // If we are frozen, we do not send this yet. We send when unfreeze() is called
- m_ClientHandle->SendPlayerMaxSpeed();
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendPlayerMaxSpeed();
+ )
}
}
@@ -776,8 +719,9 @@ void cPlayer::SetSprintingMaxSpeed(double a_Speed)
m_SprintingMaxSpeed = a_Speed;
if (m_IsSprinting && !m_IsFlying && !m_IsFrozen)
{
- // If we are frozen, we do not send this yet. We send when unfreeze() is called
- m_ClientHandle->SendPlayerMaxSpeed();
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendPlayerMaxSpeed();
+ )
}
}
@@ -789,11 +733,13 @@ void cPlayer::SetFlyingMaxSpeed(double a_Speed)
{
m_FlyingMaxSpeed = a_Speed;
- // Update the flying speed, always:
+ // Update the flying speed:
if (!m_IsFrozen)
{
// If we are frozen, we do not send this yet. We send when unfreeze() is called
- m_ClientHandle->SendPlayerAbilities();
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendPlayerAbilities();
+ )
}
}
@@ -827,7 +773,9 @@ void cPlayer::SetSprint(bool a_IsSprinting)
}
m_IsSprinting = a_IsSprinting;
- m_ClientHandle->SendPlayerMaxSpeed();
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendPlayerMaxSpeed();
+ )
}
@@ -842,7 +790,9 @@ void cPlayer::SetCanFly(bool a_CanFly)
}
m_CanFly = a_CanFly;
- m_ClientHandle->SendPlayerAbilities();
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendPlayerAbilities();
+ )
}
@@ -904,7 +854,9 @@ void cPlayer::SetFlying(bool a_IsFlying)
if (!m_IsFrozen)
{
// If we are frozen, we do not send this yet. We send when unfreeze() is called
- m_ClientHandle->SendPlayerAbilities();
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendPlayerAbilities();
+ )
}
}
@@ -1147,18 +1099,20 @@ void cPlayer::Respawn(void)
m_LifetimeTotalXp = 0;
// ToDo: send score to client? How?
- m_ClientHandle->SendRespawn(m_SpawnWorld->GetDimension(), true);
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendRespawn(m_SpawnWorld->GetDimension(), true);
+ )
// Extinguish the fire:
StopBurning();
if (GetWorld() != m_SpawnWorld)
{
- MoveToWorld(m_SpawnWorld, false, GetLastBedPos());
+ MoveToWorld(*m_SpawnWorld, GetLastBedPos());
}
else
{
- TeleportToCoords(GetLastBedPos().x, GetLastBedPos().y, GetLastBedPos().z);
+ TeleportToCoords(GetLastBedPos());
}
SetVisible(true);
@@ -1335,7 +1289,9 @@ void cPlayer::SetGameMode(eGameMode a_GameMode)
}
m_GameMode = a_GameMode;
- m_ClientHandle->SendGameMode(a_GameMode);
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendGameMode(a_GameMode);
+ )
SetCapabilities();
@@ -1422,7 +1378,9 @@ unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach)
StatValue New = m_Stats.AddValue(a_Ach);
// Achievement Get!
- m_ClientHandle->SendStatistics(m_Stats);
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendStatistics(m_Stats);
+ )
return static_cast<unsigned int>(New);
}
@@ -1432,18 +1390,18 @@ unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach)
-void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
+void cPlayer::TeleportToCoords(const Vector3d & a_Position)
{
- // ask plugins to allow teleport to the new position.
- if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, Vector3d(a_PosX, a_PosY, a_PosZ)))
+ // Ask plugins to allow teleport to the new position.
+ if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, a_Position))
{
- SetPosition(a_PosX, a_PosY, a_PosZ);
- FreezeInternal(GetPosition(), false);
- m_LastGroundHeight = a_PosY;
- m_bIsTeleporting = true;
+ SetPosition(a_Position);
+ m_LastGroundHeight = a_Position.y;
m_World->BroadcastTeleportEntity(*this, GetClientHandle());
- m_ClientHandle->SendPlayerMoveLook();
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendPlayerMoveLook();
+ )
}
}
@@ -1453,7 +1411,32 @@ void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
void cPlayer::Freeze(const Vector3d & a_Location)
{
- FreezeInternal(a_Location, true);
+ SetSpeed(0, 0, 0);
+ SetPosition(a_Location);
+ m_IsFrozen = true;
+
+ double NormalMaxSpeed = GetNormalMaxSpeed();
+ double SprintMaxSpeed = GetSprintingMaxSpeed();
+ double FlyingMaxpeed = GetFlyingMaxSpeed();
+ bool IsFlying = m_IsFlying;
+
+ // Set the client-side speed to 0
+ m_NormalMaxSpeed = 0;
+ m_SprintingMaxSpeed = 0;
+ m_FlyingMaxSpeed = 0;
+ m_IsFlying = true;
+
+ // Send the client its fake speed and max speed of 0
+ GetClientHandle()->SendPlayerMoveLook();
+ GetClientHandle()->SendPlayerAbilities();
+ GetClientHandle()->SendPlayerMaxSpeed();
+ GetClientHandle()->SendEntityVelocity(*this);
+
+ // Keep the server side speed variables as they were in the first place
+ m_NormalMaxSpeed = NormalMaxSpeed;
+ m_SprintingMaxSpeed = SprintMaxSpeed;
+ m_FlyingMaxSpeed = FlyingMaxpeed;
+ m_IsFlying = IsFlying;
}
@@ -1475,8 +1458,10 @@ void cPlayer::Unfreeze()
GetClientHandle()->SendPlayerMaxSpeed();
m_IsFrozen = false;
- BroadcastMovementUpdate(GetClientHandle());
- GetClientHandle()->SendPlayerPosition();
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendPlayerPosition();
+ BroadcastMovementUpdate(ClientHandle.get());
+ )
}
@@ -1487,7 +1472,9 @@ void cPlayer::SendRotation(double a_YawDegrees, double a_PitchDegrees)
{
SetYaw(a_YawDegrees);
SetPitch(a_PitchDegrees);
- m_ClientHandle->SendPlayerMoveLook();
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendPlayerMoveLook();
+ )
}
@@ -1524,15 +1511,6 @@ Vector3d cPlayer::GetThrowSpeed(double a_SpeedCoeff) const
-void cPlayer::ForceSetSpeed(const Vector3d & a_Speed)
-{
- SetSpeed(a_Speed);
-}
-
-
-
-
-
void cPlayer::DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ)
{
if (m_IsFrozen)
@@ -1542,7 +1520,9 @@ void cPlayer::DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ)
}
super::DoSetSpeed(a_SpeedX, a_SpeedY, a_SpeedZ);
// Send the speed to the client so he actualy moves
- m_ClientHandle->SendEntityVelocity(*this);
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendEntityVelocity(*this);
+ )
}
@@ -1560,10 +1540,120 @@ void cPlayer::SetVisible(bool a_bVisible)
if (!a_bVisible && m_bVisible)
{
m_bVisible = false;
- m_World->BroadcastDestroyEntity(*this, m_ClientHandle.get()); // Destroy on all clients
+ m_World->BroadcastDestroyEntity(*this, m_ClientHandle.lock().get()); // Destroy on all clients
}
}
+void cPlayer::SendMessage(const AString & a_Message)
+{
+ SendMessage(cCompositeChat(a_Message, mtCustom));
+}
+
+
+
+
+
+void cPlayer::SendMessageInfo(const AString & a_Message)
+{
+ SendMessage(cCompositeChat(a_Message, mtInformation));
+}
+
+
+
+
+
+void cPlayer::SendMessageFailure(const AString & a_Message)
+{
+ SendMessage(cCompositeChat(a_Message, mtFailure));
+}
+
+
+
+
+
+void cPlayer::SendMessageSuccess(const AString & a_Message)
+{
+ SendMessage(cCompositeChat(a_Message, mtSuccess));
+}
+
+
+
+
+
+void cPlayer::SendMessageWarning(const AString & a_Message)
+{
+ SendMessage(cCompositeChat(a_Message, mtWarning));
+}
+
+
+
+
+
+void cPlayer::SendMessageFatal(const AString & a_Message)
+{
+ SendMessage(cCompositeChat(a_Message, mtFailure));
+}
+
+
+
+
+
+void cPlayer::SendMessagePrivateMsg(const AString & a_Message, const AString & a_Sender)
+{
+ SendMessage(cCompositeChat(a_Message, mtPrivateMessage, a_Sender));
+}
+
+
+
+
+
+void cPlayer::SendMessage(const cCompositeChat & a_Message)
+{
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendChat(a_Message);
+ )
+}
+
+
+
+
+
+void cPlayer::SendSystemMessage(const AString & a_Message)
+{
+ SendSystemMessage(cCompositeChat(a_Message));
+}
+
+
+
+
+
+void cPlayer::SendAboveActionBarMessage(const AString & a_Message)
+{
+ SendAboveActionBarMessage(cCompositeChat(a_Message));
+}
+
+
+
+
+
+void cPlayer::SendSystemMessage(const cCompositeChat & a_Message)
+{
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendChatSystem(a_Message);
+ )
+}
+
+
+
+
+
+void cPlayer::SendAboveActionBarMessage(const cCompositeChat & a_Message)
+{
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendChatAboveActionBar(a_Message);
+ )
+}
+
@@ -1760,77 +1850,94 @@ void cPlayer::TossItems(const cItems & a_Items)
-bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition)
+bool cPlayer::OnPreWorldTravel(cWorld & a_NewWorld)
{
- ASSERT(a_World != nullptr);
- ASSERT(IsTicking());
-
- if (GetWorld() == a_World)
+ if ((GetWorld() == &a_NewWorld) || IsChangingWorlds())
{
- // Don't move to same world
+ // Don't move a) to same world or b) when already moving worlds
return false;
}
+ ASSERT(IsTicking());
// Ask the plugins if the player is allowed to changing the world
- if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World))
+ if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, a_NewWorld))
{
// A Plugin doesn't allow the player to changing the world
return false;
}
- // The clienthandle caches the coords of the chunk we're standing at. Invalidate this.
- GetClientHandle()->InvalidateCachedSentChunk();
-
// Prevent further ticking in this world
SetIsTicking(false);
- // Tell others we are gone
+ // Stop sending chunks
+ SetIsChangingWorlds(true);
+
+ auto ClientHandle = GetClientHandlePtr().lock();
+ if (ClientHandle != nullptr)
+ {
+ cRoot::Get()->GetServer()->QueueTask(
+ [ClientHandle]
+ {
+ ClientHandle->RemoveFromWorld();
+ }
+ );
+
+ GetWorld()->RemovePlayer(this);
+ ClientHandle->SendRespawn(a_NewWorld.GetDimension());
+
+ switch (a_NewWorld.GetDimension())
+ {
+ case eDimension::dimOverworld: break;
+ case eDimension::dimNether: AwardAchievement(achEnterPortal); break;
+ case eDimension::dimEnd: AwardAchievement(achEnterTheEnd); break;
+ default: ASSERT(!"Unexpected dimension during world travel.");
+ }
+ }
+ // If std::weak_ptr<>::lock returned nullptr, the destructor of cClientHandle would have handled player removal.
+
+ auto Entity = GetParentChunk()->AcquireAssociatedEntityPtr(*this);
+
+ // Broadcast for other people that the player is gone.
GetWorld()->BroadcastDestroyEntity(*this);
- // Remove player from world
- GetWorld()->RemovePlayer(this, false);
+ cpp14::move_on_copy_wrapper<decltype(Entity)> EntityPtr(std::move(Entity));
+ a_NewWorld.QueueTask(
+ [EntityPtr](cWorld & a_DestinationWorld) mutable
+ {
+ ASSERT(EntityPtr.value->IsPlayer());
+ auto Player = static_cast<cPlayer *>(EntityPtr.value.get());
- // Set position to the new position
- SetPosition(a_NewPosition);
- FreezeInternal(a_NewPosition, false);
+ // Player changed world, call the hook
+ cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*EntityPtr.value, *EntityPtr.value->GetWorld());
- // Stop all mobs from targeting this player
- StopEveryoneFromTargetingMe();
+ EntityPtr.value->Initialize(std::move(EntityPtr.value), a_DestinationWorld);
+ a_DestinationWorld.AddPlayer(Player);
- // Send the respawn packet:
- if (a_ShouldSendRespawn && (m_ClientHandle != nullptr))
- {
- m_ClientHandle->SendRespawn(a_World->GetDimension());
- }
+ Player->SetIsChangingWorlds(false);
+ }
+ );
+
+ return true;
+}
- // Update the view distance.
- m_ClientHandle->SetViewDistance(m_ClientHandle->GetRequestedViewDistance());
- // Send current weather of target world to player
- if (a_World->GetDimension() == dimOverworld)
- {
- m_ClientHandle->SendWeather(a_World->GetWeather());
- }
- // Broadcast the player into the new world.
- a_World->BroadcastSpawnEntity(*this);
- // Queue add to new world and removal from the old one
- cChunk * ParentChunk = GetParentChunk();
- cWorld * OldWorld = GetWorld();
- SetWorld(a_World); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value
- OldWorld->QueueTask([this, ParentChunk, a_World](cWorld & a_OldWorld)
+
+void cPlayer::OnPostWorldTravel(eDimension a_PreviousDimension, const Vector3d & a_RecommendedPosition)
+{
+ if ((a_PreviousDimension == eDimension::dimEnd) && (GetWorld()->GetDimension() == eDimension::dimOverworld))
{
- LOGD("Warping player \"%s\" from world \"%s\" to \"%s\". Source chunk: (%d, %d) ",
- this->GetName().c_str(),
- a_OldWorld.GetName().c_str(), a_World->GetName().c_str(),
- ParentChunk->GetPosX(), ParentChunk->GetPosZ()
- );
- ParentChunk->RemoveEntity(this);
- a_World->AddPlayer(this);
- cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*this, a_OldWorld);
- });
- return true;
+ super::OnPostWorldTravel(a_PreviousDimension, GetLastBedPos());
+ }
+ else
+ {
+ super::OnPostWorldTravel(a_PreviousDimension, a_RecommendedPosition);
+ }
+
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendPlayerMoveLook();
+ )
}
@@ -2251,12 +2358,6 @@ bool cPlayer::IsClimbing(void) const
void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIsOnGround)
{
- if (m_bIsTeleporting)
- {
- m_bIsTeleporting = false;
- return;
- }
-
StatValue Value = FloorC<StatValue>(a_DeltaPos.Length() * 100 + 0.5);
if (m_AttachedTo == nullptr)
{
@@ -2412,7 +2513,9 @@ void cPlayer::SendBlocksAround(int a_BlockX, int a_BlockY, int a_BlockZ, int a_R
// Send the blocks for each affected chunk:
for (auto itr = Changes.cbegin(), end = Changes.cend(); itr != end; ++itr)
{
- m_ClientHandle->SendBlockChanges(itr->first.m_ChunkX, itr->first.m_ChunkZ, itr->second);
+ DO_WITH_VALID_CLIENTHANDLE(
+ SendBlockChanges(itr->first.m_ChunkX, itr->first.m_ChunkZ, itr->second);
+ )
}
}
@@ -2449,7 +2552,7 @@ bool cPlayer::PlaceBlocks(const sSetBlockVector & a_Blocks)
}
// Call the "placed" hooks:
- for (auto blk: a_Blocks)
+ for (const auto & blk: a_Blocks)
{
pm->CallHookPlayerPlacedBlock(*this, blk);
}
@@ -2482,7 +2585,7 @@ void cPlayer::Detach()
cBlockInfo::IsSolid(m_World->GetBlock(x, y - 1, z))
)
{
- TeleportToCoords(x + 0.5, y, z + 0.5);
+ TeleportToCoords(Vector3d(x + 0.5, y, z + 0.5));
return;
}
}
@@ -2494,16 +2597,6 @@ void cPlayer::Detach()
-void cPlayer::RemoveClientHandle(void)
-{
- ASSERT(m_ClientHandle != nullptr);
- m_ClientHandle.reset();
-}
-
-
-
-
-
AString cPlayer::GetUUIDFileName(const AString & a_UUID)
{
AString UUID = cMojangAPI::MakeUUIDDashed(a_UUID);
@@ -2516,38 +2609,3 @@ AString cPlayer::GetUUIDFileName(const AString & a_UUID)
res.append(".json");
return res;
}
-
-
-
-
-
-void cPlayer::FreezeInternal(const Vector3d & a_Location, bool a_ManuallyFrozen)
-{
- SetSpeed(0, 0, 0);
- SetPosition(a_Location);
- m_IsFrozen = true;
- m_IsManuallyFrozen = a_ManuallyFrozen;
-
- double NormalMaxSpeed = GetNormalMaxSpeed();
- double SprintMaxSpeed = GetSprintingMaxSpeed();
- double FlyingMaxpeed = GetFlyingMaxSpeed();
- bool IsFlying = m_IsFlying;
-
- // Set the client-side speed to 0
- m_NormalMaxSpeed = 0;
- m_SprintingMaxSpeed = 0;
- m_FlyingMaxSpeed = 0;
- m_IsFlying = true;
-
- // Send the client its fake speed and max speed of 0
- GetClientHandle()->SendPlayerMoveLook();
- GetClientHandle()->SendPlayerAbilities();
- GetClientHandle()->SendPlayerMaxSpeed();
- GetClientHandle()->SendEntityVelocity(*this);
-
- // Keep the server side speed variables as they were in the first place
- m_NormalMaxSpeed = NormalMaxSpeed;
- m_SprintingMaxSpeed = SprintMaxSpeed;
- m_FlyingMaxSpeed = FlyingMaxpeed;
- m_IsFlying = IsFlying;
-}
diff --git a/src/Entities/Player.h b/src/Entities/Player.h
index f6e9da45e..c8e7b8297 100644
--- a/src/Entities/Player.h
+++ b/src/Entities/Player.h
@@ -40,9 +40,7 @@ public:
CLASS_PROTODEF(cPlayer)
- cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName);
-
- virtual bool Initialize(cWorld & a_World) override;
+ cPlayer(std::weak_ptr<cClientHandle> a_Client, const AString & a_PlayerName);
virtual ~cPlayer();
@@ -138,7 +136,7 @@ public:
/** Returns whether the player is climbing (ladders, vines etc.) */
bool IsClimbing(void) const;
- virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) override;
+ virtual void TeleportToCoords(const Vector3d & a_Position) override;
// tolua_begin
@@ -217,10 +215,6 @@ public:
void SetIP(const AString & a_IP);
- /** Forces the player to move in the given direction.
- @deprecated Use SetSpeed instead. */
- void ForceSetSpeed(const Vector3d & a_Speed); // tolua_export
-
cWindow * GetWindow(void) { return m_CurrentWindow; } // tolua_export
const cWindow * GetWindow(void) const { return m_CurrentWindow; }
@@ -236,28 +230,28 @@ public:
void CloseWindowIfID(char a_WindowID, bool a_CanRefuse = true);
/** Returns the raw client handle associated with the player. */
- cClientHandle * GetClientHandle(void) const { return m_ClientHandle.get(); }
+ cClientHandle * GetClientHandle(void) const { return m_ClientHandle.lock().get(); }
// tolua_end
/** Returns the SharedPtr to client handle associated with the player. */
- cClientHandlePtr GetClientHandlePtr(void) const { return m_ClientHandle; }
+ std::weak_ptr<cClientHandle> GetClientHandlePtr(void) const { return m_ClientHandle; }
// tolua_begin
- void SendMessage (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtCustom); }
- void SendMessageInfo (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtInformation); }
- void SendMessageFailure (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtFailure); }
- void SendMessageSuccess (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtSuccess); }
- void SendMessageWarning (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtWarning); }
- void SendMessageFatal (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtFailure); }
- void SendMessagePrivateMsg (const AString & a_Message, const AString & a_Sender) { m_ClientHandle->SendChat(a_Message, mtPrivateMessage, a_Sender); }
- void SendMessage (const cCompositeChat & a_Message) { m_ClientHandle->SendChat(a_Message); }
+ void SendMessage(const AString & a_Message);
+ void SendMessageInfo(const AString & a_Message);
+ void SendMessageFailure(const AString & a_Message);
+ void SendMessageSuccess(const AString & a_Message);
+ void SendMessageWarning(const AString & a_Message);
+ void SendMessageFatal(const AString & a_Message);
+ void SendMessagePrivateMsg(const AString & a_Message, const AString & a_Sender);
+ void SendMessage(const cCompositeChat & a_Message);
- void SendSystemMessage (const AString & a_Message) { m_ClientHandle->SendChatSystem(a_Message, mtCustom); }
- void SendAboveActionBarMessage(const AString & a_Message) { m_ClientHandle->SendChatAboveActionBar(a_Message, mtCustom); }
- void SendSystemMessage (const cCompositeChat & a_Message) { m_ClientHandle->SendChatSystem(a_Message); }
- void SendAboveActionBarMessage(const cCompositeChat & a_Message) { m_ClientHandle->SendChatAboveActionBar(a_Message); }
+ void SendSystemMessage(const AString & a_Message);
+ void SendAboveActionBarMessage(const AString & a_Message);
+ void SendSystemMessage(const cCompositeChat & a_Message);
+ void SendAboveActionBarMessage(const cCompositeChat & a_Message);
const AString & GetName(void) const { return m_PlayerName; }
void SetName(const AString & a_Name) { m_PlayerName = a_Name; }
@@ -266,7 +260,7 @@ public:
bool HasPermission(const AString & a_Permission); // tolua_export
- /** Returns true iff a_Permission matches the a_Template.
+ /** Returns true if a_Permission matches the a_Template.
A match is defined by either being exactly the same, or each sub-item matches until there's a wildcard in a_Template.
Ie. {"a", "b", "c"} matches {"a", "b", "*"} but doesn't match {"a", "b"} */
static bool PermissionMatches(const AStringVector & a_Permission, const AStringVector & a_Template); // Exported in ManualBindings with AString params
@@ -367,12 +361,22 @@ public:
bool IsVisible(void) const { return m_bVisible; } // tolua_export
/** Moves the player to the specified world.
- Returns true if successful, false on failure (world not found). */
- virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) override;
+ Overloads cEntity to perform additional housekeeping with regards to the clienthandle and world. */
+ virtual bool OnPreWorldTravel(cWorld & a_NewWorld) override;
+
+ /** Sets the player to the correct coordinates having changed world.
+ Overloads cEntity to ensure we return to our own spawn point (not the world spawn) when returning from the End. */
+ virtual void OnPostWorldTravel(eDimension a_PreviousDimension, const Vector3d & a_RecommendedPosition) override;
/** Saves all player data, such as inventory, to JSON */
bool SaveToDisk(void);
+ /** Returns whether the player is in the process of changing worlds in a thread-safe manner. */
+ bool IsChangingWorlds(void) const { return m_IsChangingWorlds; }
+
+ /** Sets whether the player is in the process of changing worlds in a thread-safe manner. */
+ void SetIsChangingWorlds(bool a_Flag) { m_IsChangingWorlds = a_Flag; }
+
typedef cWorld * cWorldPtr;
/** Loads the player data from the disk file
@@ -517,10 +521,6 @@ public:
virtual void Detach(void) override;
- /** Called by cClientHandle when the client is being destroyed.
- The player removes its m_ClientHandle ownership so that the ClientHandle gets deleted. */
- void RemoveClientHandle(void);
-
protected:
typedef std::vector<std::vector<AString> > AStringVectorVector;
@@ -598,16 +598,13 @@ protected:
std::chrono::steady_clock::time_point m_LastPlayerListTime;
- cClientHandlePtr m_ClientHandle;
+ std::weak_ptr<cClientHandle> m_ClientHandle;
cSlotNums m_InventoryPaintSlots;
/** If true, we are locking m_Position to m_FrozenPosition. */
bool m_IsFrozen;
- /** Was the player frozen manually by a plugin or automatically by the server? */
- bool m_IsManuallyFrozen;
-
/** Max speed, relative to the game default.
1 means regular speed, 2 means twice as fast, 0.5 means half-speed.
Default value is 1. */
@@ -659,10 +656,10 @@ protected:
Default save interval is #defined in PLAYER_INVENTORY_SAVE_INTERVAL */
unsigned int m_TicksUntilNextSave;
- /** Flag used by food handling system to determine whether a teleport has just happened
- Will not apply food penalties if found to be true; will set to false after processing
- */
- bool m_bIsTeleporting;
+ /** Flag indicating whether the entity is in the process of changing worlds.
+ MAY be accessed concurrently.
+ Used currently to prevent chunk sending before the player is added to the destination world. */
+ std::atomic_bool m_IsChangingWorlds;
/** The short UUID (no dashes) of the player, as read from the ClientHandle.
If no ClientHandle is given, the UUID is initialized to empty. */
@@ -673,9 +670,6 @@ protected:
/** Sets the speed and sends it to the client, so that they are forced to move so. */
virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) override;
- void ResolvePermissions(void);
- void ResolveGroups(void);
-
virtual void Destroyed(void) override;
/** Filters out damage for creative mode / friendly fire */
@@ -697,9 +691,4 @@ protected:
This can be used both for online and offline UUIDs. */
AString GetUUIDFileName(const AString & a_UUID);
-private:
-
- /** Pins the player to a_Location until Unfreeze() is called.
- If ManuallyFrozen is false, the player will unfreeze when the chunk is loaded. */
- void FreezeInternal(const Vector3d & a_Location, bool a_ManuallyFrozen);
-} ; // tolua_export
+}; // tolua_export