diff --git a/src/entity/Entity.php b/src/entity/Entity.php index e483b4348..1631667c5 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -635,7 +635,7 @@ abstract class Entity{ $this->checkBlockIntersections(); - if($this->location->y <= -16 and $this->isAlive()){ + if($this->location->y <= World::Y_MIN - 16 and $this->isAlive()){ $ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_VOID, 10); $this->attack($ev); $hasUpdate = true; diff --git a/src/network/mcpe/serializer/ChunkSerializer.php b/src/network/mcpe/serializer/ChunkSerializer.php index 6fe392b67..af5336465 100644 --- a/src/network/mcpe/serializer/ChunkSerializer.php +++ b/src/network/mcpe/serializer/ChunkSerializer.php @@ -46,8 +46,8 @@ final class ChunkSerializer{ * Chunks are sent in a stack, so every chunk below the top non-empty one must be sent. */ public static function getSubChunkCount(Chunk $chunk) : int{ - for($count = count($chunk->getSubChunks()); $count > 0; --$count){ - if($chunk->getSubChunk($count - 1)->isEmptyFast()){ + for($y = Chunk::MAX_SUBCHUNK_INDEX, $count = count($chunk->getSubChunks()); $y >= Chunk::MIN_SUBCHUNK_INDEX; --$y, --$count){ + if($chunk->getSubChunk($y)->isEmptyFast()){ continue; } return $count; @@ -59,7 +59,7 @@ final class ChunkSerializer{ public static function serializeFullChunk(Chunk $chunk, RuntimeBlockMapping $blockMapper, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{ $stream = PacketSerializer::encoder($encoderContext); $subChunkCount = self::getSubChunkCount($chunk); - for($y = 0; $y < $subChunkCount; ++$y){ + for($y = Chunk::MIN_SUBCHUNK_INDEX, $writtenCount = 0; $writtenCount < $subChunkCount; ++$y, ++$writtenCount){ self::serializeSubChunk($chunk->getSubChunk($y), $blockMapper, $stream, false); } $stream->put($chunk->getBiomeIdArray()); diff --git a/src/world/World.php b/src/world/World.php index a06f2ad5e..dcd27805c 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -331,7 +331,7 @@ class World implements ChunkManager{ private const BLOCKHASH_Y_OFFSET = self::BLOCKHASH_Y_PADDING - self::Y_MIN; private const BLOCKHASH_Y_MASK = (1 << self::BLOCKHASH_Y_BITS) - 1; private const BLOCKHASH_XZ_MASK = (1 << self::MORTON3D_BIT_SIZE) - 1; - private const BLOCKHASH_XZ_EXTRA_BITS = 6; + private const BLOCKHASH_XZ_EXTRA_BITS = (self::MORTON3D_BIT_SIZE - self::BLOCKHASH_Y_BITS) >> 1; private const BLOCKHASH_XZ_EXTRA_MASK = (1 << self::BLOCKHASH_XZ_EXTRA_BITS) - 1; private const BLOCKHASH_XZ_SIGN_SHIFT = 64 - self::MORTON3D_BIT_SIZE - self::BLOCKHASH_XZ_EXTRA_BITS; private const BLOCKHASH_X_SHIFT = self::BLOCKHASH_Y_BITS; diff --git a/src/world/format/Chunk.php b/src/world/format/Chunk.php index f1b1320ae..8bb5eed9f 100644 --- a/src/world/format/Chunk.php +++ b/src/world/format/Chunk.php @@ -35,7 +35,9 @@ class Chunk{ public const DIRTY_FLAG_BLOCKS = 1 << 0; public const DIRTY_FLAG_BIOMES = 1 << 3; - public const MAX_SUBCHUNKS = 16; + public const MIN_SUBCHUNK_INDEX = 0; + public const MAX_SUBCHUNK_INDEX = 15; + public const MAX_SUBCHUNKS = self::MAX_SUBCHUNK_INDEX - self::MIN_SUBCHUNK_INDEX + 1; public const EDGE_LENGTH = SubChunk::EDGE_LENGTH; public const COORD_BIT_SIZE = SubChunk::COORD_BIT_SIZE; @@ -71,10 +73,10 @@ class Chunk{ $this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS); foreach($this->subChunks as $y => $null){ - $this->subChunks[$y] = $subChunks[$y] ?? new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, []); + $this->subChunks[$y] = $subChunks[$y + self::MIN_SUBCHUNK_INDEX] ?? new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, []); } - $val = ($this->subChunks->getSize() * SubChunk::EDGE_LENGTH); + $val = (self::MAX_SUBCHUNK_INDEX + 1) * SubChunk::EDGE_LENGTH; $this->heightMap = HeightArray::fill($val); //TODO: what about lazily initializing this? $this->biomeIds = $biomeIds; @@ -118,7 +120,7 @@ class Chunk{ * @return int|null 0-255, or null if there are no blocks in the column */ public function getHighestBlockAt(int $x, int $z) : ?int{ - for($y = $this->subChunks->count() - 1; $y >= 0; --$y){ + for($y = self::MAX_SUBCHUNK_INDEX; $y >= self::MIN_SUBCHUNK_INDEX; --$y){ $height = $this->getSubChunk($y)->getHighestBlockAt($x, $z); if($height !== null){ return $height | ($y << SubChunk::COORD_BIT_SIZE); @@ -280,21 +282,21 @@ class Chunk{ } public function getSubChunk(int $y) : SubChunk{ - if($y < 0 || $y >= $this->subChunks->getSize()){ + if($y < self::MIN_SUBCHUNK_INDEX || $y > self::MAX_SUBCHUNK_INDEX){ throw new \InvalidArgumentException("Invalid subchunk Y coordinate $y"); } - return $this->subChunks[$y]; + return $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX]; } /** * Sets a subchunk in the chunk index */ public function setSubChunk(int $y, ?SubChunk $subChunk) : void{ - if($y < 0 or $y >= $this->subChunks->getSize()){ + if($y < self::MIN_SUBCHUNK_INDEX or $y > self::MAX_SUBCHUNK_INDEX){ throw new \InvalidArgumentException("Invalid subchunk Y coordinate $y"); } - $this->subChunks[$y] = $subChunk ?? new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, []); + $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX] = $subChunk ?? new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, []); $this->setTerrainDirtyFlag(self::DIRTY_FLAG_BLOCKS, true); } @@ -303,7 +305,11 @@ class Chunk{ * @phpstan-return array */ public function getSubChunks() : array{ - return $this->subChunks->toArray(); + $result = []; + foreach($this->subChunks as $yOffset => $subChunk){ + $result[$yOffset + self::MIN_SUBCHUNK_INDEX] = $subChunk; + } + return $result; } /** diff --git a/src/world/format/io/FastChunkSerializer.php b/src/world/format/io/FastChunkSerializer.php index bb8bec687..c8ab53a73 100644 --- a/src/world/format/io/FastChunkSerializer.php +++ b/src/world/format/io/FastChunkSerializer.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\world\format\io; +use pocketmine\utils\Binary; use pocketmine\utils\BinaryStream; use pocketmine\world\format\BiomeArray; use pocketmine\world\format\Chunk; @@ -96,7 +97,7 @@ final class FastChunkSerializer{ $count = $stream->getByte(); for($subCount = 0; $subCount < $count; ++$subCount){ - $y = $stream->getByte(); + $y = Binary::signByte($stream->getByte()); $airBlockId = $stream->getInt(); /** @var PalettedBlockArray[] $layers */ diff --git a/src/world/format/io/leveldb/LevelDB.php b/src/world/format/io/leveldb/LevelDB.php index a96e329b3..3fe2e359c 100644 --- a/src/world/format/io/leveldb/LevelDB.php +++ b/src/world/format/io/leveldb/LevelDB.php @@ -266,7 +266,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ case 3: //MCPE 1.0 $convertedLegacyExtraData = $this->deserializeLegacyExtraData($index, $chunkVersion); - for($y = 0; $y < Chunk::MAX_SUBCHUNKS; ++$y){ + for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){ if(($data = $this->db->get($index . self::TAG_SUBCHUNK_PREFIX . chr($y))) === false){ continue; } diff --git a/src/world/light/SkyLightUpdate.php b/src/world/light/SkyLightUpdate.php index 42d12be17..2c4214568 100644 --- a/src/world/light/SkyLightUpdate.php +++ b/src/world/light/SkyLightUpdate.php @@ -115,11 +115,10 @@ class SkyLightUpdate extends LightUpdate{ //have to avoid filling full light for any subchunk that contains a heightmap Y coordinate $highestHeightMapPlusOne = max($chunk->getHeightMapArray()) + 1; $lowestClearSubChunk = ($highestHeightMapPlusOne >> SubChunk::COORD_BIT_SIZE) + (($highestHeightMapPlusOne & SubChunk::COORD_MASK) !== 0 ? 1 : 0); - $chunkHeight = count($chunk->getSubChunks()); - for($y = 0; $y < $lowestClearSubChunk && $y < $chunkHeight; $y++){ + for($y = Chunk::MIN_SUBCHUNK_INDEX; $y < $lowestClearSubChunk && $y <= Chunk::MAX_SUBCHUNK_INDEX; $y++){ $chunk->getSubChunk($y)->setBlockSkyLightArray(LightArray::fill(0)); } - for($y = $lowestClearSubChunk, $yMax = $chunkHeight; $y < $yMax; $y++){ + for($y = $lowestClearSubChunk; $y <= Chunk::MAX_SUBCHUNK_INDEX; $y++){ $chunk->getSubChunk($y)->setBlockSkyLightArray(LightArray::fill(15)); } @@ -130,7 +129,7 @@ class SkyLightUpdate extends LightUpdate{ for($x = 0; $x < Chunk::EDGE_LENGTH; ++$x){ for($z = 0; $z < Chunk::EDGE_LENGTH; ++$z){ $currentHeight = $chunk->getHeightMap($x, $z); - $maxAdjacentHeight = 0; + $maxAdjacentHeight = World::Y_MIN; if($x !== 0){ $maxAdjacentHeight = max($maxAdjacentHeight, $chunk->getHeightMap($x - 1, $z)); } @@ -174,21 +173,21 @@ class SkyLightUpdate extends LightUpdate{ * @phpstan-param \SplFixedArray $directSkyLightBlockers */ private static function recalculateHeightMap(Chunk $chunk, \SplFixedArray $directSkyLightBlockers) : HeightArray{ - $maxSubChunkY = count($chunk->getSubChunks()) - 1; - for(; $maxSubChunkY >= 0; $maxSubChunkY--){ + $maxSubChunkY = Chunk::MAX_SUBCHUNK_INDEX; + for(; $maxSubChunkY >= Chunk::MIN_SUBCHUNK_INDEX; $maxSubChunkY--){ if(!$chunk->getSubChunk($maxSubChunkY)->isEmptyFast()){ break; } } $result = HeightArray::fill(World::Y_MIN); - if($maxSubChunkY === -1){ //whole column is definitely empty + if($maxSubChunkY < Chunk::MIN_SUBCHUNK_INDEX){ //whole column is definitely empty return $result; } for($z = 0; $z < Chunk::EDGE_LENGTH; ++$z){ for($x = 0; $x < Chunk::EDGE_LENGTH; ++$x){ $y = null; - for($subChunkY = $maxSubChunkY; $subChunkY >= 0; $subChunkY--){ + for($subChunkY = $maxSubChunkY; $subChunkY >= Chunk::MIN_SUBCHUNK_INDEX; $subChunkY--){ $subHighestBlockY = $chunk->getSubChunk($subChunkY)->getHighestBlockAt($x, $z); if($subHighestBlockY !== null){ $y = ($subChunkY * SubChunk::EDGE_LENGTH) + $subHighestBlockY; diff --git a/src/world/utils/SubChunkExplorer.php b/src/world/utils/SubChunkExplorer.php index 9d7faeb96..69bdb44e1 100644 --- a/src/world/utils/SubChunkExplorer.php +++ b/src/world/utils/SubChunkExplorer.php @@ -68,7 +68,7 @@ class SubChunkExplorer{ if($this->currentSubChunk === null or $this->currentY !== $newChunkY){ $this->currentY = $newChunkY; - if($this->currentY < 0 or $this->currentY >= $this->currentChunk->getHeight()){ + if($this->currentY < Chunk::MIN_SUBCHUNK_INDEX or $this->currentY > Chunk::MAX_SUBCHUNK_INDEX){ $this->currentSubChunk = null; return SubChunkExplorerStatus::INVALID; }