diff --git a/src/pocketmine/level/ChunkManager.php b/src/pocketmine/level/ChunkManager.php index 5e10da7e8..f6281a7e1 100644 --- a/src/pocketmine/level/ChunkManager.php +++ b/src/pocketmine/level/ChunkManager.php @@ -131,4 +131,22 @@ interface ChunkManager{ * @return int */ public function getSeed(); + + /** + * Returns the height of the world + * @return int + */ + public function getWorldHeight() : int; + + /** + * Returns whether the specified coordinates are within the valid world boundaries, taking world format limitations + * into account. + * + * @param float $x + * @param float $y + * @param float $z + * + * @return bool + */ + public function isInWorld(float $x, float $y, float $z) : bool; } \ No newline at end of file diff --git a/src/pocketmine/level/Explosion.php b/src/pocketmine/level/Explosion.php index 46b818163..a55fe2b9a 100644 --- a/src/pocketmine/level/Explosion.php +++ b/src/pocketmine/level/Explosion.php @@ -89,7 +89,7 @@ class Explosion{ $vBlock->x = $pointerX >= $x ? $x : $x - 1; $vBlock->y = $pointerY >= $y ? $y : $y - 1; $vBlock->z = $pointerZ >= $z ? $z : $z - 1; - if($vBlock->y < 0 or $vBlock->y >= Level::Y_MAX){ + if(!$this->level->isInWorld($vBlock->x, $vBlock->y, $vBlock->z)){ break; } $block = $this->level->getBlock($vBlock); diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 8748fec6c..8c248b3a4 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -269,6 +269,9 @@ class Level implements ChunkManager, Metadatable{ } public static function blockHash(int $x, int $y, int $z){ + if($y < 0 or $y >= Level::Y_MAX){ + throw new \InvalidArgumentException("Y coordinate $y is out of range!"); + } return (($x & 0xFFFFFFF) << 36) | (($y & Level::Y_MASK) << 28) | ($z & 0xFFFFFFF); } @@ -1078,7 +1081,10 @@ class Level implements ChunkManager, Metadatable{ * @param int $delay */ public function scheduleDelayedBlockUpdate(Vector3 $pos, int $delay){ - if(isset($this->scheduledBlockUpdateQueueIndex[$index = Level::blockHash($pos->x, $pos->y, $pos->z)]) and $this->scheduledBlockUpdateQueueIndex[$index] <= $delay){ + if( + !$this->isInWorld($pos->x, $pos->y, $pos->z) or + (isset($this->scheduledBlockUpdateQueueIndex[$index = Level::blockHash($pos->x, $pos->y, $pos->z)]) and $this->scheduledBlockUpdateQueueIndex[$index] <= $delay) + ){ return; } $this->scheduledBlockUpdateQueueIndex[$index] = $delay; @@ -1094,12 +1100,12 @@ class Level implements ChunkManager, Metadatable{ public function scheduleNeighbourBlockUpdates(Vector3 $pos){ $pos = $pos->floor(); - $this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x + 1, $pos->y, $pos->z)); - $this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x - 1, $pos->y, $pos->z)); - $this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y + 1, $pos->z)); - $this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y - 1, $pos->z)); - $this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y, $pos->z + 1)); - $this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y, $pos->z - 1)); + for($i = 0; $i <= 5; ++$i){ + $side = $pos->getSide($i); + if($this->isInWorld($side->x, $side->y, $side->z)){ + $this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($side->x, $side->y, $side->z)); + } + } } /** @@ -1298,23 +1304,39 @@ class Level implements ChunkManager, Metadatable{ return $this->getChunk($x >> 4, $z >> 4, false)->getFullBlock($x & 0x0f, $y, $z & 0x0f); } + public function isInWorld(float $x, float $y, float $z) : bool{ + return ( + $x <= INT32_MAX and $x >= INT32_MIN and + $y < $this->getWorldHeight() and $y >= 0 and + $z <= INT32_MAX and $z >= INT32_MIN + ); + } + /** - * Gets the Block object on the Vector3 location + * Gets the Block object at the Vector3 location + * + * Note for plugin developers: If you are using this method a lot (thousands of times for many positions for + * example), you may want to set addToCache to false to avoid using excessive amounts of memory. * * @param Vector3 $pos - * @param bool $cached + * @param bool $cached Whether to use the block cache for getting the block (faster, but may be inaccurate) + * @param bool $addToCache Whether to cache the block object created by this method call. * * @return Block */ - public function getBlock(Vector3 $pos, $cached = true) : Block{ + public function getBlock(Vector3 $pos, bool $cached = true, bool $addToCache = true) : Block{ $pos = $pos->floor(); - $index = Level::blockHash($pos->x, $pos->y, $pos->z); - if($cached and isset($this->blockCache[$index])){ - return $this->blockCache[$index]; - }elseif($pos->y >= 0 and $pos->y < $this->provider->getWorldHeight() and isset($this->chunks[$chunkIndex = Level::chunkHash($pos->x >> 4, $pos->z >> 4)])){ - $fullState = $this->chunks[$chunkIndex]->getFullBlock($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f); - }else{ - $fullState = 0; + + $fullState = 0; + $index = null; + + if($this->isInWorld($pos->x, $pos->y, $pos->z)){ + $index = Level::blockHash($pos->x, $pos->y, $pos->z); + if($cached and isset($this->blockCache[$index])){ + return $this->blockCache[$index]; + }elseif(isset($this->chunks[$chunkIndex = Level::chunkHash($pos->x >> 4, $pos->z >> 4)])){ + $fullState = $this->chunks[$chunkIndex]->getFullBlock($pos->x & 0x0f, $pos->y, $pos->z & 0x0f); + } } $block = clone $this->blockStates[$fullState & 0xfff]; @@ -1324,7 +1346,11 @@ class Level implements ChunkManager, Metadatable{ $block->z = $pos->z; $block->level = $this; - return $this->blockCache[$index] = $block; + if($addToCache and $index !== null){ + $this->blockCache[$index] = $block; + } + + return $block; } public function updateAllLight(Vector3 $pos){ @@ -1446,13 +1472,13 @@ class Level implements ChunkManager, Metadatable{ */ public function setBlock(Vector3 $pos, Block $block, bool $direct = false, bool $update = true) : bool{ $pos = $pos->floor(); - if($pos->y < 0 or $pos->y >= $this->provider->getWorldHeight()){ + if(!$this->isInWorld($pos->x, $pos->y, $pos->z)){ return false; } $this->timings->setBlock->startTiming(); - if($this->getChunk($pos->x >> 4, $pos->z >> 4, true)->setBlock($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f, $block->getId(), $block->getDamage())){ + if($this->getChunk($pos->x >> 4, $pos->z >> 4, true)->setBlock($pos->x & 0x0f, $pos->y, $pos->z & 0x0f, $block->getId(), $block->getDamage())){ if(!($pos instanceof Position)){ $pos = $this->temporalPosition->setComponents($pos->x, $pos->y, $pos->z); } @@ -1942,7 +1968,7 @@ class Level implements ChunkManager, Metadatable{ $chunk = $this->getChunk($pos->x >> 4, $pos->z >> 4, false); if($chunk !== null){ - return $chunk->getTile($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f); + return $chunk->getTile($pos->x & 0x0f, $pos->y, $pos->z & 0x0f); } return null; @@ -1982,7 +2008,7 @@ class Level implements ChunkManager, Metadatable{ * @return int 0-255 */ public function getBlockIdAt(int $x, int $y, int $z) : int{ - return $this->getChunk($x >> 4, $z >> 4, true)->getBlockId($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); + return $this->getChunk($x >> 4, $z >> 4, true)->getBlockId($x & 0x0f, $y, $z & 0x0f); } /** @@ -1995,7 +2021,7 @@ class Level implements ChunkManager, Metadatable{ */ public function setBlockIdAt(int $x, int $y, int $z, int $id){ unset($this->blockCache[Level::blockHash($x, $y, $z)]); - $this->getChunk($x >> 4, $z >> 4, true)->setBlockId($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $id & 0xff); + $this->getChunk($x >> 4, $z >> 4, true)->setBlockId($x & 0x0f, $y, $z & 0x0f, $id & 0xff); if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){ $this->changedBlocks[$index] = []; @@ -2016,7 +2042,7 @@ class Level implements ChunkManager, Metadatable{ * @return int 16-bit */ public function getBlockExtraDataAt(int $x, int $y, int $z) : int{ - return $this->getChunk($x >> 4, $z >> 4, true)->getBlockExtraData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); + return $this->getChunk($x >> 4, $z >> 4, true)->getBlockExtraData($x & 0x0f, $y, $z & 0x0f); } /** @@ -2029,7 +2055,7 @@ class Level implements ChunkManager, Metadatable{ * @param int $data */ public function setBlockExtraDataAt(int $x, int $y, int $z, int $id, int $data){ - $this->getChunk($x >> 4, $z >> 4, true)->setBlockExtraData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, ($data << 8) | $id); + $this->getChunk($x >> 4, $z >> 4, true)->setBlockExtraData($x & 0x0f, $y, $z & 0x0f, ($data << 8) | $id); $this->sendBlockExtraData($x, $y, $z, $id, $data); } @@ -2044,7 +2070,7 @@ class Level implements ChunkManager, Metadatable{ * @return int 0-15 */ public function getBlockDataAt(int $x, int $y, int $z) : int{ - return $this->getChunk($x >> 4, $z >> 4, true)->getBlockData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); + return $this->getChunk($x >> 4, $z >> 4, true)->getBlockData($x & 0x0f, $y, $z & 0x0f); } /** @@ -2057,7 +2083,7 @@ class Level implements ChunkManager, Metadatable{ */ public function setBlockDataAt(int $x, int $y, int $z, int $data){ unset($this->blockCache[Level::blockHash($x, $y, $z)]); - $this->getChunk($x >> 4, $z >> 4, true)->setBlockData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $data & 0x0f); + $this->getChunk($x >> 4, $z >> 4, true)->setBlockData($x & 0x0f, $y, $z & 0x0f, $data & 0x0f); if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){ $this->changedBlocks[$index] = []; @@ -2078,7 +2104,7 @@ class Level implements ChunkManager, Metadatable{ * @return int 0-15 */ public function getBlockSkyLightAt(int $x, int $y, int $z) : int{ - return $this->getChunk($x >> 4, $z >> 4, true)->getBlockSkyLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); + return $this->getChunk($x >> 4, $z >> 4, true)->getBlockSkyLight($x & 0x0f, $y, $z & 0x0f); } /** @@ -2090,7 +2116,7 @@ class Level implements ChunkManager, Metadatable{ * @param int $level 0-15 */ public function setBlockSkyLightAt(int $x, int $y, int $z, int $level){ - $this->getChunk($x >> 4, $z >> 4, true)->setBlockSkyLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $level & 0x0f); + $this->getChunk($x >> 4, $z >> 4, true)->setBlockSkyLight($x & 0x0f, $y, $z & 0x0f, $level & 0x0f); } /** @@ -2103,7 +2129,7 @@ class Level implements ChunkManager, Metadatable{ * @return int 0-15 */ public function getBlockLightAt(int $x, int $y, int $z) : int{ - return $this->getChunk($x >> 4, $z >> 4, true)->getBlockLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); + return $this->getChunk($x >> 4, $z >> 4, true)->getBlockLight($x & 0x0f, $y, $z & 0x0f); } /** @@ -2115,7 +2141,7 @@ class Level implements ChunkManager, Metadatable{ * @param int $level 0-15 */ public function setBlockLightAt(int $x, int $y, int $z, int $level){ - $this->getChunk($x >> 4, $z >> 4, true)->setBlockLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $level & 0x0f); + $this->getChunk($x >> 4, $z >> 4, true)->setBlockLight($x & 0x0f, $y, $z & 0x0f, $level & 0x0f); } /** @@ -2763,6 +2789,10 @@ class Level implements ChunkManager, Metadatable{ $this->provider->setSeed($seed); } + public function getWorldHeight() : int{ + return $this->provider->getWorldHeight(); + } + public function populateChunk(int $x, int $z, bool $force = false) : bool{ if(isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)]) or (count($this->chunkPopulationQueue) >= $this->chunkPopulationQueueSize and !$force)){ diff --git a/src/pocketmine/level/SimpleChunkManager.php b/src/pocketmine/level/SimpleChunkManager.php index da5d0b3b3..c22cdcccc 100644 --- a/src/pocketmine/level/SimpleChunkManager.php +++ b/src/pocketmine/level/SimpleChunkManager.php @@ -31,9 +31,11 @@ class SimpleChunkManager implements ChunkManager{ protected $chunks = []; protected $seed; + protected $worldHeight; - public function __construct($seed){ + public function __construct($seed, int $worldHeight = Level::Y_MAX){ $this->seed = $seed; + $this->worldHeight = $worldHeight; } /** @@ -159,4 +161,16 @@ class SimpleChunkManager implements ChunkManager{ public function getSeed(){ return $this->seed; } + + public function getWorldHeight() : int{ + return $this->worldHeight; + } + + public function isInWorld(float $x, float $y, float $z) : bool{ + return ( + $x <= INT32_MAX and $x >= INT32_MIN and + $y < $this->worldHeight and $y >= 0 and + $z <= INT32_MAX and $z >= INT32_MIN + ); + } } \ No newline at end of file diff --git a/src/pocketmine/level/generator/GeneratorRegisterTask.php b/src/pocketmine/level/generator/GeneratorRegisterTask.php index 0e71a9d9d..dadd70d0a 100644 --- a/src/pocketmine/level/generator/GeneratorRegisterTask.php +++ b/src/pocketmine/level/generator/GeneratorRegisterTask.php @@ -36,18 +36,20 @@ class GeneratorRegisterTask extends AsyncTask{ public $settings; public $seed; public $levelId; + public $worldHeight = Level::Y_MAX; public function __construct(Level $level, Generator $generator){ $this->generator = get_class($generator); $this->settings = serialize($generator->getSettings()); $this->seed = $level->getSeed(); $this->levelId = $level->getId(); + $this->worldHeight = $level->getWorldHeight(); } public function onRun(){ Block::init(); Biome::init(); - $manager = new SimpleChunkManager($this->seed); + $manager = new SimpleChunkManager($this->seed, $this->worldHeight); $this->saveToThreadStore("generation.level{$this->levelId}.manager", $manager); /** @var Generator $generator */ $generator = $this->generator; diff --git a/src/pocketmine/level/light/LightUpdate.php b/src/pocketmine/level/light/LightUpdate.php index f8a15cb42..37fbfd168 100644 --- a/src/pocketmine/level/light/LightUpdate.php +++ b/src/pocketmine/level/light/LightUpdate.php @@ -62,9 +62,14 @@ abstract class LightUpdate{ abstract protected function setLight(int $x, int $y, int $z, int $level); public function setAndUpdateLight(int $x, int $y, int $z, int $newLevel){ + if(!$this->level->isInWorld($x, $y, $z)){ + throw new \InvalidArgumentException("Coordinates x=$x, y=$y, z=$z are out of range"); + } + if(isset($this->spreadVisited[$index = Level::blockHash($x, $y, $z)]) or isset($this->removalVisited[$index])){ throw new \InvalidArgumentException("Already have a visit ready for this block"); } + $oldLevel = $this->getLight($x, $y, $z); if($oldLevel !== $newLevel){ @@ -93,7 +98,7 @@ abstract class LightUpdate{ ]; foreach($points as list($cx, $cy, $cz)){ - if($cy < 0){ + if(!$this->level->isInWorld($cx, $cy, $cz)){ continue; } $this->computeRemoveLight($cx, $cy, $cz, $oldAdjacentLight); @@ -118,7 +123,7 @@ abstract class LightUpdate{ ]; foreach($points as list($cx, $cy, $cz)){ - if($cy < 0){ + if(!$this->level->isInWorld($cx, $cy, $cz)){ continue; } $this->computeSpreadLight($cx, $cy, $cz, $newAdjacentLight);