diff options
Diffstat (limited to 'src/Entities/Entity.cpp')
-rw-r--r-- | src/Entities/Entity.cpp | 355 |
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 |