summaryrefslogtreecommitdiffstats
path: root/src/Entities/Entity.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Entities/Entity.cpp')
-rw-r--r--src/Entities/Entity.cpp355
1 files changed, 156 insertions, 199 deletions
diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp
index 2adbc3142..5693bc42b 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());
/*
@@ -133,9 +130,11 @@ const char * cEntity::GetParentClass(void) const
-bool cEntity::Initialize(cWorld & a_World)
+bool cEntity::Initialize(std::unique_ptr<cEntity> a_Entity, cWorld & a_EntityWorld)
{
- if (cPluginManager::Get()->CallHookSpawningEntity(a_World, *this))
+ ASSERT(a_EntityWorld.IsInTickThread());
+
+ if (cPluginManager::Get()->CallHookSpawningEntity(a_EntityWorld, *this) && !IsPlayer())
{
return false;
}
@@ -147,15 +146,22 @@ bool cEntity::Initialize(cWorld & a_World)
);
*/
- ASSERT(m_World == nullptr);
ASSERT(GetParentChunk() == nullptr);
- a_World.AddEntity(this);
- ASSERT(m_World != nullptr);
+ cpp14::move_on_copy_wrapper<decltype(a_Entity)> Entity(std::move(a_Entity));
+
+ // So that entities do not appear in the middle of ticks
+ a_EntityWorld.QueueTask(
+ [Entity, &a_EntityWorld](cWorld & a_World)
+ {
+ auto & EntityPtr = *Entity.value;
- cPluginManager::Get()->CallHookSpawnedEntity(a_World, *this);
+ EntityPtr.SetWorld(&a_EntityWorld);
+ a_EntityWorld.AddEntity(std::move(Entity.value));
- // Spawn the entity on the clients:
- a_World.BroadcastSpawnEntity(*this);
+ a_EntityWorld.BroadcastSpawnEntity(EntityPtr);
+ cPluginManager::Get()->CallHookSpawnedEntity(a_EntityWorld, EntityPtr);
+ }
+ );
return true;
}
@@ -201,7 +207,7 @@ void cEntity::SetParentChunk(cChunk * a_Chunk)
-cChunk * cEntity::GetParentChunk()
+cChunk * cEntity::GetParentChunk() const
{
return m_ParentChunk;
}
@@ -212,8 +218,7 @@ cChunk * cEntity::GetParentChunk()
void cEntity::Destroy(bool a_ShouldBroadcast)
{
- ASSERT(IsTicking());
- ASSERT(GetParentChunk() != nullptr);
+ ASSERT(GetWorld()->IsInTickThread());
SetIsTicking(false);
if (a_ShouldBroadcast)
@@ -221,17 +226,20 @@ void cEntity::Destroy(bool a_ShouldBroadcast)
m_World->BroadcastDestroyEntity(*this);
}
- cChunk * ParentChunk = GetParentChunk();
- m_World->QueueTask([this, ParentChunk](cWorld & a_World)
- {
- LOGD("Destroying entity #%i (%s) from chunk (%d, %d)",
- this->GetUniqueID(), this->GetClass(),
- ParentChunk->GetPosX(), ParentChunk->GetPosZ()
- );
- ParentChunk->RemoveEntity(this);
- delete this;
- });
- Destroyed();
+ // So that entities do not disappear unexpectedly during ticks
+ m_World->QueueTask(
+ [this](cWorld & a_World)
+ {
+ auto ParentChunk = GetParentChunk();
+ LOGD("Destroying entity #%i (%s) from chunk (%d, %d)",
+ GetUniqueID(), GetClass(),
+ ParentChunk->GetPosX(), ParentChunk->GetPosZ()
+ );
+
+ Destroyed(); // TODO: rename to OnPreDestroy()
+ ParentChunk->RemoveEntity(*this);
+ }
+ );
}
@@ -1369,36 +1377,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 +1397,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 +1410,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 +1470,9 @@ bool cEntity::DetectPortal()
return false;
}
+ m_PortalCooldownData.m_ShouldPreventTeleportation = true;
+ m_PortalCooldownData.m_PositionValid = false;
+
if (GetWorld()->GetDimension() == dimEnd)
{
@@ -1499,18 +1481,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 +1493,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 +1512,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 +1708,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 +1729,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