summaryrefslogtreecommitdiffstats
path: root/src/SpawnPrepare.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/SpawnPrepare.cpp287
1 files changed, 231 insertions, 56 deletions
diff --git a/src/SpawnPrepare.cpp b/src/SpawnPrepare.cpp
index 3ea383ee9..4f9086772 100644
--- a/src/SpawnPrepare.cpp
+++ b/src/SpawnPrepare.cpp
@@ -12,17 +12,17 @@ class cSpawnPrepareCallback :
public cChunkCoordCallback
{
public:
- cSpawnPrepareCallback(cSpawnPrepare & a_SpawnPrepare) :
- m_SpawnPrepare(a_SpawnPrepare)
+ cSpawnPrepareCallback(std::shared_ptr<cSpawnPrepare> a_SpawnPrepare) :
+ m_SpawnPrepare(std::move(a_SpawnPrepare))
{
}
protected:
- cSpawnPrepare & m_SpawnPrepare;
+ std::shared_ptr<cSpawnPrepare> m_SpawnPrepare;
virtual void Call(int a_ChunkX, int a_ChunkZ, bool a_IsSuccess) override
{
- m_SpawnPrepare.PreparedChunkCallback(a_ChunkX, a_ChunkZ);
+ m_SpawnPrepare->PreparedChunkCallback(a_ChunkX, a_ChunkZ);
}
};
@@ -30,16 +30,13 @@ protected:
-cSpawnPrepare::cSpawnPrepare(cWorld & a_World, int a_SpawnChunkX, int a_SpawnChunkZ, int a_PrepareDistance, int a_FirstIdx):
+cSpawnPrepare::cSpawnPrepare(cWorld & a_World, int a_PrepareDistance, std::function<void()> a_PreparationCompletedCallback):
m_World(a_World),
- m_SpawnChunkX(a_SpawnChunkX),
- m_SpawnChunkZ(a_SpawnChunkZ),
- m_PrepareDistance(a_PrepareDistance),
- m_NextIdx(a_FirstIdx),
- m_MaxIdx(a_PrepareDistance * a_PrepareDistance),
+ m_TotalChunks(a_PrepareDistance * a_PrepareDistance),
m_NumPrepared(0),
m_LastReportTime(std::chrono::steady_clock::now()),
- m_LastReportChunkCount(0)
+ m_LastReportChunkCount(0),
+ m_PreparationCompletedCallback(std::move(a_PreparationCompletedCallback))
{
}
@@ -49,24 +46,50 @@ cSpawnPrepare::cSpawnPrepare(cWorld & a_World, int a_SpawnChunkX, int a_SpawnChu
-void cSpawnPrepare::PrepareChunks(cWorld & a_World, int a_SpawnChunkX, int a_SpawnChunkZ, int a_PrepareDistance)
+void cSpawnPrepare::PrepareChunks(cWorld & a_World, int a_PrepareDistance, std::function<void()> a_PreparationCompletedCallback)
{
+ auto PerformanceAnalysisObject = std::make_shared<cSpawnPrepare>(a_World, a_PrepareDistance, a_PreparationCompletedCallback);
+ auto HalfPrepareDistance = (a_PrepareDistance - 1) / 2.f;
+ auto NegativePrepareDistance = FloorC(-HalfPrepareDistance), PositivePrepareDistance = FloorC(HalfPrepareDistance);
- // Queue the initial chunks:
- int MaxIdx = a_PrepareDistance * a_PrepareDistance;
- int maxQueue = std::min(MaxIdx - 1, 100); // Number of chunks to queue at once
- cSpawnPrepare prep(a_World, a_SpawnChunkX, a_SpawnChunkZ, a_PrepareDistance, maxQueue);
- for (int i = 0; i < maxQueue; i++)
+ for (int ChunkX = NegativePrepareDistance; ChunkX <= PositivePrepareDistance; ++ChunkX)
{
- int chunkX, chunkZ;
- prep.DecodeChunkCoords(i, chunkX, chunkZ);
- a_World.PrepareChunk(chunkX, chunkZ, cpp14::make_unique<cSpawnPrepareCallback>(prep));
- } // for i
+ for (int ChunkZ = NegativePrepareDistance; ChunkZ <= PositivePrepareDistance; ++ChunkZ)
+ {
+ a_World.PrepareChunk(ChunkX, ChunkZ, cpp14::make_unique<cSpawnPrepareCallback>(PerformanceAnalysisObject));
+ }
+ }
+}
+
+
+
+
+
+void cSpawnPrepare::PreparedChunkCallback(int a_ChunkX, int a_ChunkZ)
+{
+ m_NumPrepared += 1;
+ if (m_NumPrepared == m_TotalChunks)
+ {
+ if (m_PreparationCompletedCallback)
+ {
+ m_PreparationCompletedCallback();
+ }
+
+ LOG("Preparing spawn (%s): completed!", m_World.GetName().c_str());
+ return;
+ }
- // Wait for the lighting thread to prepare everything. Event is set in the Call() callback:
- if (MaxIdx > 0)
+ // Report progress every 1 second:
+ auto Now = std::chrono::steady_clock::now();
+ if (Now - m_LastReportTime > std::chrono::seconds(1))
{
- prep.m_EvtFinished.Wait();
+ float PercentDone = static_cast<float>(m_NumPrepared * 100) / m_TotalChunks;
+ float ChunkSpeed = static_cast<float>((m_NumPrepared - m_LastReportChunkCount) * 1000) / std::chrono::duration_cast<std::chrono::milliseconds>(Now - m_LastReportTime).count();
+ LOG("Preparing spawn (%s): %.02f%% (%d/%d; %.02f chunks / sec)",
+ m_World.GetName().c_str(), PercentDone, m_NumPrepared, m_TotalChunks, ChunkSpeed
+ );
+ m_LastReportTime = Now;
+ m_LastReportChunkCount = m_NumPrepared;
}
}
@@ -74,55 +97,207 @@ void cSpawnPrepare::PrepareChunks(cWorld & a_World, int a_SpawnChunkX, int a_Spa
-void cSpawnPrepare::DecodeChunkCoords(int a_Idx, int & a_ChunkX, int & a_ChunkZ)
+bool cSpawnPrepare::IsValidSpawnBiome(cWorld & a_World, int a_ChunkX, int a_ChunkZ)
{
- // A zigzag pattern from the top to bottom, each row alternating between forward-x and backward-x:
- int z = a_Idx / m_PrepareDistance;
- int x = a_Idx % m_PrepareDistance;
- if ((z & 1) == 0)
+ auto Biome = a_World.GetBiomeAt(a_ChunkX, a_ChunkZ);
+ if ((Biome != EMCSBiome::biOcean) && (Biome != EMCSBiome::biFrozenOcean) && (Biome != EMCSBiome::biDeepOcean))
{
- // Reverse every second row:
- x = m_PrepareDistance - 1 - x;
+ return true;
}
- a_ChunkZ = m_SpawnChunkZ + z - m_PrepareDistance / 2;
- a_ChunkX = m_SpawnChunkX + x - m_PrepareDistance / 2;
+
+ return false;
}
-void cSpawnPrepare::PreparedChunkCallback(int a_ChunkX, int a_ChunkZ)
+Vector3d cSpawnPrepare::GenerateRandomSpawn(cWorld & a_World, int a_PrepareDistance)
{
- // Check if this was the last chunk:
- m_NumPrepared += 1;
- if (m_NumPrepared >= m_MaxIdx)
+ LOGD("Generating random spawnpoint...");
+ int ChunkX = 0, ChunkZ = 0;
+
+ for (int CurrentRadius = 0; CurrentRadius <= a_PrepareDistance; ++CurrentRadius)
{
- m_EvtFinished.Set();
- // Must return here, because "this" may have gotten deleted by the previous line
- return;
+ // Iterate through right and left sides
+ for (int PerpendicularRadius = -CurrentRadius; PerpendicularRadius <= CurrentRadius; ++PerpendicularRadius)
+ {
+ if (cSpawnPrepare::IsValidSpawnBiome(a_World, CurrentRadius, PerpendicularRadius))
+ {
+ ChunkX = CurrentRadius;
+ ChunkZ = PerpendicularRadius;
+ goto IUsedAGotoEatThat;
+ }
+
+ if (cSpawnPrepare::IsValidSpawnBiome(a_World, -CurrentRadius, PerpendicularRadius))
+ {
+ ChunkX = -CurrentRadius;
+ ChunkZ = PerpendicularRadius;
+ goto IUsedAGotoEatThat;
+ }
+ }
+
+ // Iterate through top and bottom sides, omitting the corners
+ for (int PerpendicularRadius = -CurrentRadius + 1; PerpendicularRadius < CurrentRadius; ++PerpendicularRadius)
+ {
+ if (cSpawnPrepare::IsValidSpawnBiome(a_World, PerpendicularRadius, CurrentRadius))
+ {
+ ChunkX = PerpendicularRadius;
+ ChunkZ = CurrentRadius;
+ goto IUsedAGotoEatThat;
+ }
+
+ if (cSpawnPrepare::IsValidSpawnBiome(a_World, PerpendicularRadius, -CurrentRadius))
+ {
+ ChunkX = PerpendicularRadius;
+ ChunkZ = -CurrentRadius;
+ goto IUsedAGotoEatThat;
+ }
+ }
}
- // Queue another chunk, if appropriate:
- if (m_NextIdx < m_MaxIdx)
+IUsedAGotoEatThat:
+
+ for (auto BlockX = ChunkX * cChunkDef::Width; BlockX != (ChunkX + 1) * cChunkDef::Width; ++BlockX)
{
- int chunkX, chunkZ;
- DecodeChunkCoords(m_NextIdx, chunkX, chunkZ);
- m_World.GetLightingThread().QueueChunk(chunkX, chunkZ, cpp14::make_unique<cSpawnPrepareCallback>(*this));
- m_NextIdx += 1;
+ for (auto BlockZ = ChunkZ * cChunkDef::Width; BlockZ != (ChunkZ + 1) * cChunkDef::Width; ++BlockZ)
+ {
+ Vector3d Position;
+ Position.x = static_cast<double>(BlockX);
+ Position.z = static_cast<double>(BlockZ);
+
+ if (CanSpawnAt(a_World, Position.x, Position.y, Position.z))
+ {
+ Position += {0.5, 0.0, 0.5};
+
+ LOGINFO("Generated spawn position at {%.2f, %.2f, %.2f}", Position.x, Position.y, Position.z);
+ return Position;
+ }
+ }
}
- // Report progress every 1 second:
- auto Now = std::chrono::steady_clock::now();
- if (Now - m_LastReportTime > std::chrono::seconds(1))
+ int Height;
+ VERIFY(a_World.TryGetHeight(0, 0, Height));
+
+ LOGWARNING("Did not find an acceptable spawnpoint. Defaulted to spawn at the origin, elevation %i blocks", Height);
+ return { 0.0, static_cast<double>(Height), 0.0 };
+}
+
+
+
+
+
+bool cSpawnPrepare::CanSpawnAt(cWorld & a_World, double a_X, double & a_Y, double a_Z)
+{
+ // All this blocks can only be found above ground.
+ // Apart from netherrack (as the Nether is technically a massive cave)
+ static const BLOCKTYPE ValidSpawnBlocks[] =
{
- float PercentDone = static_cast<float>(m_NumPrepared * 100) / m_MaxIdx;
- float ChunkSpeed = static_cast<float>((m_NumPrepared - m_LastReportChunkCount) * 1000) / std::chrono::duration_cast<std::chrono::milliseconds>(Now - m_LastReportTime).count();
- LOG("Preparing spawn (%s): %.02f%% (%d/%d; %.02f chunks / sec)",
- m_World.GetName().c_str(), PercentDone, m_NumPrepared.load(std::memory_order_seq_cst), m_MaxIdx, ChunkSpeed
- );
- m_LastReportTime = Now;
- m_LastReportChunkCount = m_NumPrepared;
+ E_BLOCK_GRASS,
+ E_BLOCK_SAND,
+ E_BLOCK_SNOW,
+ E_BLOCK_SNOW_BLOCK,
+ E_BLOCK_NETHERRACK,
+ E_BLOCK_END_STONE
+ };
+
+ static const int ValidSpawnBlocksCount = ARRAYCOUNT(ValidSpawnBlocks);
+
+ // Increase this by two, because we need two more blocks for body and head
+ int HighestSpawnPoint;
+ VERIFY(a_World.TryGetHeight(static_cast<int>(a_X), static_cast<int>(a_Z), HighestSpawnPoint));
+ HighestSpawnPoint += 2;
+
+ const int LowestSpawnPoint = static_cast<int>(HighestSpawnPoint / 2.0f);
+
+ for (int PotentialY = HighestSpawnPoint; PotentialY > LowestSpawnPoint; --PotentialY)
+ {
+ BLOCKTYPE HeadBlock = a_World.GetBlock(static_cast<int>(a_X), PotentialY, static_cast<int>(a_Z));
+
+ // Is this block safe for spawning
+ if (HeadBlock != E_BLOCK_AIR)
+ {
+ continue;
+ }
+
+ BLOCKTYPE BodyBlock = a_World.GetBlock(static_cast<int>(a_X), PotentialY - 1, static_cast<int>(a_Z));
+
+ // Is this block safe for spawning
+ if (BodyBlock != E_BLOCK_AIR)
+ {
+ continue;
+ }
+
+ BLOCKTYPE FloorBlock = a_World.GetBlock(static_cast<int>(a_X), PotentialY - 2, static_cast<int>(a_Z));
+
+ // Early out - Is the floor block air
+ if (FloorBlock == E_BLOCK_AIR)
+ {
+ continue;
+ }
+
+ // Is the floor block ok
+ bool ValidSpawnBlock = false;
+ for (int BlockIndex = 0; BlockIndex < ValidSpawnBlocksCount; ++BlockIndex)
+ {
+ ValidSpawnBlock |= (ValidSpawnBlocks[BlockIndex] == FloorBlock);
+ }
+
+ if (!ValidSpawnBlock)
+ {
+ continue;
+ }
+
+ if (!CheckPlayerSpawnPoint(a_World, static_cast<int>(a_X), PotentialY - 1, static_cast<int>(a_Z)))
+ {
+ continue;
+ }
+
+ a_Y = PotentialY - 1.0;
+ return true;
}
+
+ return false;
}
+
+
+
+
+bool cSpawnPrepare::CheckPlayerSpawnPoint(cWorld & a_World, int a_PosX, int a_PosY, int a_PosZ)
+{
+ // Check height bounds
+ if (!cChunkDef::IsValidHeight(a_PosY))
+ {
+ return false;
+ }
+
+ // Check that surrounding blocks are neither solid or liquid
+ static const Vector3i SurroundingCoords[] =
+ {
+ Vector3i(0, 0, 1),
+ Vector3i(1, 0, 1),
+ Vector3i(1, 0, 0),
+ Vector3i(1, 0, -1),
+ Vector3i(0, 0, -1),
+ Vector3i(-1, 0, -1),
+ Vector3i(-1, 0, 0),
+ Vector3i(-1, 0, 1),
+ };
+
+ static const int SurroundingCoordsCount = ARRAYCOUNT(SurroundingCoords);
+
+ for (int CoordIndex = 0; CoordIndex < SurroundingCoordsCount; ++CoordIndex)
+ {
+ const int XPos = a_PosX + SurroundingCoords[CoordIndex].x;
+ const int ZPos = a_PosZ + SurroundingCoords[CoordIndex].z;
+
+ const BLOCKTYPE BlockType = a_World.GetBlock(XPos, a_PosY, ZPos);
+ if (cBlockInfo::IsSolid(BlockType) || IsBlockLiquid(BlockType))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}