1
0

Improved spawn location calculations.

- Supports Overworld and Nether spawns.
 - Supports spawning under objects, but still above ground (e.g. under the leaves of a tree).
 - Protects against spawning in oceans.
 - Protects against spawning in water.
 - Uses a radial search about the origin, rather than a linear.
 - Correctly calculates Nether spawn on spawn world generation (fixes: cuberite#2548)
 - Fixes a bug in CheckPlayerSpawnPoint() where the X offset was used in both the X and Z coords (BLOCKTYPE BlockType = GetBlock(a_PosX + Coords[i].x, a_PosY, a_PosZ + Coords[i].x);)
This commit is contained in:
samoatesgames 2015-11-13 15:08:16 +00:00
parent 62b1d0b772
commit 19ea5d5423
3 changed files with 181 additions and 78 deletions

View File

@ -1343,7 +1343,7 @@ bool cEntity::DetectPortal()
TargetPos.x *= 8.0; TargetPos.x *= 8.0;
TargetPos.z *= 8.0; TargetPos.z *= 8.0;
cWorld * TargetWorld = cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName(), dimNether, GetWorld()->GetName(), false); cWorld * TargetWorld = cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName(), dimNether, GetWorld()->GetName(), true);
LOGD("Jumping nether -> overworld"); LOGD("Jumping nether -> overworld");
new cNetherPortalScanner(this, TargetWorld, TargetPos, 256); new cNetherPortalScanner(this, TargetWorld, TargetPos, 256);
return true; return true;
@ -1367,7 +1367,7 @@ bool cEntity::DetectPortal()
TargetPos.x /= 8.0; TargetPos.x /= 8.0;
TargetPos.z /= 8.0; TargetPos.z /= 8.0;
cWorld * TargetWorld = cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedNetherWorldName(), dimNether, GetWorld()->GetName(), false); cWorld * TargetWorld = cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedNetherWorldName(), dimNether, GetWorld()->GetName(), true);
LOGD("Jumping overworld -> nether"); LOGD("Jumping overworld -> nether");
new cNetherPortalScanner(this, TargetWorld, TargetPos, 128); new cNetherPortalScanner(this, TargetWorld, TargetPos, 128);
return true; return true;

View File

