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
This commit is contained in:
parent
d7a726a423
commit
7d4934534e
@ -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). <b>OBSOLETE</b>, 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). <b>OBSOLETE</b>, 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. <b>OBSOLETE</b>, 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. <b>OBSOLETE</b>, use MoveToWorld instead.",
|
||||
},
|
||||
SetGravity =
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<cMonster*>(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<sWorldChangeInfo>();
|
||||
*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;
|
||||
|
@ -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<sWorldChangeInfo> 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:
|
||||
|
||||
|
@ -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<float>((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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<cPlayer *>(ParentChunk->RemoveEntity(*this).release());
|
||||
a_World->AddPlayer(std::unique_ptr<cPlayer>(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;
|
||||
}
|
||||
);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
@ -28,7 +28,7 @@ public:
|
||||
m_Pos = Floater.GetPosition();
|
||||
m_BitePos = Floater.GetBitePos();
|
||||
m_AttachedMobID = Floater.GetAttachedMobID();
|
||||
Floater.Destroy(true);
|
||||
Floater.Destroy();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
81
src/OSSupport/AtomicUniquePtr.h
Normal file
81
src/OSSupport/AtomicUniquePtr.h
Normal file
@ -0,0 +1,81 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
/** An RAII wrapper for std::atomic<T*>. */
|
||||
template <typename T>
|
||||
class cAtomicUniquePtr
|
||||
{
|
||||
public:
|
||||
static_assert(!std::is_array<T>::value, "cAtomicUniquePtr does not support arrays");
|
||||
DISALLOW_COPY_AND_ASSIGN(cAtomicUniquePtr);
|
||||
|
||||
cAtomicUniquePtr() NOEXCEPT:
|
||||
m_Ptr(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
cAtomicUniquePtr(std::unique_ptr<T> a_Ptr) NOEXCEPT:
|
||||
m_Ptr(a_Ptr.release())
|
||||
{
|
||||
}
|
||||
|
||||
cAtomicUniquePtr & operator = (std::unique_ptr<T> 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<T> && 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<T> && 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<T> exchange(std::unique_ptr<T> a_Ptr, std::memory_order a_Order = std::memory_order_seq_cst) NOEXCEPT
|
||||
{
|
||||
return std::unique_ptr<T>{ 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<T> 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<T*> m_Ptr;
|
||||
};
|
@ -19,6 +19,7 @@ SET (SRCS
|
||||
)
|
||||
|
||||
SET (HDRS
|
||||
AtomicUniquePtr.h
|
||||
CriticalSection.h
|
||||
Errors.h
|
||||
Event.h
|
||||
|
@ -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<cWolf &>(Monster);
|
||||
if (!Wolf.IsAngry() && !Wolf.IsTame())
|
||||
{
|
||||
Monster.Destroy(true);
|
||||
Monster.Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2454,23 +2455,34 @@ void cWorld::AddPlayer(std::unique_ptr<cPlayer> a_Player, cWorld * a_OldWorld)
|
||||
|
||||
|
||||
|
||||
std::unique_ptr<cPlayer> cWorld::RemovePlayer(cPlayer & a_Player, bool a_RemoveFromChunk)
|
||||
std::unique_ptr<cPlayer> cWorld::RemovePlayer(cPlayer & a_Player)
|
||||
{
|
||||
std::unique_ptr<cPlayer> PlayerPtr;
|
||||
// Check the chunkmap
|
||||
std::unique_ptr<cPlayer> PlayerPtr(static_cast<cPlayer *>(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<cPlayer>(static_cast<cPlayer *>(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);
|
||||
|
@ -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<cPlayer> RemovePlayer(cPlayer & a_Player, bool a_RemoveFromChunk);
|
||||
std::unique_ptr<cPlayer> RemovePlayer(cPlayer & a_Player);
|
||||
|
||||
#ifdef _DEBUG
|
||||
bool IsPlayerReferencedInWorldOrChunk(cPlayer & a_Player);
|
||||
|
Loading…
Reference in New Issue
Block a user