From 73b6a44d5f9e35f7471b7e86e4fd5ee463b2ea44 Mon Sep 17 00:00:00 2001 From: Lane Kolbly Date: Tue, 26 May 2015 20:35:28 -0500 Subject: Implemented nether portal scanning code. --- CONTRIBUTORS | 1 + src/CMakeLists.txt | 2 + src/Chunk.cpp | 2 +- src/Entities/Entity.cpp | 47 ++++++- src/Entities/Entity.h | 26 ++-- src/Entities/Player.cpp | 7 +- src/Entities/Player.h | 2 +- src/NetherPortalScanner.cpp | 290 ++++++++++++++++++++++++++++++++++++++++++++ src/NetherPortalScanner.h | 75 ++++++++++++ 9 files changed, 432 insertions(+), 20 deletions(-) create mode 100644 src/NetherPortalScanner.cpp create mode 100644 src/NetherPortalScanner.h diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 82ae6a7a8..27910f478 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -14,6 +14,7 @@ jan64 jasperarmstrong keyboard Lapayo +lkolbly Luksor linnemannr (Reid Linnemann) M10360 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c68795bb3..429087ae2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET (SRCS MobProximityCounter.cpp MobSpawner.cpp MonsterConfig.cpp + NetherPortalScanner.cpp OverridesSettingsRepository.cpp ProbabDistrib.cpp RankManager.cpp @@ -126,6 +127,7 @@ SET (HDRS MobProximityCounter.h MobSpawner.h MonsterConfig.h + NetherPortalScanner.h OverridesSettingsRepository.h ProbabDistrib.h RankManager.h diff --git a/src/Chunk.cpp b/src/Chunk.cpp index 7af669163..2e83f2b72 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -640,6 +640,7 @@ void cChunk::Tick(std::chrono::milliseconds a_Dt) else if ((*itr)->IsWorldTravellingFrom(m_World)) { // Remove all entities that are travelling to another world + LOGD("Removing entity from [%d, %d] that's travelling between worlds.", m_PosX, m_PosZ); MarkDirty(); (*itr)->SetWorldTravellingFrom(nullptr); itr = m_Entities.erase(itr); @@ -695,7 +696,6 @@ void cChunk::MoveEntityToNewChunk(cEntity * a_Entity) } ASSERT(Neighbor != this); // Moving into the same chunk? wtf? - Neighbor->AddEntity(a_Entity); class cMover : diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp index 91eb0744a..108f79e82 100644 --- a/src/Entities/Entity.cpp +++ b/src/Entities/Entity.cpp @@ -13,6 +13,7 @@ #include "Player.h" #include "Items/ItemHandler.h" #include "../FastRandom.h" +#include "../NetherPortalScanner.h" @@ -42,6 +43,7 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d m_WorldTravellingFrom(nullptr), m_EntityType(a_EntityType), m_World(nullptr), + m_IsWorldChangeScheduled(false), m_IsFireproof(false), m_TicksSinceLastBurnDamage(0), m_TicksSinceLastLavaDamage(0), @@ -1260,9 +1262,26 @@ void cEntity::DetectCacti(void) +void cEntity::ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition) +{ + m_NewWorld = a_World; + m_NewWorldPosition = a_NewPosition; + m_IsWorldChangeScheduled = true; +} + + + bool cEntity::DetectPortal() { + // If somebody scheduled a world change with ScheduleMoveToWorld, change worlds now. + if (m_IsWorldChangeScheduled) + { + m_IsWorldChangeScheduled = false; + MoveToWorld(m_NewWorld, false, m_NewWorldPosition); + return true; + } + if (GetWorld()->GetDimension() == dimOverworld) { if (GetWorld()->GetLinkedNetherWorldName().empty() && GetWorld()->GetLinkedEndWorldName().empty()) @@ -1312,8 +1331,15 @@ bool cEntity::DetectPortal() // Send a respawn packet before world is loaded / generated so the client isn't left in limbo ((cPlayer *)this)->GetClientHandle()->SendRespawn(dimOverworld); } - - return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName()), false); + + Vector3d TargetPos = GetPosition(); + TargetPos.x *= 8.0; + TargetPos.z *= 8.0; + + cWorld * TargetWorld = cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName(), dimNether, GetWorld()->GetName()); + LOGD("Jumping nether -> overworld"); + new cNetherPortalScanner(this, TargetWorld, TargetPos, 256); + return false; } else { @@ -1329,8 +1355,15 @@ bool cEntity::DetectPortal() ((cPlayer *)this)->AwardAchievement(achEnterPortal); ((cPlayer *)this)->GetClientHandle()->SendRespawn(dimNether); } - - return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedNetherWorldName(), dimNether, GetWorld()->GetName()), false); + + Vector3d TargetPos = GetPosition(); + TargetPos.x /= 8.0; + TargetPos.z /= 8.0; + + cWorld * TargetWorld = cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedNetherWorldName(), dimNether, GetWorld()->GetName()); + LOGD("Jumping overworld -> nether"); + new cNetherPortalScanner(this, TargetWorld, TargetPos, 128); + return false; } } case E_BLOCK_END_PORTAL: @@ -1392,7 +1425,7 @@ bool cEntity::DetectPortal() -bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn) +bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) { UNUSED(a_ShouldSendRespawn); ASSERT(a_World != nullptr); @@ -1414,6 +1447,8 @@ bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn) SetWorldTravellingFrom(GetWorld()); // cChunk::Tick() handles entity removal GetWorld()->BroadcastDestroyEntity(*this); + SetPosition(a_NewPosition); + // Queue add to new world a_World->AddEntity(this); cWorld * OldWorld = cRoot::Get()->GetWorld(GetWorld()->GetName()); // Required for the hook HOOK_ENTITY_CHANGED_WORLD @@ -1438,7 +1473,7 @@ bool cEntity::MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn) return false; } - return DoMoveToWorld(World, a_ShouldSendRespawn); + return DoMoveToWorld(World, a_ShouldSendRespawn, GetPosition()); } diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 58d1287e7..8d1d62ddf 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -392,15 +392,20 @@ public: /// Teleports to the coordinates specified virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ); + /// Schedules a MoveToWorld call to occur on the next Tick of the entity + void ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition); + + bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) { return DoMoveToWorld(a_World, a_ShouldSendRespawn, a_NewPosition); } + /** Moves entity to specified world, taking a world pointer */ - bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn = true) { return DoMoveToWorld(a_World, a_ShouldSendRespawn); } + bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn = true) { return MoveToWorld(a_World, a_ShouldSendRespawn, GetPosition()); } /** Moves entity to specified world, taking a world name */ bool MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn = true); // tolua_end - virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn); + virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition); /** Returns if the entity is travelling away from a specified world */ bool IsWorldTravellingFrom(cWorld * a_World) const { return (m_WorldTravellingFrom == a_World); } @@ -530,23 +535,28 @@ protected: eEntityType m_EntityType; cWorld * m_World; + + /** State variables for ScheduleMoveToWorld. */ + bool m_IsWorldChangeScheduled; + cWorld * m_NewWorld; + Vector3d m_NewWorldPosition; - /// Whether the entity is capable of taking fire or lava damage. + /** Whether the entity is capable of taking fire or lava damage. */ bool m_IsFireproof; - /// Time, in ticks, since the last damage dealt by being on fire. Valid only if on fire (IsOnFire()) + /** Time, in ticks, since the last damage dealt by being on fire. Valid only if on fire (IsOnFire()) */ int m_TicksSinceLastBurnDamage; - /// Time, in ticks, since the last damage dealt by standing in lava. Reset to zero when moving out of lava. + /** Time, in ticks, since the last damage dealt by standing in lava. Reset to zero when moving out of lava. */ int m_TicksSinceLastLavaDamage; - /// Time, in ticks, since the last damage dealt by standing in fire. Reset to zero when moving out of fire. + /** Time, in ticks, since the last damage dealt by standing in fire. Reset to zero when moving out of fire. */ int m_TicksSinceLastFireDamage; - /// Time, in ticks, until the entity extinguishes its fire + /** Time, in ticks, until the entity extinguishes its fire */ int m_TicksLeftBurning; - /// Time, in ticks, since the last damage dealt by the void. Reset to zero when moving out of the void. + /** Time, in ticks, since the last damage dealt by the void. Reset to zero when moving out of the void. */ int m_TicksSinceLastVoidDamage; /** Does the actual speed-setting. The default implementation just sets the member variable value; diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index e3e3fac4f..0ca560d75 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -1620,10 +1620,7 @@ void cPlayer::TossItems(const cItems & a_Items) } - - - -bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn) +bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) { ASSERT(a_World != nullptr); @@ -1653,6 +1650,8 @@ bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn) SetWorldTravellingFrom(GetWorld()); // cChunk handles entity removal GetWorld()->RemovePlayer(this, false); + SetPosition(a_NewPosition); + // Queue adding player to the new world, including all the necessary adjustments to the object a_World->AddPlayer(this); cWorld * OldWorld = cRoot::Get()->GetWorld(GetWorld()->GetName()); // Required for the hook HOOK_ENTITY_CHANGED_WORLD diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 799990bf2..dffb61677 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -358,7 +358,7 @@ public: /** 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) override; + 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); diff --git a/src/NetherPortalScanner.cpp b/src/NetherPortalScanner.cpp new file mode 100644 index 000000000..fe563509d --- /dev/null +++ b/src/NetherPortalScanner.cpp @@ -0,0 +1,290 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "NetherPortalScanner.h" +#include "Vector3.h" +#include "Entities/Entity.h" +#include "World.h" + + + + + +cNetherPortalScanner::cNetherPortalScanner(cEntity * a_MovingEntity, cWorld * a_DestinationWorld, Vector3d a_DestPosition, int a_MaxY) : + m_Entity(a_MovingEntity), + m_World(a_DestinationWorld), + m_FoundPortal(false), + m_BuildPlatform(true), + m_Dir(Direction::X), + m_PortalLoc(a_DestPosition.Floor()), + m_Position(a_DestPosition), + m_MaxY(a_MaxY) +{ + int MinX = FloorC((m_Position.x - SearchRadius) / cChunkDef::Width); + int MinZ = FloorC((m_Position.z - SearchRadius) / cChunkDef::Width); + int MaxX = CeilC((m_Position.x + SearchRadius) / cChunkDef::Width); + int MaxZ = CeilC((m_Position.z + SearchRadius) / cChunkDef::Width); + for (int x = MinX; x < MaxX; x++) + { + for (int z = MinZ; z < MaxZ; z++) + { + Add(x, z); + } + } + Enable(*a_DestinationWorld->GetChunkMap()); +} + + + + + +void cNetherPortalScanner::OnChunkAvailable(int a_ChunkX, int a_ChunkZ) +{ + cChunkDef::BlockTypes blocks; + m_World->GetChunkBlockTypes(a_ChunkX, a_ChunkZ, blocks); + + // Iterate through all of the blocks in the chunk + for (unsigned int i = 0; i < cChunkDef::NumBlocks; i++) + { + if (blocks[i] == E_BLOCK_NETHER_PORTAL) + { + Vector3i Coordinate = cChunkDef::IndexToCoordinate(i); + Vector3d PortalLoc = Vector3d(Coordinate.x + a_ChunkX * cChunkDef::Width, Coordinate.y, Coordinate.z + a_ChunkZ * cChunkDef::Width); + if (!m_FoundPortal) + { + m_FoundPortal = true; + m_PortalLoc = PortalLoc; + } + else + { + if ((PortalLoc - m_Position).SqrLength() < (m_PortalLoc - m_Position).SqrLength()) + { + m_FoundPortal = true; + m_PortalLoc = PortalLoc; + } + } + } + } +} + + + + + +bool cNetherPortalScanner::IsValidBuildLocation(Vector3i a_BlockPos) +{ + // Check the base + for (int i = 0; i < SearchSolidBaseWidth; i++) + { + for (int j = 0; j < PortalLength; j++) + { + BLOCKTYPE blocktype = m_World->GetBlock(a_BlockPos.x + i, a_BlockPos.y, a_BlockPos.z + j); + if (!cBlockInfo::IsSolid(blocktype)) + { + return false; + } + + // Check the airspace + for (int k = 1; k < PortalHeight; k++) + { + blocktype = m_World->GetBlock(a_BlockPos.x + i, a_BlockPos.y + k, a_BlockPos.z + j); + if (blocktype != E_BLOCK_AIR) + { + return false; + } + } + } + } + return true; +} + + + + + +bool cNetherPortalScanner::OnAllChunksAvailable(void) +{ + if (m_FoundPortal) + { + // Find the bottom of this portal + while (m_World->GetBlock(m_PortalLoc.x, m_PortalLoc.y, m_PortalLoc.z) == E_BLOCK_NETHER_PORTAL) + { + m_PortalLoc.y -= 1.0; + } + m_PortalLoc.y += 1.0; + + // Figure out which way the portal is facing + int BXP = m_World->GetBlock(m_PortalLoc.x + 1, m_PortalLoc.y, m_PortalLoc.z); + int BXM = m_World->GetBlock(m_PortalLoc.x - 1, m_PortalLoc.y, m_PortalLoc.z); + if ((BXP == E_BLOCK_NETHER_PORTAL) || (BXM == E_BLOCK_NETHER_PORTAL)) + { + // The long axis is along X + m_Dir = Direction::X; + } + else + { + // The long axis is along Z + m_Dir = Direction::Y; + } + } + else + { + // Scan the area for a suitable location + int minx = FloorC(m_Position.x) - BuildSearchRadius; + int minz = FloorC(m_Position.z) - BuildSearchRadius; + int maxx = FloorC(m_Position.x) + BuildSearchRadius; + int maxz = FloorC(m_Position.z) + BuildSearchRadius; + int maxy = m_MaxY; + std::vector Possibilities; + int x, y, z; + for (y = 0; y < maxy - PortalHeight; y++) + { + for (x = minx; x < maxx - PortalLength; x++) + { + for (z = minz; z < maxz - SearchSolidBaseWidth; z++) + { + Vector3i Location = Vector3i(x, y, z); + if (IsValidBuildLocation(Location)) + { + Possibilities.push_back(Vector3i(x, y, z)); + } + } + } + } + + if (Possibilities.size() > 0) + { + m_BuildPlatform = false; + + // Find the nearest + double DistanceToClosest = (Possibilities[0] - m_Position).SqrLength(); + Vector3i Closest = Possibilities[0]; + for (const auto & itr : Possibilities) + { + double Distance = (itr - m_Position).SqrLength(); + if (Distance < DistanceToClosest) + { + DistanceToClosest = Distance; + Closest = itr; + } + } + + m_PortalLoc = Closest; + } + } + return true; +} + + + + + +void cNetherPortalScanner::BuildNetherPortal(Vector3i a_Location, Direction a_Direction, bool a_IncludePlatform) +{ + int x = a_Location.x; + int y = a_Location.y; + int z = a_Location.z; + + // Clear a 3x4x4 area starting right above the base + for (int i = 0; i < SearchSolidBaseWidth; i++) + { + for (int j = 0; j < PortalLength; j++) + { + for (int k = 1; k < PortalHeight; k++) + { + if (a_Direction == Direction::Y) + { + m_World->SetBlock(x + i, y + k, z + j, E_BLOCK_AIR, 0); + } + else if (a_Direction == Direction::X) + { + m_World->SetBlock(x + j, y + k, z + i, E_BLOCK_AIR, 0); + } + } + } + } + + // Put in an obsidian base + if (a_IncludePlatform) + { + for (int j = 0; j < PortalLength; j++) + { + // +2 on the short axis because that's where we deposit the entity + if (a_Direction == Direction::Y) + { + m_World->SetBlock(x + 2, y, z + j, E_BLOCK_OBSIDIAN, 0); + } + else if (a_Direction == Direction::X) + { + m_World->SetBlock(x + j, y, z + 2, E_BLOCK_OBSIDIAN, 0); + } + } + } + + // Build an obsidian frame + for (int i = 0; i < PortalHeight; i++) + { + if (a_Direction == Direction::Y) + { + m_World->SetBlock(x + 1, y + i, z, E_BLOCK_OBSIDIAN, 0); + m_World->SetBlock(x + 1, y + i, z + 3, E_BLOCK_OBSIDIAN, 0); + } + else if (a_Direction == Direction::X) + { + m_World->SetBlock(x, y + i, z + 1, E_BLOCK_OBSIDIAN, 0); + m_World->SetBlock(x + 3, y + i, z + 1, E_BLOCK_OBSIDIAN, 0); + } + } + for (int i = 0; i < PortalLength; i++) + { + if (a_Direction == Direction::Y) + { + m_World->SetBlock(x + 1, y + 4, z + i, E_BLOCK_OBSIDIAN, 0); + m_World->SetBlock(x + 1, y, z + i, E_BLOCK_OBSIDIAN, 0); + } + else if (a_Direction == Direction::X) + { + m_World->SetBlock(x + i, y + 4, z + 1, E_BLOCK_OBSIDIAN, 0); + m_World->SetBlock(x + i, y, z + 1, E_BLOCK_OBSIDIAN, 0); + } + } + + // Fill the frame (place a fire in the bottom) + m_World->SetBlock(x + 1, y + 1, z + 1, E_BLOCK_FIRE, 0); +} + + + + + +void cNetherPortalScanner::OnDisabled(void) +{ + // Now we actually move the player + if (!m_FoundPortal) + { + // Build a new nether portal. + LOGD("Building nether portal at {%d, %d, %d}", m_PortalLoc.x, m_PortalLoc.y, m_PortalLoc.z); + BuildNetherPortal(m_PortalLoc, m_Dir, m_BuildPlatform); + m_PortalLoc.x += 1; + m_PortalLoc.y += 2; + m_PortalLoc.z += 1; + } + + // Put the entity near the opening + Vector3d Position = m_PortalLoc; + if (m_Dir == Direction::Y) + { + Position.x += OutOffset; + Position.z += AcrossOffset; + } + else if (m_Dir == Direction::X) + { + Position.x += AcrossOffset; + Position.z += OutOffset; + } + + LOGD("Placing player at {%f, %f, %f}", Position.x, Position.y, Position.z); + m_Entity->ScheduleMoveToWorld(m_World, Position); + delete this; +} + diff --git a/src/NetherPortalScanner.h b/src/NetherPortalScanner.h new file mode 100644 index 000000000..89ffd7d0e --- /dev/null +++ b/src/NetherPortalScanner.h @@ -0,0 +1,75 @@ + +#pragma once + +#include "Vector3.h" +#include "ChunkStay.h" + + +class cEntity; +class cWorld; + + + + + +// This is the chunk stay which finds nearby nether portals +class cNetherPortalScanner : public cChunkStay +{ +public: + cNetherPortalScanner(cEntity * a_MovingEntity, cWorld * a_DestinationWorld, Vector3d a_DestPosition, int a_MaxY); + virtual void OnChunkAvailable(int a_ChunkX, int a_ChunkY) override; + virtual bool OnAllChunksAvailable(void) override; + virtual void OnDisabled(void) override; + + enum class Direction + { + X, + Y + }; + +private: + + /** Length and height, including the obsidian. */ + static const int PortalLength = 4; + static const int PortalHeight = 5; + + static const int SearchRadius = 128; + static const int BuildSearchRadius = 16; + + /** The width of a solid base to search for when building. */ + static const int SearchSolidBaseWidth = 3; + + /** Where to place the player out from the face and across the face */ + const double OutOffset = 1.5; + const double AcrossOffset = 0.5; + + /** Builds a portal. */ + void BuildNetherPortal(Vector3i a_Location, Direction a_Direction, bool a_IncludePlatform); + + /** Whether the given location is a valid location to build a portal. */ + bool IsValidBuildLocation(Vector3i a_BlockPosition); + + /** The entity that's being moved. */ + cEntity * m_Entity; + + /** The world we're moving the entity to. */ + cWorld * m_World; + + /** Whether we found a portal during the loading of the chunks. */ + bool m_FoundPortal; + + /** Whether to build a platform. True if we couldn't build the portal on solid ground */ + bool m_BuildPlatform; + + /** The direction of the portal. */ + Direction m_Dir; + + /** The position of the pre-existing portal. */ + Vector3i m_PortalLoc; + + /** The center of the search area */ + Vector3d m_Position; + + /** The maximum Y to scan to */ + int m_MaxY; +}; -- cgit v1.2.3