@ -325,10 +325,17 @@ void cWorld::SetNextBlockTick(int a_BlockX, int a_BlockY, int a_BlockZ)
void cWorld::InitializeSpawn(void) void cWorld::InitializeSpawn(void)
{ {
// For the debugging builds, don't make the server build too much world upon start:
#if defined(_DEBUG) || defined(ANDROID_NDK)
const int DefaultViewDist = 9;
#else
const int DefaultViewDist = 20; // Always prepare an area 20 chunks across, no matter what the actual cClientHandle::VIEWDISTANCE is
#endif // _DEBUG
if (!m_IsSpawnExplicitlySet) if (!m_IsSpawnExplicitlySet)
{ {
// Spawn position wasn't already explicitly set, enerate random solid-land coordinate and then write it to the world configuration: // Spawn position wasn't already explicitly set, enumerate random solid-land coordinate and then write it to the world configuration:
GenerateRandomSpawn(); GenerateRandomSpawn(DefaultViewDist);
cIniFile IniFile; cIniFile IniFile;
IniFile.ReadFile(m_IniFileName); IniFile.ReadFile(m_IniFileName);
IniFile.SetValueF("SpawnPosition", "X", m_SpawnX); IniFile.SetValueF("SpawnPosition", "X", m_SpawnX);
@ -337,20 +344,13 @@ void cWorld::InitializeSpawn(void)
IniFile.WriteFile(m_IniFileName); IniFile.WriteFile(m_IniFileName);
} }
int ChunkX = 0, ChunkZ = 0;
cChunkDef::BlockToChunk(FloorC(m_SpawnX), FloorC(m_SpawnZ), ChunkX, ChunkZ);
// For the debugging builds, don't make the server build too much world upon start:
#if defined(_DEBUG) || defined(ANDROID_NDK)
const int DefaultViewDist = 9;
#else
const int DefaultViewDist = 20; // Always prepare an area 20 chunks across, no matter what the actual cClientHandle::VIEWDISTANCE is
#endif // _DEBUG
cIniFile IniFile; cIniFile IniFile;
IniFile.ReadFile(m_IniFileName); IniFile.ReadFile(m_IniFileName);
int ViewDist = IniFile.GetValueSetI("SpawnPosition", "PregenerateDistance", DefaultViewDist); int ViewDist = IniFile.GetValueSetI("SpawnPosition", "PregenerateDistance", DefaultViewDist);
IniFile.WriteFile(m_IniFileName); IniFile.WriteFile(m_IniFileName);
int ChunkX = 0, ChunkZ = 0;
cChunkDef::BlockToChunk(FloorC(m_SpawnX), FloorC(m_SpawnZ), ChunkX, ChunkZ);
cSpawnPrepare::PrepareChunks(*this, ChunkX, ChunkZ, ViewDist); cSpawnPrepare::PrepareChunks(*this, ChunkX, ChunkZ, ViewDist);
#ifdef TEST_LINEBLOCKTRACER #ifdef TEST_LINEBLOCKTRACER
@ -572,50 +572,158 @@ void cWorld::Start(void)
void cWorld::GenerateRandomSpawn(void) void cWorld::GenerateRandomSpawn(int a_MaxSpawnRadius)
{ {
LOGD("Generating random spawnpoint..."); LOGD("Generating random spawnpoint...");
bool foundSpawnPoint = false;
int SpawnX = FloorC(m_SpawnX);
int SpawnZ = FloorC(m_SpawnZ);
// Look for a spawn point at most 100 chunks away from map center:
for (int i = 0; i < 100; i++)
{
EMCSBiome biome = GetBiomeAt(SpawnX, SpawnZ);
if ( // Number of checks to make sure we have a valid biome
(biome != biOcean) && (biome != biFrozenOcean) && // The biome is acceptable (don't want a small ocean island) // 100 checks will check across 400 chunks, we should have
!IsBlockWaterOrIce(GetBlock(SpawnX, GetHeight(SpawnX, SpawnZ), SpawnZ)) // The terrain is acceptable (don't want to spawn inside a lake / river) // a valid biome by then.
) static const int BiomeCheckCount = 100;
// Make sure we are in a valid biome
Vector3i BiomeOffset = Vector3i(0, 0, 0);
for (int BiomeCheckIndex = 0; BiomeCheckIndex < BiomeCheckCount; ++BiomeCheckIndex)
{ {
if (CheckPlayerSpawnPoint(SpawnX, GetHeight(SpawnX, SpawnZ), SpawnZ)) EMCSBiome Biome = GetBiomeAt(BiomeOffset.x, BiomeOffset.z);
if ((Biome == EMCSBiome::biOcean) || (Biome == EMCSBiome::biFrozenOcean))
{ {
// A good spawnpoint was found BiomeOffset += Vector3d(cChunkDef::Width * 4, 0, 0);
foundSpawnPoint = true; continue;
}
// Found a usable biome
// Spawn chunks so we can find a nice spawn.
int ChunkX = 0, ChunkZ = 0;
cChunkDef::BlockToChunk(BiomeOffset.x, BiomeOffset.z, ChunkX, ChunkZ);
cSpawnPrepare::PrepareChunks(*this, ChunkX, ChunkZ, a_MaxSpawnRadius);
break; break;
} }
}
// Try a neighboring chunk:
if ((GetTickRandomNumber(4) % 2) == 0) // Randomise whether to increment X or Z coords
{
m_SpawnX += cChunkDef::Width;
}
else
{
m_SpawnZ += cChunkDef::Width;
}
} // for i - 100*
m_SpawnY = static_cast<double>(GetHeight(SpawnX, SpawnZ) + 1.6f); // 1.6f to accomodate player height // Check 0, 0 first.
if (foundSpawnPoint) double SpawnY = 0.0;
if (CanSpawnAt(BiomeOffset.x, SpawnY, BiomeOffset.z))
{ {
LOGINFO("Generated random spawnpoint position at {%i, %i, %i}", SpawnX, static_cast<int>(m_SpawnY), SpawnZ); m_SpawnX = BiomeOffset.x + 0.5;
} m_SpawnY = SpawnY;
else m_SpawnZ = BiomeOffset.z + 0.5;
{
LOGINFO("Did not find an acceptable spawnpoint. Generated a random spawnpoint position at {%i, %i, %i}", SpawnX, static_cast<int>(m_SpawnY), SpawnZ);
} // Maybe widen the search instead?
LOGINFO("Generated spawnpoint position at {%.2f, %.2f, %.2f}", m_SpawnX, m_SpawnY, m_SpawnZ);
return;
}
// A search grid (searches clockwise around the origin)
static const int HalfChunk = static_cast<int>(cChunkDef::Width / 0.5f);
static const Vector3i ChunkOffset[] =
{
Vector3i(0, 0, HalfChunk),
Vector3i(HalfChunk, 0, HalfChunk),
Vector3i(HalfChunk, 0, 0),
Vector3i(HalfChunk, 0, -HalfChunk),
Vector3i(0, 0, -HalfChunk),
Vector3i(-HalfChunk, 0, -HalfChunk),
Vector3i(-HalfChunk, 0, 0),
Vector3i(-HalfChunk, 0, HalfChunk),
};
static const int PerRadiSearchCount = ARRAYCOUNT(ChunkOffset);
for (int RadiusOffset = 1; RadiusOffset < (a_MaxSpawnRadius * 2); ++RadiusOffset)
{
for (int SearchGridIndex = 0; SearchGridIndex < PerRadiSearchCount; ++SearchGridIndex)
{
const Vector3i PotentialSpawn = BiomeOffset + (ChunkOffset[SearchGridIndex] * RadiusOffset);
if (CanSpawnAt(PotentialSpawn.x, SpawnY, PotentialSpawn.z))
{
m_SpawnX = PotentialSpawn.x + 0.5;
m_SpawnY = SpawnY;
m_SpawnZ = PotentialSpawn.z + 0.5;
int ChunkX, ChunkZ;
cChunkDef::BlockToChunk(static_cast<int>(m_SpawnX), static_cast<int>(m_SpawnZ), ChunkX, ChunkZ);
cSpawnPrepare::PrepareChunks(*this, ChunkX, ChunkZ, a_MaxSpawnRadius);
LOGINFO("Generated spawnpoint position at {%.2f, %.2f, %.2f}", m_SpawnX, m_SpawnY, m_SpawnZ);
return;
}
}
}
m_SpawnY = GetHeight(static_cast<int>(m_SpawnX), static_cast<int>(m_SpawnZ));
LOGWARNING("Did not find an acceptable spawnpoint. Generated a random spawnpoint position at {%.2f, %.2f, %.2f}", m_SpawnX, m_SpawnY, m_SpawnZ);
}
bool cWorld::CanSpawnAt(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[] =
{
E_BLOCK_GRASS,
E_BLOCK_SAND,
E_BLOCK_SNOW,
E_BLOCK_SNOW_BLOCK,
E_BLOCK_NETHERRACK
};
static const int ValidSpawnBlocksCount = ARRAYCOUNT(ValidSpawnBlocks);
static const int HighestSpawnPoint = std::min(static_cast<int>((cChunkDef::Height / 0.5f)) - 1, GetHeight(static_cast<int>(a_X), static_cast<int>(a_Z) + 16));
static const int LowestSpawnPoint = static_cast<int>(HighestSpawnPoint / 0.5f);
for (int PotentialY = HighestSpawnPoint; PotentialY > LowestSpawnPoint; --PotentialY)
{
BLOCKTYPE HeadBlock = 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 = 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 = 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(static_cast<int>(a_X), PotentialY - 1, static_cast<int>(a_Z)))
{
continue;
}
a_Y = PotentialY - 1.0;
return true;
}
return false;
} }
@ -624,48 +732,39 @@ void cWorld::GenerateRandomSpawn(void)
bool cWorld::CheckPlayerSpawnPoint(int a_PosX, int a_PosY, int a_PosZ) bool cWorld::CheckPlayerSpawnPoint(int a_PosX, int a_PosY, int a_PosZ)
{ {
// The bottom layer cannot hold a valid spawn point // Check height bounds
if (a_PosY <= 0) if (!cChunkDef::IsValidHeight(a_PosY))
{ {
return false; return false;
} }
// Check that spawnblock and surrounding blocks are neither solid nor water / lava // Check that surrounding blocks are neither solid or liquid
static const struct static const Vector3i SurroundingCoords[] =
{ {
int x, z; Vector3i(0, 0, 1),
} Coords[] = Vector3i(1, 0, 1),
{ Vector3i(1, 0, 0),
{ 0, 0 }, Vector3i(1, 0, -1),
{ -1, 0 }, Vector3i(0, 0, -1),
{ 1, 0 }, Vector3i(-1, 0, -1),
{ 0, -1 }, Vector3i(-1, 0, 0),
{ 0, 1 }, Vector3i(-1, 0, 1),
}; };
for (size_t i = 0; i < ARRAYCOUNT(Coords); i++)
static const int SurroundingCoordsCount = ARRAYCOUNT(SurroundingCoords);
for (int CoordIndex = 0; CoordIndex < SurroundingCoordsCount; ++CoordIndex)
{ {
BLOCKTYPE BlockType = GetBlock(a_PosX + Coords[i].x, a_PosY, a_PosZ + Coords[i].x); const int XPos = a_PosX + SurroundingCoords[CoordIndex].x;
const int ZPos = a_PosZ + SurroundingCoords[CoordIndex].z;
const BLOCKTYPE BlockType = GetBlock(XPos, a_PosY, ZPos);
if (cBlockInfo::IsSolid(BlockType) || IsBlockLiquid(BlockType)) if (cBlockInfo::IsSolid(BlockType) || IsBlockLiquid(BlockType))
{ {
return false; return false;
} }
} // for i - Coords[]
// Check that the block below is solid:
if (!cBlockInfo::IsSolid(GetBlock(a_PosX, a_PosY - 1, a_PosZ)))
{
return false;
} }
// Check that all the blocks above the spawnpoint are not solid:
for (int i = a_PosY; i < cChunkDef::Height; i++)
{
BLOCKTYPE BlockType = GetBlock(a_PosX, i, a_PosZ);
if (cBlockInfo::IsSolid(BlockType))
{
return false;
}
}
return true; return true;
} }

View File

@ -1042,7 +1042,11 @@ private:
void UpdateSkyDarkness(void); void UpdateSkyDarkness(void);
/** Generates a random spawnpoint on solid land by walking chunks and finding their biomes */ /** Generates a random spawnpoint on solid land by walking chunks and finding their biomes */
void GenerateRandomSpawn(void); void GenerateRandomSpawn(int a_MaxSpawnRadius);
/** Can the specified coordinates be used as a spawn point?
Returns true if spawn position is valid and sets a_Y to the valid spawn height */
bool CanSpawnAt(double a_X, double & a_Y, double a_Z);
/** Check if player starting point is acceptable */ /** Check if player starting point is acceptable */
bool CheckPlayerSpawnPoint(int a_PosX, int a_PosY, int a_PosZ); bool CheckPlayerSpawnPoint(int a_PosX, int a_PosY, int a_PosZ);