diff --git a/changelogs/4.0-snapshot.md b/changelogs/4.0-snapshot.md index ddc77f939..8155d3d90 100644 --- a/changelogs/4.0-snapshot.md +++ b/changelogs/4.0-snapshot.md @@ -867,6 +867,7 @@ This version features substantial changes to the network system, improving coher - `World->populateChunk()` has been split into `World->requestChunkPopulation()` and `World->orderChunkPopulation()`. - The following API methods have changed behaviour: - `World->getChunk()` no longer tries to load chunks from disk. If the chunk is not already in memory, null is returned. (This behaviour now properly matches other `ChunkManager` implementations.) + - `World->getHighestBlockAt()` now returns `null` instead of `-1` if the target X/Z column contains no blocks. - The following methods now throw `WorldException` when targeting ungenerated terrain: - `World->getSafeSpawn()` (previously it just silently returned the input position) - `World->getHighestBlockAt()` (previously it returned -1) diff --git a/src/item/ChorusFruit.php b/src/item/ChorusFruit.php index 8ba82d281..7cf621e67 100644 --- a/src/item/ChorusFruit.php +++ b/src/item/ChorusFruit.php @@ -49,7 +49,7 @@ class ChorusFruit extends Food{ $origin = $consumer->getPosition(); $minX = $origin->getFloorX() - 8; - $minY = min($origin->getFloorY(), $consumer->getWorld()->getWorldHeight()) - 8; + $minY = min($origin->getFloorY(), $consumer->getWorld()->getMaxY()) - 8; $minZ = $origin->getFloorZ() - 8; $maxX = $minX + 16; diff --git a/src/world/ChunkManager.php b/src/world/ChunkManager.php index e0bb90f50..e84b88dc0 100644 --- a/src/world/ChunkManager.php +++ b/src/world/ChunkManager.php @@ -45,9 +45,14 @@ interface ChunkManager{ public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk) : void; /** - * Returns the height of the world + * Returns the lowest buildable Y coordinate of the world */ - public function getWorldHeight() : int; + public function getMinY() : int; + + /** + * Returns the highest buildable Y coordinate of the world + */ + public function getMaxY() : int; /** * Returns whether the specified coordinates are within the valid world boundaries, taking world format limitations diff --git a/src/world/SimpleChunkManager.php b/src/world/SimpleChunkManager.php index 2c7fb01c6..d78e53338 100644 --- a/src/world/SimpleChunkManager.php +++ b/src/world/SimpleChunkManager.php @@ -35,13 +35,13 @@ class SimpleChunkManager implements ChunkManager{ protected $chunks = []; /** @var int */ - protected $worldHeight; + private $minY; + /** @var int */ + private $maxY; - /** - * SimpleChunkManager constructor. - */ - public function __construct(int $worldHeight = World::Y_MAX){ - $this->worldHeight = $worldHeight; + public function __construct(int $minY, int $maxY){ + $this->minY = $minY; + $this->maxY = $maxY; } public function getBlockAt(int $x, int $y, int $z) : Block{ @@ -71,14 +71,18 @@ class SimpleChunkManager implements ChunkManager{ $this->chunks = []; } - public function getWorldHeight() : int{ - return $this->worldHeight; + public function getMinY() : int{ + return $this->minY; + } + + public function getMaxY() : int{ + return $this->maxY; } public function isInWorld(int $x, int $y, int $z) : bool{ return ( $x <= Limits::INT32_MAX and $x >= Limits::INT32_MIN and - $y < $this->worldHeight and $y >= 0 and + $y < $this->maxY and $y >= $this->minY and $z <= Limits::INT32_MAX and $z >= Limits::INT32_MIN ); } diff --git a/src/world/World.php b/src/world/World.php index 2fadf68cb..123e41d31 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -174,7 +174,9 @@ class World implements ChunkManager{ private $providerGarbageCollectionTicker = 0; /** @var int */ - private $worldHeight; + private $minY; + /** @var int */ + private $maxY; /** @var ChunkLoader[] */ private $loaders = []; @@ -383,7 +385,8 @@ class World implements ChunkManager{ $this->displayName = $this->provider->getWorldData()->getName(); $this->logger = new \PrefixedLogger($server->getLogger(), "World: $this->displayName"); - $this->worldHeight = $this->provider->getWorldHeight(); + $this->minY = $this->provider->getWorldMinY(); + $this->maxY = $this->provider->getWorldMaxY(); $this->server->getLogger()->info($this->server->getLanguage()->translateString("pocketmine.level.preparing", [$this->displayName])); $this->generator = GeneratorManager::getInstance()->getGenerator($this->provider->getWorldData()->getGenerator(), true); @@ -1335,7 +1338,7 @@ class World implements ChunkManager{ public function isInWorld(int $x, int $y, int $z) : bool{ return ( $x <= Limits::INT32_MAX and $x >= Limits::INT32_MIN and - $y < $this->worldHeight and $y >= 0 and + $y < $this->maxY and $y >= $this->minY and $z <= Limits::INT32_MAX and $z >= Limits::INT32_MIN ); } @@ -2089,10 +2092,10 @@ class World implements ChunkManager{ /** * Gets the highest block Y value at a specific $x and $z * - * @return int 0-255, or -1 if the column is empty + * @return int|null 0-255, or null if the column is empty * @throws WorldException if the terrain is not generated */ - public function getHighestBlockAt(int $x, int $z) : int{ + public function getHighestBlockAt(int $x, int $z) : ?int{ if(($chunk = $this->loadChunk($x >> 4, $z >> 4)) !== null){ return $chunk->getHighestBlockAt($x & 0x0f, $z & 0x0f); } @@ -2481,7 +2484,7 @@ class World implements ChunkManager{ $spawn = $this->getSpawnLocation(); } - $max = $this->worldHeight; + $max = $this->maxY; $v = $spawn->floor(); $chunk = $this->getOrLoadChunkAtPosition($v); if($chunk === null){ @@ -2491,7 +2494,7 @@ class World implements ChunkManager{ $z = (int) $v->z; $y = (int) min($max - 2, $v->y); $wasAir = $this->getBlockAt($x, $y - 1, $z)->getId() === BlockLegacyIds::AIR; //TODO: bad hack, clean up - for(; $y > 0; --$y){ + for(; $y > $this->minY; --$y){ if($this->getBlockAt($x, $y, $z)->isFullCube()){ if($wasAir){ $y++; @@ -2502,7 +2505,7 @@ class World implements ChunkManager{ } } - for(; $y >= 0 and $y < $max; ++$y){ + for(; $y >= $this->minY and $y < $max; ++$y){ if(!$this->getBlockAt($x, $y + 1, $z)->isFullCube()){ if(!$this->getBlockAt($x, $y, $z)->isFullCube()){ return new Position($spawn->x, $y === (int) $spawn->y ? $spawn->y : $y, $spawn->z, $this); @@ -2575,8 +2578,12 @@ class World implements ChunkManager{ return $this->provider->getWorldData()->getSeed(); } - public function getWorldHeight() : int{ - return $this->worldHeight; + public function getMinY() : int{ + return $this->minY; + } + + public function getMaxY() : int{ + return $this->maxY; } public function getDifficulty() : int{ diff --git a/src/world/format/Chunk.php b/src/world/format/Chunk.php index 13570b665..a625b1727 100644 --- a/src/world/format/Chunk.php +++ b/src/world/format/Chunk.php @@ -131,17 +131,17 @@ class Chunk{ * @param int $x 0-15 * @param int $z 0-15 * - * @return int 0-255, or -1 if there are no blocks in the column + * @return int|null 0-255, or null if there are no blocks in the column */ - public function getHighestBlockAt(int $x, int $z) : int{ + public function getHighestBlockAt(int $x, int $z) : ?int{ for($y = $this->subChunks->count() - 1; $y >= 0; --$y){ - $height = $this->getSubChunk($y)->getHighestBlockAt($x, $z) | ($y << 4); - if($height !== -1){ - return $height; + $height = $this->getSubChunk($y)->getHighestBlockAt($x, $z); + if($height !== null){ + return $height | ($y << 4); } } - return -1; + return null; } /** diff --git a/src/world/format/SubChunk.php b/src/world/format/SubChunk.php index e784aa650..2224fe1d5 100644 --- a/src/world/format/SubChunk.php +++ b/src/world/format/SubChunk.php @@ -96,9 +96,9 @@ class SubChunk{ return $this->blockLayers; } - public function getHighestBlockAt(int $x, int $z) : int{ + public function getHighestBlockAt(int $x, int $z) : ?int{ if(count($this->blockLayers) === 0){ - return -1; + return null; } for($y = 15; $y >= 0; --$y){ if($this->blockLayers[0]->get($x, $y, $z) !== $this->emptyBlockId){ @@ -106,7 +106,7 @@ class SubChunk{ } } - return -1; //highest block not in this subchunk + return null; //highest block not in this subchunk } public function getBlockSkyLightArray() : LightArray{ diff --git a/src/world/format/io/WorldProvider.php b/src/world/format/io/WorldProvider.php index d2c858475..77f0fa199 100644 --- a/src/world/format/io/WorldProvider.php +++ b/src/world/format/io/WorldProvider.php @@ -36,10 +36,15 @@ interface WorldProvider{ */ public function __construct(string $path); + /** + * Returns the lowest buildable Y coordinate of this world + */ + public function getWorldMinY() : int; + /** * Gets the build height limit of this world */ - public function getWorldHeight() : int; + public function getWorldMaxY() : int; public function getPath() : string; diff --git a/src/world/format/io/leveldb/LevelDB.php b/src/world/format/io/leveldb/LevelDB.php index d428d8b4e..a84391543 100644 --- a/src/world/format/io/leveldb/LevelDB.php +++ b/src/world/format/io/leveldb/LevelDB.php @@ -132,7 +132,11 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ return new BedrockWorldData($this->getPath() . DIRECTORY_SEPARATOR . "level.dat"); } - public function getWorldHeight() : int{ + public function getWorldMinY() : int{ + return 0; + } + + public function getWorldMaxY() : int{ return 256; } diff --git a/src/world/format/io/region/Anvil.php b/src/world/format/io/region/Anvil.php index 88951c92b..dc2c039d4 100644 --- a/src/world/format/io/region/Anvil.php +++ b/src/world/format/io/region/Anvil.php @@ -51,7 +51,11 @@ class Anvil extends RegionWorldProvider{ return 19133; } - public function getWorldHeight() : int{ + public function getWorldMinY() : int{ + return 0; + } + + public function getWorldMaxY() : int{ //TODO: add world height options return 256; } diff --git a/src/world/format/io/region/McRegion.php b/src/world/format/io/region/McRegion.php index 3e5633e0b..1a80f90b6 100644 --- a/src/world/format/io/region/McRegion.php +++ b/src/world/format/io/region/McRegion.php @@ -106,7 +106,11 @@ class McRegion extends RegionWorldProvider{ return 19132; } - public function getWorldHeight() : int{ + public function getWorldMinY() : int{ + return 0; + } + + public function getWorldMaxY() : int{ //TODO: add world height options return 128; } diff --git a/src/world/format/io/region/PMAnvil.php b/src/world/format/io/region/PMAnvil.php index 960dd5369..58de81d12 100644 --- a/src/world/format/io/region/PMAnvil.php +++ b/src/world/format/io/region/PMAnvil.php @@ -50,7 +50,11 @@ class PMAnvil extends RegionWorldProvider{ return -1; //Not a PC format, only PocketMine-MP } - public function getWorldHeight() : int{ + public function getWorldMinY() : int{ + return 0; + } + + public function getWorldMaxY() : int{ return 256; } } diff --git a/src/world/generator/GeneratorRegisterTask.php b/src/world/generator/GeneratorRegisterTask.php index 79957df50..5189adf54 100644 --- a/src/world/generator/GeneratorRegisterTask.php +++ b/src/world/generator/GeneratorRegisterTask.php @@ -42,7 +42,9 @@ class GeneratorRegisterTask extends AsyncTask{ /** @var int */ public $worldId; /** @var int */ - public $worldHeight = World::Y_MAX; + public $worldMinY; + /** @var int */ + public $worldMaxY; /** * @param mixed[] $generatorSettings @@ -54,7 +56,8 @@ class GeneratorRegisterTask extends AsyncTask{ $this->settings = igbinary_serialize($generatorSettings); $this->seed = $world->getSeed(); $this->worldId = $world->getId(); - $this->worldHeight = $world->getWorldHeight(); + $this->worldMinY = $world->getMinY(); + $this->worldMaxY = $world->getMaxY(); } public function onRun() : void{ @@ -63,6 +66,6 @@ class GeneratorRegisterTask extends AsyncTask{ * @see Generator::__construct() */ $generator = new $this->generatorClass($this->seed, igbinary_unserialize($this->settings)); - ThreadLocalGeneratorContext::register(new ThreadLocalGeneratorContext($generator, $this->worldHeight), $this->worldId); + ThreadLocalGeneratorContext::register(new ThreadLocalGeneratorContext($generator, $this->worldMinY, $this->worldMaxY), $this->worldId); } } diff --git a/src/world/generator/PopulationTask.php b/src/world/generator/PopulationTask.php index 9bcfc85dd..13c0126d2 100644 --- a/src/world/generator/PopulationTask.php +++ b/src/world/generator/PopulationTask.php @@ -83,7 +83,7 @@ class PopulationTask extends AsyncTask{ throw new AssumptionFailedError("Generator context should have been initialized before any PopulationTask execution"); } $generator = $context->getGenerator(); - $manager = new SimpleChunkManager($context->getWorldHeight()); + $manager = new SimpleChunkManager($context->getWorldMinY(), $context->getWorldMaxY()); /** @var Chunk[] $chunks */ $chunks = []; diff --git a/src/world/generator/ThreadLocalGeneratorContext.php b/src/world/generator/ThreadLocalGeneratorContext.php index ebdd8ee27..56a9b78f6 100644 --- a/src/world/generator/ThreadLocalGeneratorContext.php +++ b/src/world/generator/ThreadLocalGeneratorContext.php @@ -47,15 +47,21 @@ final class ThreadLocalGeneratorContext{ /** @var Generator */ private $generator; - /** @var int */ - private $worldHeight; - public function __construct(Generator $generator, int $worldHeight){ + /** @var int */ + private $worldMinY; + /** @var int */ + private $worldMaxY; + + public function __construct(Generator $generator, int $worldMinY, int $worldMaxY){ $this->generator = $generator; - $this->worldHeight = $worldHeight; + $this->worldMinY = $worldMinY; + $this->worldMaxY = $worldMaxY; } public function getGenerator() : Generator{ return $this->generator; } - public function getWorldHeight() : int{ return $this->worldHeight; } + public function getWorldMinY() : int{ return $this->worldMinY; } + + public function getWorldMaxY() : int{ return $this->worldMaxY; } } diff --git a/src/world/light/LightPopulationTask.php b/src/world/light/LightPopulationTask.php index f69ac46a9..70e55ad79 100644 --- a/src/world/light/LightPopulationTask.php +++ b/src/world/light/LightPopulationTask.php @@ -62,7 +62,7 @@ class LightPopulationTask extends AsyncTask{ public function onRun() : void{ $chunk = FastChunkSerializer::deserialize($this->chunk); - $manager = new SimpleChunkManager(); + $manager = new SimpleChunkManager(World::Y_MIN, World::Y_MAX); $manager->setChunk($this->chunkX, $this->chunkZ, $chunk); $blockFactory = BlockFactory::getInstance(); diff --git a/src/world/light/SkyLightUpdate.php b/src/world/light/SkyLightUpdate.php index 7ca2c1274..e86cf95bb 100644 --- a/src/world/light/SkyLightUpdate.php +++ b/src/world/light/SkyLightUpdate.php @@ -178,7 +178,7 @@ class SkyLightUpdate extends LightUpdate{ break; } } - $result = HeightArray::fill(0); + $result = HeightArray::fill(World::Y_MIN); if($maxSubChunkY === -1){ //whole column is definitely empty return $result; } @@ -188,16 +188,16 @@ class SkyLightUpdate extends LightUpdate{ $y = null; for($subChunkY = $maxSubChunkY; $subChunkY >= 0; $subChunkY--){ $subHighestBlockY = $chunk->getSubChunk($subChunkY)->getHighestBlockAt($x, $z); - if($subHighestBlockY !== -1){ + if($subHighestBlockY !== null){ $y = ($subChunkY * 16) + $subHighestBlockY; break; } } if($y === null){ //no blocks in the column - $result->set($x, $z, 0); + $result->set($x, $z, World::Y_MIN); }else{ - for(; $y >= 0; --$y){ + for(; $y >= World::Y_MIN; --$y){ if($directSkyLightBlockers[$chunk->getFullBlock($x, $y, $z)]){ $result->set($x, $z, $y + 1); break; @@ -221,7 +221,10 @@ class SkyLightUpdate extends LightUpdate{ */ private static function recalculateHeightMapColumn(Chunk $chunk, int $x, int $z, \SplFixedArray $directSkyLightBlockers) : int{ $y = $chunk->getHighestBlockAt($x, $z); - for(; $y >= 0; --$y){ + if($y === null){ + return World::Y_MIN; + } + for(; $y >= World::Y_MIN; --$y){ if($directSkyLightBlockers[$chunk->getFullBlock($x, $y, $z)]){ break; }