From 7d4934534e9c58a111215859ba83c32a9bc0fa8a Mon Sep 17 00:00:00 2001 From: Mat Date: Thu, 5 Mar 2020 12:52:34 +0200 Subject: [PATCH] Stabilise MoveToWorld (#4004) * Stabilise MoveToWorld * Fix comments and deprecate ScheduleMoveToWorld * Enhanced thread safety for m_WorldChangeInfo * Return unique_ptr from cAtomicUniquePtr::exchange * cWorld now calls entity cEntity::OnAddToWorld and cEntity::OnRemoveFromWorld. Allows broadcasting entities added to the world from the world's tick thread. This also factors out some common code from cEntity::DoMoveToWorld and cEntity::Initialize. As a consequence, cEntity::Destroy(false) (i.e. Destroying the entity without broadcasting) is impossible. This isn't used anywhere in Cuberite so it's now deprecated. * Update entity position after removing it from the world. Fixes broadcasts being sent to the wrong chunk. * Fix style * cEntity: Update LastSentPosition when sending spawn packet * Add Wno-deprecated-declarations to the lua bindings * Kill uses of ScheduleMoveToWorld --- Server/Plugins/APIDump/APIDesc.lua | 38 +++++- src/Bindings/CMakeLists.txt | 3 +- src/ClientHandle.cpp | 2 +- src/Entities/Boat.cpp | 2 +- src/Entities/Entity.cpp | 197 ++++++++++++++++------------- src/Entities/Entity.h | 73 +++++++++-- src/Entities/ExpOrb.cpp | 6 +- src/Entities/FallingBlock.cpp | 8 +- src/Entities/Floater.cpp | 2 +- src/Entities/Pickup.cpp | 8 +- src/Entities/Player.cpp | 128 ++++++++----------- src/Entities/Player.h | 6 +- src/Entities/TNTEntity.cpp | 2 +- src/Entities/WitherSkullEntity.cpp | 2 +- src/Items/ItemFishingRod.h | 2 +- src/Mobs/Monster.cpp | 22 +++- src/Mobs/Monster.h | 4 +- src/NetherPortalScanner.cpp | 2 +- src/OSSupport/AtomicUniquePtr.h | 81 ++++++++++++ src/OSSupport/CMakeLists.txt | 1 + src/World.cpp | 36 ++++-- src/World.h | 3 +- 22 files changed, 406 insertions(+), 222 deletions(-) create mode 100644 src/OSSupport/AtomicUniquePtr.h diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index 830035087..fcd923e01 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -3996,7 +3996,7 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") Type = "boolean", }, }, - Notes = "Removes the entity from this world and starts moving it to the specified world's spawn point. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions). OBSOLETE, use ScheduleMoveToWorld() instead.", + Notes = "Removes the entity from this world and starts moving it to the specified world's spawn point. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions).", }, { Params = @@ -4017,7 +4017,7 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") Type = "boolean", }, }, - Notes = "Removes the entity from this world and starts moving it to the specified world's spawn point. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions). OBSOLETE, use ScheduleMoveToWorld() instead.", + Notes = "Removes the entity from this world and starts moving it to the specified world's spawn point. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions).", }, { Params = @@ -4041,7 +4041,37 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") Type = "boolean", }, }, - Notes = "Removes the entity from this world and starts moving it to the specified world. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions). The Position parameter specifies the location that the entity should be placed in, in the new world. OBSOLETE, use ScheduleMoveToWorld() instead.", + Notes = "Removes the entity from this world and starts moving it to the specified world. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions). The Position parameter specifies the location that the entity should be placed in, in the new world.", + }, + { + Params = + { + { + Name = "World", + Type = "cWorld", + }, + { + Name = "Position", + Type = "Vector3d", + }, + { + Name = "ShouldSetPortalCooldown", + Type = "boolean", + IsOptional = true, + }, + { + Name = "ShouldSendRespawn", + Type = "boolean", + IsOptional = true, + }, + }, + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Removes the entity from this world and starts moving it to the specified world. Note that to avoid deadlocks, the move is asynchronous - the entity is moved into a queue and will be moved from that queue into the destination world at some (unpredictable) time in the future. If ShouldSetPortalCooldown is false (default), doesn't set any portal cooldown, if it is true, the default portal cooldown is applied to the entity. ShouldSendRespawn is used only for players, it specifies whether the player should be sent a Respawn packet upon leaving the world (The client handles respawns only between different dimensions). The Position parameter specifies the location that the entity should be placed in, in the new world.", }, }, ScheduleMoveToWorld = @@ -4067,7 +4097,7 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") IsOptional = true, }, }, - Notes = "Schedules a MoveToWorld call to occur on the next Tick of the entity. If ShouldSetPortalCooldown is false (default), doesn't set any portal cooldown, if it is true, the default portal cooldown is applied to the entity. If ShouldSendRespawn is false (default), no respawn packet is sent, if it is true then a respawn packet is sent to the client.", + Notes = "Schedules a MoveToWorld call to occur on the next Tick of the entity. If ShouldSetPortalCooldown is false (default), doesn't set any portal cooldown, if it is true, the default portal cooldown is applied to the entity. If ShouldSendRespawn is false (default), no respawn packet is sent, if it is true then a respawn packet is sent to the client. OBSOLETE, use MoveToWorld instead.", }, SetGravity = { diff --git a/src/Bindings/CMakeLists.txt b/src/Bindings/CMakeLists.txt index b1a19beca..17f2bdf39 100644 --- a/src/Bindings/CMakeLists.txt +++ b/src/Bindings/CMakeLists.txt @@ -164,7 +164,8 @@ set_source_files_properties(${BINDING_OUTPUTS} PROPERTIES GENERATED TRUE) set_source_files_properties(${CMAKE_SOURCE_DIR}/src/Bindings/Bindings.cpp PROPERTIES COMPILE_FLAGS -Wno-error) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - set_source_files_properties(Bindings.cpp PROPERTIES COMPILE_FLAGS "-Wno-old-style-cast -Wno-missing-prototypes") + set_source_files_properties(Bindings.cpp PROPERTIES COMPILE_FLAGS + "-Wno-old-style-cast -Wno-missing-prototypes -Wno-deprecated-declarations") endif() if(NOT MSVC) diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 49a882c77..ce6d4c981 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -189,7 +189,7 @@ void cClientHandle::Destroy(void) // If ownership was transferred, our own smart pointer should be unset ASSERT(!m_PlayerPtr); - m_PlayerPtr = world->RemovePlayer(*player, true); + m_PlayerPtr = world->RemovePlayer(*player); // And RemovePlayer should have returned a valid smart pointer ASSERT(m_PlayerPtr); diff --git a/src/Entities/Boat.cpp b/src/Entities/Boat.cpp index c05e67275..c88df4952 100644 --- a/src/Entities/Boat.cpp +++ b/src/Entities/Boat.cpp @@ -60,7 +60,7 @@ bool cBoat::DoTakeDamage(TakeDamageInfo & TDI) m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 0, 0, 0, true); } } - Destroy(true); + Destroy(); } return true; } diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 57a4680bd..44808e2a5 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -47,7 +47,6 @@ cEntity::cEntity(eEntityType a_EntityType, Vector3d a_Pos, double a_Width, doubl m_LastPosition(a_Pos), m_EntityType(a_EntityType), m_World(nullptr), - m_IsWorldChangeScheduled(false), m_IsFireproof(false), m_TicksSinceLastBurnDamage(0), m_TicksSinceLastLavaDamage(0), @@ -158,18 +157,6 @@ bool cEntity::Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld) cPluginManager::Get()->CallHookSpawnedEntity(a_EntityWorld, *this); - // Spawn the entity on the clients: - a_EntityWorld.BroadcastSpawnEntity(*this); - - // If has any mob leashed broadcast every leashed entity to this - if (HasAnyMobLeashed()) - { - for (auto LeashedMob : m_LeashedMobs) - { - m_World->BroadcastLeashEntity(*LeashedMob, *this); - } - } - return true; } @@ -177,6 +164,28 @@ bool cEntity::Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld) +void cEntity::OnAddToWorld(cWorld & a_World) +{ + // Spawn the entity on the clients: + m_LastSentPosition = GetPosition(); + a_World.BroadcastSpawnEntity(*this); + BroadcastLeashedMobs(); +} + + + + + +void cEntity::OnRemoveFromWorld(cWorld & a_World) +{ + RemoveAllLeashedMobs(); + a_World.BroadcastDestroyEntity(*this); +} + + + + + void cEntity::WrapHeadYaw(void) { m_HeadYaw = NormalizeAngleDegrees(m_HeadYaw); @@ -216,7 +225,7 @@ void cEntity::SetParentChunk(cChunk * a_Chunk) -void cEntity::Destroy(bool a_ShouldBroadcast) +void cEntity::Destroy() { SetIsTicking(false); @@ -226,11 +235,6 @@ void cEntity::Destroy(bool a_ShouldBroadcast) m_LeashedMobs.front()->Unleash(true, true); } - if (a_ShouldBroadcast) - { - m_World->BroadcastDestroyEntity(*this); - } - auto ParentChunkCoords = cChunkDef::BlockToChunk(GetPosition()); m_World->QueueTask([this, ParentChunkCoords](cWorld & a_World) { @@ -1166,7 +1170,7 @@ void cEntity::ApplyFriction(Vector3d & a_Speed, double a_SlowdownMultiplier, flo void cEntity::TickBurning(cChunk & a_Chunk) { // If we're about to change worlds, then we can't accurately determine whether we're in lava (#3939) - if (m_IsWorldChangeScheduled) + if (IsWorldChangeScheduled()) { return; } @@ -1310,34 +1314,11 @@ void cEntity::DetectCacti(void) -void cEntity::ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_SetPortalCooldown, bool a_ShouldSendRespawn) -{ - m_NewWorld = a_World; - m_NewWorldPosition = a_NewPosition; - m_IsWorldChangeScheduled = true; - m_WorldChangeSetPortalCooldown = a_SetPortalCooldown; - m_WorldChangeSendRespawn = a_ShouldSendRespawn; -} - - - - - bool cEntity::DetectPortal() { - // If somebody scheduled a world change with ScheduleMoveToWorld, change worlds now. - if (m_IsWorldChangeScheduled) + // If somebody scheduled a world change, do nothing. + if (IsWorldChangeScheduled()) { - m_IsWorldChangeScheduled = false; - - if (m_WorldChangeSetPortalCooldown) - { - // Delay the portal check. - m_PortalCooldownData.m_TicksDelayed = 0; - m_PortalCooldownData.m_ShouldPreventTeleportation = true; - } - - MoveToWorld(m_NewWorld, m_WorldChangeSendRespawn, m_NewWorldPosition); return true; } @@ -1519,69 +1500,81 @@ bool cEntity::DetectPortal() -bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) +void cEntity::DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo) { - UNUSED(a_ShouldSendRespawn); - ASSERT(a_World != nullptr); + ASSERT(a_WorldChangeInfo.m_NewWorld != nullptr); - if (GetWorld() == a_World) + if (a_WorldChangeInfo.m_SetPortalCooldown) { - // Don't move to same world - return false; + m_PortalCooldownData.m_TicksDelayed = 0; + m_PortalCooldownData.m_ShouldPreventTeleportation = true; } - // Ask the plugins if the entity is allowed to changing the world - if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World)) + if (GetWorld() == a_WorldChangeInfo.m_NewWorld) { - // A Plugin doesn't allow the entity to changing the world - return false; + // Moving to same world, don't need to remove from world + SetPosition(a_WorldChangeInfo.m_NewPosition); + return; } + LOGD("Warping entity #%i (%s) from world \"%s\" to \"%s\". Source chunk: (%d, %d) ", + GetUniqueID(), GetClass(), + m_World->GetName(), a_WorldChangeInfo.m_NewWorld->GetName(), + GetChunkX(), GetChunkZ() + ); + // Stop ticking, in preperation for detaching from this world. SetIsTicking(false); - // Tell others we are gone - GetWorld()->BroadcastDestroyEntity(*this); + // Remove from the old world + auto Self = m_World->RemoveEntity(*this); - // Take note of old chunk coords - auto OldChunkCoords = cChunkDef::BlockToChunk(GetPosition()); + // Update entity before calling hook + ResetPosition(a_WorldChangeInfo.m_NewPosition); + SetWorld(a_WorldChangeInfo.m_NewWorld); - // Set position to the new position - ResetPosition(a_NewPosition); + cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*this, *m_World); - // Stop all mobs from targeting this entity - // Stop this entity from targeting other mobs - if (this->IsMob()) - { - cMonster * Monster = static_cast(this); - Monster->SetTarget(nullptr); - Monster->StopEveryoneFromTargetingMe(); - } - - // Queue add to new world and removal from the old one - cWorld * OldWorld = GetWorld(); - SetWorld(a_World); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value - OldWorld->QueueTask([this, OldChunkCoords, 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(), - OldChunkCoords.m_ChunkX, OldChunkCoords.m_ChunkZ - ); - UNUSED(OldChunkCoords); // Non Debug mode only - a_World->AddEntity(a_OldWorld.RemoveEntity(*this)); - cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*this, a_OldWorld); - }); - return true; + // Don't do anything after adding as the old world's CS no longer protects us + a_WorldChangeInfo.m_NewWorld->AddEntity(std::move(Self)); } -bool cEntity::MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) +bool cEntity::MoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_SetPortalCooldown, bool a_ShouldSendRespawn) { - return DoMoveToWorld(a_World, a_ShouldSendRespawn, a_NewPosition); + ASSERT(a_World != nullptr); + + // Ask the plugins if the entity is allowed to change world + if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World)) + { + // A Plugin isn't allowing the entity to change world + return false; + } + + // Create new world change info + auto NewWCI = cpp14::make_unique(); + *NewWCI = { a_World, a_NewPosition, a_SetPortalCooldown, a_ShouldSendRespawn }; + + // Publish atomically + auto OldWCI = m_WorldChangeInfo.exchange(std::move(NewWCI)); + + if (OldWCI == nullptr) + { + // Schedule a new world change. + GetWorld()->QueueTask( + [this](cWorld & a_CurWorld) + { + auto WCI = m_WorldChangeInfo.exchange(nullptr); + cWorld::cLock Lock(a_CurWorld); + DoMoveToWorld(*WCI); + } + ); + } + + return true; } @@ -1606,7 +1599,7 @@ bool cEntity::MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn) return false; } - return DoMoveToWorld(World, a_ShouldSendRespawn, Vector3d(World->GetSpawnX(), World->GetSpawnY(), World->GetSpawnZ())); + return MoveToWorld(World, Vector3d(World->GetSpawnX(), World->GetSpawnY(), World->GetSpawnZ()), false, a_ShouldSendRespawn); } @@ -2253,6 +2246,34 @@ void cEntity::RemoveLeashedMob(cMonster * a_Monster) +void cEntity::RemoveAllLeashedMobs() +{ + while (!m_LeashedMobs.empty()) + { + m_LeashedMobs.front()->Unleash(false, true); + } +} + + + + + +void cEntity::BroadcastLeashedMobs() +{ + // If has any mob leashed broadcast every leashed entity to this + if (HasAnyMobLeashed()) + { + for (auto LeashedMob : m_LeashedMobs) + { + m_World->BroadcastLeashEntity(*LeashedMob, *this); + } + } +} + + + + + float cEntity::GetExplosionExposureRate(Vector3d a_ExplosionPosition, float a_ExlosionPower) { double EntitySize = m_Width * m_Width * m_Height; diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 2805ee9e0..b151f745d 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -2,6 +2,7 @@ #pragma once #include "../Item.h" +#include "../OSSupport/AtomicUniquePtr.h" @@ -72,6 +73,16 @@ struct TakeDamageInfo // tolua_begin class cEntity { +protected: + /** State variables for MoveToWorld. */ + struct sWorldChangeInfo + { + cWorld * m_NewWorld; + Vector3d m_NewPosition; + bool m_SetPortalCooldown; + bool m_SendRespawn; + }; + public: enum eEntityType @@ -163,6 +174,16 @@ public: Adds the entity to the world. */ virtual bool Initialize(OwnedEntity a_Self, cWorld & a_EntityWorld); + /** Called when the entity is added to a world. + e.g after first spawning or after successfuly moving between worlds. + \param a_World The world being added to. */ + virtual void OnAddToWorld(cWorld & a_World); + + /** Called when the entity is removed from a world. + e.g. When the entity is destroyed or moved to a different world. + \param a_World The world being removed from. */ + virtual void OnRemoveFromWorld(cWorld & a_World); + // tolua_begin eEntityType GetEntityType(void) const { return m_EntityType; } @@ -268,8 +289,14 @@ public: If this returns false, you must stop using the cEntity pointer you have. */ bool IsTicking(void) const; - /** Destroys the entity and schedules it for memory freeing; if a_ShouldBroadcast is set to true, broadcasts the DestroyEntity packet */ - virtual void Destroy(bool a_ShouldBroadcast = true); + /** Destroys the entity, schedules it for memory freeing and broadcasts the DestroyEntity packet */ + virtual void Destroy(); + + OBSOLETE void Destroy(bool a_ShouldBroadcast) + { + LOGWARNING("cEntity:Destory(bool) is deprecated, use cEntity:Destroy() instead."); + Destroy(); + } /** Makes this pawn take damage from an attack by a_Attacker. Damage values are calculated automatically and DoTakeDamage() called */ void TakeDamage(cEntity & a_Attacker); @@ -442,9 +469,18 @@ public: 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 a_ShouldSendRespawn = false); + OBSOLETE void ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_ShouldSetPortalCooldown = false, bool a_ShouldSendRespawn = false) + { + LOGWARNING("ScheduleMoveToWorld is deprecated, use MoveToWorld instead"); + MoveToWorld(a_World, a_NewPosition, a_ShouldSetPortalCooldown, a_ShouldSendRespawn); + } - bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition); + bool MoveToWorld(cWorld * a_World, Vector3d a_NewPosition, bool a_ShouldSetPortalCooldown = false, bool a_ShouldSendRespawn = false); + + bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) + { + return MoveToWorld(a_World, a_NewPosition, false, a_ShouldSendRespawn); + } /** Moves entity to specified world, taking a world pointer */ bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn = true); @@ -454,7 +490,11 @@ public: // tolua_end - virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition); + /** Returns true if a world change is scheduled to happen. */ + bool IsWorldChangeScheduled() const + { + return (m_WorldChangeInfo.load() != nullptr); + } /** Updates clients of changes in the entity. */ virtual void BroadcastMovementUpdate(const cClientHandle * a_Exclude = nullptr); @@ -543,13 +583,16 @@ public: /** Set the entity's status to either ticking or not ticking. */ void SetIsTicking(bool a_IsTicking); - /** Adds a mob to the leashed list of mobs */ + /** Adds a mob to the leashed list of mobs. */ void AddLeashedMob(cMonster * a_Monster); - /** Removes a mob from the leashed list of mobs */ + /** Removes a mob from the leashed list of mobs. */ void RemoveLeashedMob(cMonster * a_Monster); - /** Returs whether the entity has any mob leashed to */ + /** Removes all mobs from the leashed list of mobs. */ + void RemoveAllLeashedMobs(); + + /** Returs whether the entity has any mob leashed to it. */ bool HasAnyMobLeashed() const { return m_LeashedMobs.size() > 0; } /** a lightweight calculation approach to get explosion exposure rate @@ -619,12 +662,8 @@ protected: cWorld * m_World; - /** State variables for ScheduleMoveToWorld. */ - bool m_IsWorldChangeScheduled; - bool m_WorldChangeSetPortalCooldown; - bool m_WorldChangeSendRespawn; - cWorld * m_NewWorld; - Vector3d m_NewWorldPosition; + /** If not nullptr, a world change is scheduled and a task is queued in the current world. */ + cAtomicUniquePtr m_WorldChangeInfo; /** Whether the entity is capable of taking fire or lava damage. */ bool m_IsFireproof; @@ -671,6 +710,10 @@ protected: overrides can provide further processing, such as forcing players to move at the given speed. */ virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ); + /** Handles the moving of this entity between worlds. + Should handle degenerate cases such as moving to the same world. */ + virtual void DoMoveToWorld(const sWorldChangeInfo & a_WorldChangeInfo); + virtual void Destroyed(void) {} // Called after the entity has been destroyed /** Applies friction to an entity @@ -689,6 +732,8 @@ protected: Only to be used when the caller will broadcast a teleport or equivalent to clients. */ virtual void ResetPosition(Vector3d a_NewPos); + /** If has any mobs are leashed, broadcasts every leashed entity to this. */ + void BroadcastLeashedMobs(); private: diff --git a/src/Entities/ExpOrb.cpp b/src/Entities/ExpOrb.cpp index ad6f6e97d..3d0c9e2b8 100644 --- a/src/Entities/ExpOrb.cpp +++ b/src/Entities/ExpOrb.cpp @@ -49,7 +49,7 @@ void cExpOrb::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) a_Player.DeltaExperience(m_Reward); m_World->BroadcastSoundEffect("entity.experience_orb.pickup", GetPosition(), 0.5f, (0.75f + (static_cast((GetUniqueID() * 23) % 32)) / 64)); - Destroy(true); + Destroy(); return true; } @@ -84,7 +84,7 @@ void cExpOrb::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_Timer += a_Dt; if (m_Timer >= std::chrono::minutes(5)) { - Destroy(true); + Destroy(); } } @@ -96,7 +96,7 @@ bool cExpOrb::DoTakeDamage(TakeDamageInfo & a_TDI) { if (a_TDI.DamageType == dtCactusContact) { - Destroy(true); + Destroy(); return true; } diff --git a/src/Entities/FallingBlock.cpp b/src/Entities/FallingBlock.cpp index 55b9e81e1..132acd09a 100644 --- a/src/Entities/FallingBlock.cpp +++ b/src/Entities/FallingBlock.cpp @@ -46,7 +46,7 @@ void cFallingBlock::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) // Fallen out of this world, just continue falling until out of sight, then destroy: if (BlockY < VOID_BOUNDARY) { - Destroy(true); + Destroy(); } return; } @@ -64,7 +64,7 @@ void cFallingBlock::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) // Fallen onto a block that breaks this into pickups (e. g. half-slab) // Must finish the fall with coords one below the block: cSandSimulator::FinishFalling(m_World, BlockX, BlockY, BlockZ, m_BlockType, m_BlockMeta); - Destroy(true); + Destroy(); return; } else if (!cSandSimulator::CanContinueFallThrough(BlockBelow)) @@ -83,14 +83,14 @@ void cFallingBlock::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { cSandSimulator::FinishFalling(m_World, BlockX, BlockY + 1, BlockZ, m_BlockType, m_BlockMeta); } - Destroy(true); + Destroy(); return; } else if ((m_BlockType == E_BLOCK_CONCRETE_POWDER) && IsBlockWater(BlockBelow)) { // Concrete powder falling into water solidifies on the first water it touches cSandSimulator::FinishFalling(m_World, BlockX, BlockY, BlockZ, E_BLOCK_CONCRETE, m_BlockMeta); - Destroy(true); + Destroy(); return; } diff --git a/src/Entities/Floater.cpp b/src/Entities/Floater.cpp index c9cc526a7..7b0314ae5 100644 --- a/src/Entities/Floater.cpp +++ b/src/Entities/Floater.cpp @@ -179,7 +179,7 @@ void cFloater::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) if (!m_World->DoWithEntityByID(m_PlayerID, [](cEntity &) { return true; })) // The owner doesn't exist anymore. Destroy the floater entity. { - Destroy(true); + Destroy(); } if (m_AttachedMobID != cEntity::INVALID_ID) diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index be64e9e2f..679e9d5ff 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -156,7 +156,7 @@ void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_Timer += a_Dt; // In case we have to destroy the pickup in the same tick. if (m_Timer > std::chrono::milliseconds(500)) { - Destroy(true); + Destroy(); return; } } @@ -180,14 +180,14 @@ void cPickup::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { if (m_Timer > std::chrono::milliseconds(500)) // 0.5 second { - Destroy(true); + Destroy(); return; } } if (m_Timer > m_Lifetime) { - Destroy(true); + Destroy(); return; } } @@ -200,7 +200,7 @@ bool cPickup::DoTakeDamage(TakeDamageInfo & a_TDI) { if (a_TDI.DamageType == dtCactusContact) { - Destroy(true); + Destroy(); return true; } diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index e7b6ade15..421eddbd5 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -193,9 +193,6 @@ bool cPlayer::Initialize(OwnedEntity a_Self, cWorld & a_World) cPluginManager::Get()->CallHookSpawnedEntity(*GetWorld(), *this); - // Spawn the entity on the clients: - GetWorld()->BroadcastSpawnEntity(*this); - return true; } @@ -243,6 +240,9 @@ void cPlayer::SpawnOn(cClientHandle & a_Client) { return; } + + LOGD("Spawing %s on %s", GetName().c_str(), a_Client.GetUsername().c_str()); + a_Client.SendPlayerSpawn(*this); a_Client.SendEntityHeadLook(*this); a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem()); @@ -1225,7 +1225,7 @@ void cPlayer::Respawn(void) if (GetWorld() != m_SpawnWorld) { - ScheduleMoveToWorld(m_SpawnWorld, GetLastBedPos(), false); + MoveToWorld(m_SpawnWorld, GetLastBedPos(), false); } else { @@ -2003,92 +2003,70 @@ void cPlayer::TossItems(const cItems & a_Items) -bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) +void cPlayer::DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) { - ASSERT(a_World != nullptr); - ASSERT(IsTicking()); + ASSERT(a_WorldChangeInfo.m_NewWorld != nullptr); - if (GetWorld() == a_World) + // Reset portal cooldown + if (a_WorldChangeInfo.m_SetPortalCooldown) { - // Don't move to same world - return false; + m_PortalCooldownData.m_TicksDelayed = 0; + m_PortalCooldownData.m_ShouldPreventTeleportation = true; } - // Ask the plugins if the player is allowed to change the world - if (cRoot::Get()->GetPluginManager()->CallHookEntityChangingWorld(*this, *a_World)) + if (m_World == a_WorldChangeInfo.m_NewWorld) { - // A Plugin doesn't allow the player to change the world - return false; + // Moving to same world, don't need to remove from world + SetPosition(a_WorldChangeInfo.m_NewPosition); + return; } - GetWorld()->QueueTask([this, a_World, a_ShouldSendRespawn, a_NewPosition](cWorld & a_OldWorld) + LOGD("Warping player \"%s\" from world \"%s\" to \"%s\". Source chunk: (%d, %d) ", + GetName(), GetWorld()->GetName(), a_WorldChangeInfo.m_NewWorld->GetName(), + GetChunkX(), GetChunkZ() + ); + + // Stop all mobs from targeting this player + StopEveryoneFromTargetingMe(); + + // Prevent further ticking in this world + SetIsTicking(false); + + // Remove from the old world + auto & OldWorld = *GetWorld(); + auto Self = OldWorld.RemovePlayer(*this); + + ResetPosition(a_WorldChangeInfo.m_NewPosition); + FreezeInternal(a_WorldChangeInfo.m_NewPosition, false); + SetWorld(a_WorldChangeInfo.m_NewWorld); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value + + // Set capabilities based on new world + SetCapabilities(); + + cClientHandle * ch = GetClientHandle(); + if (ch != nullptr) { // The clienthandle caches the coords of the chunk we're standing at. Invalidate this. - GetClientHandle()->InvalidateCachedSentChunk(); + ch->InvalidateCachedSentChunk(); - // Prevent further ticking in this world - SetIsTicking(false); - - // Tell others we are gone - GetWorld()->BroadcastDestroyEntity(*this); - - // Remove player from world - // Make sure that RemovePlayer didn't return a valid smart pointer, due to the second parameter being false - // We remain valid and not destructed after this call - VERIFY(!GetWorld()->RemovePlayer(*this, false)); - - // Set position to the new position - ResetPosition(a_NewPosition); - FreezeInternal(a_NewPosition, false); - - // Stop all mobs from targeting this player - StopEveryoneFromTargetingMe(); - - // Deal with new world - SetWorld(a_World); - - // Set capabilities based on new world - SetCapabilities(); - - cClientHandle * ch = this->GetClientHandle(); - if (ch != nullptr) + // Send the respawn packet: + if (a_WorldChangeInfo.m_SendRespawn) { - // Send the respawn packet: - if (a_ShouldSendRespawn) - { - m_ClientHandle->SendRespawn(a_World->GetDimension()); - } - - // Update the view distance. - ch->SetViewDistance(m_ClientHandle->GetRequestedViewDistance()); - - // Send current weather of target world to player - if (a_World->GetDimension() == dimOverworld) - { - ch->SendWeather(a_World->GetWeather()); - } + ch->SendRespawn(a_WorldChangeInfo.m_NewWorld->GetDimension()); } - // Broadcast the player into the new world. - a_World->BroadcastSpawnEntity(*this); + // Update the view distance. + ch->SetViewDistance(ch->GetRequestedViewDistance()); - // Queue add to new world and removal from the old one + // Send current weather of target world to player + if (a_WorldChangeInfo.m_NewWorld->GetDimension() == dimOverworld) + { + ch->SendWeather(a_WorldChangeInfo.m_NewWorld->GetWeather()); + } + } - // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value - cChunk * ParentChunk = this->GetParentChunk(); - - 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() - ); - - // New world will take over and announce client at its next tick - auto PlayerPtr = static_cast(ParentChunk->RemoveEntity(*this).release()); - a_World->AddPlayer(std::unique_ptr(PlayerPtr), &a_OldWorld); - }); - - return true; + // New world will take over and announce client at its next tick + a_WorldChangeInfo.m_NewWorld->AddPlayer(std::move(Self), &OldWorld); } @@ -2515,7 +2493,7 @@ void cPlayer::HandleFloater() } m_World->DoWithEntityByID(m_FloaterID, [](cEntity & a_Entity) { - a_Entity.Destroy(true); + a_Entity.Destroy(); return true; } ); diff --git a/src/Entities/Player.h b/src/Entities/Player.h index c9249b2f1..1ce2c5d9d 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -387,10 +387,6 @@ public: void SetVisible( bool a_bVisible); // tolua_export 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; - /** Saves all player data, such as inventory, to JSON */ bool SaveToDisk(void); @@ -735,6 +731,8 @@ protected: /** The main hand of the player */ eMainHand m_MainHand; + virtual void DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) override; + /** 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; diff --git a/src/Entities/TNTEntity.cpp b/src/Entities/TNTEntity.cpp index c76b09119..fa17296cf 100644 --- a/src/Entities/TNTEntity.cpp +++ b/src/Entities/TNTEntity.cpp @@ -34,7 +34,7 @@ void cTNTEntity::SpawnOn(cClientHandle & a_ClientHandle) void cTNTEntity::Explode(void) { m_FuseTicks = 0; - Destroy(true); + Destroy(); FLOGD("BOOM at {0}", GetPosition()); m_World->DoExplosionAt(4.0, GetPosX() + 0.49, GetPosY() + 0.49, GetPosZ() + 0.49, true, esPrimedTNT, this); } diff --git a/src/Entities/WitherSkullEntity.cpp b/src/Entities/WitherSkullEntity.cpp index 65e055e59..55f1ff32f 100644 --- a/src/Entities/WitherSkullEntity.cpp +++ b/src/Entities/WitherSkullEntity.cpp @@ -43,7 +43,7 @@ void cWitherSkullEntity::OnHitEntity(cEntity & a_EntityHit, Vector3d a_HitPos) // TODO: Explode // TODO: Apply wither effect to entity and others nearby - Destroy(true); + Destroy(); } diff --git a/src/Items/ItemFishingRod.h b/src/Items/ItemFishingRod.h index 60e0617d6..2d42411e3 100644 --- a/src/Items/ItemFishingRod.h +++ b/src/Items/ItemFishingRod.h @@ -28,7 +28,7 @@ public: m_Pos = Floater.GetPosition(); m_BitePos = Floater.GetBitePos(); m_AttachedMobID = Floater.GetAttachedMobID(); - Floater.Destroy(true); + Floater.Destroy(); return true; } diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index dfcd0dd6a..09f937564 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -132,12 +132,12 @@ cMonster::~cMonster() -void cMonster::Destroy(bool a_ShouldBroadcast) +void cMonster::OnRemoveFromWorld(cWorld & a_World) { if (IsLeashed()) { cEntity * LeashedTo = GetLeashedTo(); - Unleash(false, a_ShouldBroadcast); + Unleash(false, true); // Remove leash knot if there are no more mobs leashed to if (!LeashedTo->HasAnyMobLeashed() && LeashedTo->IsLeashKnot()) @@ -146,7 +146,7 @@ void cMonster::Destroy(bool a_ShouldBroadcast) } } - super::Destroy(a_ShouldBroadcast); + super::OnRemoveFromWorld(a_World); } @@ -282,7 +282,7 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_DestroyTimer += a_Dt; if (m_DestroyTimer > std::chrono::seconds(1)) { - Destroy(true); + Destroy(); } return; } @@ -590,6 +590,20 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) +void cMonster::DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) +{ + // Stop all mobs from targeting this entity + // Stop this entity from targeting other mobs + SetTarget(nullptr); + StopEveryoneFromTargetingMe(); + + super::DoMoveToWorld(a_WorldChangeInfo); +} + + + + + void cMonster::KilledBy(TakeDamageInfo & a_TDI) { super::KilledBy(a_TDI); diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 11d49b82e..676e8ebe5 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -43,7 +43,7 @@ public: virtual ~cMonster() override; - virtual void Destroy(bool a_ShouldBroadcast = true) override; + virtual void OnRemoveFromWorld(cWorld & a_World) override; virtual void Destroyed() override; @@ -319,6 +319,8 @@ protected: /** Adds weapon that is equipped with the chance saved in m_DropChance[...] (this will be greter than 1 if picked up or 0.085 + (0.01 per LootingLevel) if born with) to the drop */ void AddRandomWeaponDropItem(cItems & a_Drops, unsigned int a_LootingLevel); + virtual void DoMoveToWorld(const cEntity::sWorldChangeInfo & a_WorldChangeInfo) override; + private: /** A pointer to the entity this mobile is aiming to reach. The validity of this pointer SHALL be guaranteed by the pointee; diff --git a/src/NetherPortalScanner.cpp b/src/NetherPortalScanner.cpp index 9625ed7cf..78cdb2f02 100644 --- a/src/NetherPortalScanner.cpp +++ b/src/NetherPortalScanner.cpp @@ -296,7 +296,7 @@ void cNetherPortalScanner::OnDisabled(void) } FLOGD("Placing player at {0}", Position); - m_Entity->ScheduleMoveToWorld(m_World, Position, true); + m_Entity->MoveToWorld(m_World, Position, true); delete this; } diff --git a/src/OSSupport/AtomicUniquePtr.h b/src/OSSupport/AtomicUniquePtr.h new file mode 100644 index 000000000..5b18763d3 --- /dev/null +++ b/src/OSSupport/AtomicUniquePtr.h @@ -0,0 +1,81 @@ + + +#pragma once + + +/** An RAII wrapper for std::atomic. */ +template +class cAtomicUniquePtr +{ +public: + static_assert(!std::is_array::value, "cAtomicUniquePtr does not support arrays"); + DISALLOW_COPY_AND_ASSIGN(cAtomicUniquePtr); + + cAtomicUniquePtr() NOEXCEPT: + m_Ptr(nullptr) + { + } + + + cAtomicUniquePtr(std::unique_ptr a_Ptr) NOEXCEPT: + m_Ptr(a_Ptr.release()) + { + } + + cAtomicUniquePtr & operator = (std::unique_ptr a_Ptr) NOEXCEPT + { + store(std::move(a_Ptr)); + return *this; + } + + ~cAtomicUniquePtr() NOEXCEPT + { + delete load(); + } + + operator T * () const NOEXCEPT + { + return load(); + } + + bool compare_exchange_weak(T *& a_Expected, std::unique_ptr && a_Desired, std::memory_order a_Order = std::memory_order_seq_cst) NOEXCEPT + { + bool DidExchange = m_Ptr.compare_exchange_weak(a_Expected, a_Desired.get(), a_Order); + if (DidExchange) + { + // Only release ownership from the caller if the exchange occurred + a_Desired.release(); + } + return DidExchange; + } + + bool compare_exchange_strong(T *& a_Expected, std::unique_ptr && a_Desired, std::memory_order a_Order = std::memory_order_seq_cst) NOEXCEPT + { + bool DidExchange = m_Ptr.compare_exchange_strong(a_Expected, a_Desired.get(), a_Order); + if (DidExchange) + { + // Only release ownership from the caller if the exchange occurred + a_Desired.release(); + } + return DidExchange; + } + + std::unique_ptr exchange(std::unique_ptr a_Ptr, std::memory_order a_Order = std::memory_order_seq_cst) NOEXCEPT + { + return std::unique_ptr{ m_Ptr.exchange(a_Ptr.release(), a_Order) }; + } + + T * load(std::memory_order a_Order = std::memory_order_seq_cst) const NOEXCEPT + { + return m_Ptr.load(a_Order); + } + + void store(std::unique_ptr a_Ptr, std::memory_order a_Order = std::memory_order_seq_cst) NOEXCEPT + { + // Store new value and delete old value + delete m_Ptr.exchange(a_Ptr.release(), a_Order); + } + +private: + std::atomic m_Ptr; +}; diff --git a/src/OSSupport/CMakeLists.txt b/src/OSSupport/CMakeLists.txt index 332b880ed..9f3fcb8a0 100644 --- a/src/OSSupport/CMakeLists.txt +++ b/src/OSSupport/CMakeLists.txt @@ -19,6 +19,7 @@ SET (SRCS ) SET (HDRS + AtomicUniquePtr.h CriticalSection.h Errors.h Event.h diff --git a/src/World.cpp b/src/World.cpp index 312f5967a..a80cffbcc 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -1024,6 +1024,7 @@ void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_La Entity->SetWorld(this); auto EntityPtr = Entity.get(); m_ChunkMap->AddEntity(std::move(Entity)); + EntityPtr->OnAddToWorld(*this); ASSERT(!EntityPtr->IsTicking()); EntityPtr->SetIsTicking(true); } @@ -1167,14 +1168,14 @@ void cWorld::TickMobs(std::chrono::milliseconds a_Dt) { if (Monster.GetMobType() != eMonsterType::mtWolf) { - Monster.Destroy(true); + Monster.Destroy(); } else { auto & Wolf = static_cast(Monster); if (!Wolf.IsAngry() && !Wolf.IsTame()) { - Monster.Destroy(true); + Monster.Destroy(); } } } @@ -2454,23 +2455,34 @@ void cWorld::AddPlayer(std::unique_ptr a_Player, cWorld * a_OldWorld) -std::unique_ptr cWorld::RemovePlayer(cPlayer & a_Player, bool a_RemoveFromChunk) +std::unique_ptr cWorld::RemovePlayer(cPlayer & a_Player) { - std::unique_ptr PlayerPtr; + // Check the chunkmap + std::unique_ptr PlayerPtr(static_cast(m_ChunkMap->RemoveEntity(a_Player).release())); - if (a_RemoveFromChunk) + if (PlayerPtr != nullptr) { - // To prevent iterator invalidations when an entity goes through a portal and calls this function whilst being ticked by cChunk - // we should not change cChunk's entity list if asked not to - PlayerPtr = std::unique_ptr(static_cast(m_ChunkMap->RemoveEntity(a_Player).release())); + // Player found in the world, tell it it's being removed + PlayerPtr->OnRemoveFromWorld(*this); } + else // Check the awaiting players list { cCSLock Lock(m_CSPlayersToAdd); - m_PlayersToAdd.remove_if([&](const decltype(m_PlayersToAdd)::value_type & value) -> bool + auto itr = std::find_if(m_PlayersToAdd.begin(), m_PlayersToAdd.end(), + [&](const decltype(m_PlayersToAdd)::value_type & value) + { + return (value.first.get() == &a_Player); + } + ); + + if (itr != m_PlayersToAdd.end()) { - return (value.first.get() == &a_Player); - }); + PlayerPtr = std::move(itr->first); + m_PlayersToAdd.erase(itr); + } } + + // Remove from the player list { cCSLock Lock(m_CSPlayers); LOGD("Removing player %s from world \"%s\"", a_Player.GetName().c_str(), m_WorldName.c_str()); @@ -3076,6 +3088,7 @@ OwnedEntity cWorld::RemoveEntity(cEntity & a_Entity) auto Entity = m_ChunkMap->RemoveEntity(a_Entity); if (Entity != nullptr) { + Entity->OnRemoveFromWorld(*this); return Entity; } @@ -3475,6 +3488,7 @@ void cWorld::AddQueuedPlayers(void) // Add to chunkmap, if not already there (Spawn vs MoveToWorld): auto PlayerPtr = Player.get(); m_ChunkMap->AddEntityIfNotPresent(std::move(Player)); + PlayerPtr->OnAddToWorld(*this); ASSERT(!PlayerPtr->IsTicking()); PlayerPtr->SetIsTicking(true); AddedPlayerPtrs.emplace_back(PlayerPtr, AwaitingPlayer.second); diff --git a/src/World.h b/src/World.h index 159fa7d93..4363804c6 100644 --- a/src/World.h +++ b/src/World.h @@ -263,9 +263,8 @@ public: /** Removes the player from the world. Removes the player from the addition queue, too, if appropriate. If the player has a ClientHandle, the ClientHandle is removed from all chunks in the world and will not be ticked by this world anymore. - @param a_RemoveFromChunk determines if the entity should be removed from its chunk as well. Should be false when ticking from cChunk. @return An owning reference to the given player. */ - std::unique_ptr RemovePlayer(cPlayer & a_Player, bool a_RemoveFromChunk); + std::unique_ptr RemovePlayer(cPlayer & a_Player); #ifdef _DEBUG bool IsPlayerReferencedInWorldOrChunk(cPlayer & a_Player);