From 7abfc465674cdfa37c6718e791da8eb12d0d56b9 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 17 Jan 2023 21:41:30 +0000 Subject: [PATCH] First look at 3D biome support --- .../mcpe/serializer/ChunkSerializer.php | 70 +++---- src/world/World.php | 12 +- src/world/format/BiomeArray.php | 72 ------- src/world/format/Chunk.php | 24 +-- src/world/format/SubChunk.php | 5 + src/world/format/io/ChunkUtils.php | 22 +++ src/world/format/io/FastChunkSerializer.php | 51 ++--- src/world/format/io/leveldb/LevelDB.php | 181 +++++++++++++++--- src/world/format/io/region/Anvil.php | 5 +- .../io/region/LegacyAnvilChunkTrait.php | 43 +++-- src/world/format/io/region/McRegion.php | 38 ++-- src/world/format/io/region/PMAnvil.php | 5 +- src/world/generator/Flat.php | 3 +- src/world/generator/PopulationTask.php | 4 +- src/world/generator/hell/Nether.php | 7 +- src/world/generator/normal/Normal.php | 6 +- src/world/generator/populator/GroundCover.php | 2 +- tests/phpunit/world/format/ChunkTest.php | 9 +- tests/phpunit/world/format/SubChunkTest.php | 3 +- 19 files changed, 319 insertions(+), 243 deletions(-) delete mode 100644 src/world/format/BiomeArray.php diff --git a/src/network/mcpe/serializer/ChunkSerializer.php b/src/network/mcpe/serializer/ChunkSerializer.php index e628652df..772587855 100644 --- a/src/network/mcpe/serializer/ChunkSerializer.php +++ b/src/network/mcpe/serializer/ChunkSerializer.php @@ -36,9 +36,7 @@ use pocketmine\utils\BinaryStream; use pocketmine\world\format\Chunk; use pocketmine\world\format\PalettedBlockArray; use pocketmine\world\format\SubChunk; -use function chr; use function count; -use function str_repeat; final class ChunkSerializer{ private function __construct(){ @@ -64,13 +62,16 @@ final class ChunkSerializer{ $stream = PacketSerializer::encoder($encoderContext); $subChunkCount = self::getSubChunkCount($chunk); - for($y = Chunk::MIN_SUBCHUNK_INDEX, $writtenCount = 0; $writtenCount < $subChunkCount; ++$y, ++$writtenCount){ + $writtenCount = 0; + for($y = Chunk::MIN_SUBCHUNK_INDEX; $writtenCount < $subChunkCount; ++$y, ++$writtenCount){ self::serializeSubChunk($chunk->getSubChunk($y), $blockMapper, $stream, false); } - //TODO: right now we don't support 3D natively, so we just 3Dify our 2D biomes so they fill the column - $encodedBiomePalette = self::serializeBiomesAsPalette($chunk); - $stream->put(str_repeat($encodedBiomePalette, 24)); + $biomeIdMap = LegacyBiomeIdToStringIdMap::getInstance(); + //all biomes must always be written :( + for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){ + self::serializeBiomePalette($chunk->getSubChunk($y)->getBiomeArray(), $biomeIdMap, $stream); + } $stream->putByte(0); //border block array count //Border block entry format: 1 byte (4 bits X, 4 bits Z). These are however useless since they crash the regular client. @@ -123,6 +124,28 @@ final class ChunkSerializer{ } } + private static function serializeBiomePalette(PalettedBlockArray $biomePalette, LegacyBiomeIdToStringIdMap $biomeIdMap, PacketSerializer $stream) : void{ + $biomePaletteBitsPerBlock = $biomePalette->getBitsPerBlock(); + $stream->putByte(($biomePaletteBitsPerBlock << 1) | 1); //the last bit is non-persistence (like for blocks), though it has no effect on biomes since they always use integer IDs + $stream->put($biomePalette->getWordArray()); + + //these LSHIFT by 1 uvarints are optimizations: the client expects zigzag varints here + //but since we know they are always unsigned, we can avoid the extra fcall overhead of + //zigzag and just shift directly. + $biomePaletteArray = $biomePalette->getPalette(); + if($biomePaletteBitsPerBlock !== 0){ + $stream->putUnsignedVarInt(count($biomePaletteArray) << 1); + } + + foreach($biomePaletteArray as $p){ + if($biomeIdMap->legacyToString($p) === null){ + //make sure we aren't sending bogus biomes - the 1.18.0 client crashes if we do this + $p = BiomeIds::OCEAN; + } + $stream->put(Binary::writeUnsignedVarInt($p << 1)); + } + } + public static function serializeTiles(Chunk $chunk) : string{ $stream = new BinaryStream(); foreach($chunk->getTiles() as $tile){ @@ -133,39 +156,4 @@ final class ChunkSerializer{ return $stream->getBuffer(); } - - private static function serializeBiomesAsPalette(Chunk $chunk) : string{ - $biomeIdMap = LegacyBiomeIdToStringIdMap::getInstance(); - $biomePalette = new PalettedBlockArray($chunk->getBiomeId(0, 0)); - for($x = 0; $x < 16; ++$x){ - for($z = 0; $z < 16; ++$z){ - $biomeId = $chunk->getBiomeId($x, $z); - if($biomeIdMap->legacyToString($biomeId) === null){ - //make sure we aren't sending bogus biomes - the 1.18.0 client crashes if we do this - $biomeId = BiomeIds::OCEAN; - } - for($y = 0; $y < 16; ++$y){ - $biomePalette->set($x, $y, $z, $biomeId); - } - } - } - - $biomePaletteBitsPerBlock = $biomePalette->getBitsPerBlock(); - $encodedBiomePalette = - chr(($biomePaletteBitsPerBlock << 1) | 1) . //the last bit is non-persistence (like for blocks), though it has no effect on biomes since they always use integer IDs - $biomePalette->getWordArray(); - - //these LSHIFT by 1 uvarints are optimizations: the client expects zigzag varints here - //but since we know they are always unsigned, we can avoid the extra fcall overhead of - //zigzag and just shift directly. - $biomePaletteArray = $biomePalette->getPalette(); - if($biomePaletteBitsPerBlock !== 0){ - $encodedBiomePalette .= Binary::writeUnsignedVarInt(count($biomePaletteArray) << 1); - } - foreach($biomePaletteArray as $p){ - $encodedBiomePalette .= Binary::writeUnsignedVarInt($p << 1); - } - - return $encodedBiomePalette; - } } diff --git a/src/world/World.php b/src/world/World.php index d8f555ea3..2d7f18d4e 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2241,23 +2241,23 @@ class World implements ChunkManager{ return ($chunk = $this->loadChunk($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE)) !== null ? $chunk->getTile($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK) : null; } - public function getBiomeId(int $x, int $z) : int{ + public function getBiomeId(int $x, int $y, int $z) : int{ if(($chunk = $this->loadChunk($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE)) !== null){ - return $chunk->getBiomeId($x & Chunk::COORD_MASK, $z & Chunk::COORD_MASK); + return $chunk->getBiomeId($x & Chunk::COORD_MASK, $y & Chunk::COORD_MASK, $z & Chunk::COORD_MASK); } return BiomeIds::OCEAN; //TODO: this should probably throw instead (terrain not generated yet) } - public function getBiome(int $x, int $z) : Biome{ - return BiomeRegistry::getInstance()->getBiome($this->getBiomeId($x, $z)); + public function getBiome(int $x, int $y, int $z) : Biome{ + return BiomeRegistry::getInstance()->getBiome($this->getBiomeId($x, $y, $z)); } - public function setBiomeId(int $x, int $z, int $biomeId) : void{ + public function setBiomeId(int $x, int $y, int $z, int $biomeId) : void{ $chunkX = $x >> Chunk::COORD_BIT_SIZE; $chunkZ = $z >> Chunk::COORD_BIT_SIZE; $this->unlockChunk($chunkX, $chunkZ, null); if(($chunk = $this->loadChunk($chunkX, $chunkZ)) !== null){ - $chunk->setBiomeId($x & Chunk::COORD_MASK, $z & Chunk::COORD_MASK, $biomeId); + $chunk->setBiomeId($x & Chunk::COORD_MASK, $y & Chunk::COORD_MASK, $z & Chunk::COORD_MASK, $biomeId); }else{ //if we allowed this, the modifications would be lost when the chunk is created throw new WorldException("Cannot set biome in a non-generated chunk"); diff --git a/src/world/format/BiomeArray.php b/src/world/format/BiomeArray.php deleted file mode 100644 index 5c7a3131f..000000000 --- a/src/world/format/BiomeArray.php +++ /dev/null @@ -1,72 +0,0 @@ -payload = $payload; - } - - public static function fill(int $biomeId) : self{ - return new BiomeArray(str_repeat(chr($biomeId), 256)); - } - - private static function idx(int $x, int $z) : int{ - if($x < 0 || $x >= 16 || $z < 0 || $z >= 16){ - throw new \InvalidArgumentException("x and z must be in the range 0-15"); - } - return ($z << 4) | $x; - } - - public function get(int $x, int $z) : int{ - return ord($this->payload[self::idx($x, $z)]); - } - - public function set(int $x, int $z, int $biomeId) : void{ - if($biomeId < 0 || $biomeId >= 256){ - throw new \InvalidArgumentException("Biome ID must be in the range 0-255"); - } - $this->payload[self::idx($x, $z)] = chr($biomeId); - } - - /** - * @return string ZZZZXXXX key bits - */ - public function getData() : string{ - return $this->payload; - } -} diff --git a/src/world/format/Chunk.php b/src/world/format/Chunk.php index e6b4780c6..7c55b6292 100644 --- a/src/world/format/Chunk.php +++ b/src/world/format/Chunk.php @@ -29,6 +29,7 @@ namespace pocketmine\world\format; use pocketmine\block\Block; use pocketmine\block\BlockTypeIds; use pocketmine\block\tile\Tile; +use pocketmine\data\bedrock\BiomeIds; use function array_map; class Chunk{ @@ -59,21 +60,19 @@ class Chunk{ protected HeightArray $heightMap; - protected BiomeArray $biomeIds; - /** * @param SubChunk[] $subChunks */ - public function __construct(array $subChunks, BiomeArray $biomeIds, bool $terrainPopulated){ + public function __construct(array $subChunks, bool $terrainPopulated){ $this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS); foreach($this->subChunks as $y => $null){ - $this->subChunks[$y] = $subChunks[$y + self::MIN_SUBCHUNK_INDEX] ?? new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, []); + //TODO: we should probably require all subchunks to be provided here + $this->subChunks[$y] = $subChunks[$y + self::MIN_SUBCHUNK_INDEX] ?? new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [], new PalettedBlockArray(BiomeIds::OCEAN)); } $val = (self::MAX_SUBCHUNK_INDEX + 1) * SubChunk::EDGE_LENGTH; $this->heightMap = HeightArray::fill($val); //TODO: what about lazily initializing this? - $this->biomeIds = $biomeIds; $this->terrainPopulated = $terrainPopulated; } @@ -153,8 +152,8 @@ class Chunk{ * * @return int 0-255 */ - public function getBiomeId(int $x, int $z) : int{ - return $this->biomeIds->get($x, $z); + public function getBiomeId(int $x, int $y, int $z) : int{ + return $this->getSubChunk($y >> SubChunk::COORD_BIT_SIZE)->getBiomeArray()->get($x, $y, $z); } /** @@ -164,8 +163,8 @@ class Chunk{ * @param int $z 0-15 * @param int $biomeId 0-255 */ - public function setBiomeId(int $x, int $z, int $biomeId) : void{ - $this->biomeIds->set($x, $z, $biomeId); + public function setBiomeId(int $x, int $y, int $z, int $biomeId) : void{ + $this->getSubChunk($y >> SubChunk::COORD_BIT_SIZE)->getBiomeArray()->set($x, $y, $z, $biomeId); $this->terrainDirtyFlags |= self::DIRTY_FLAG_BIOMES; } @@ -230,10 +229,6 @@ class Chunk{ } } - public function getBiomeIdArray() : string{ - return $this->biomeIds->getData(); - } - /** * @return int[] */ @@ -291,7 +286,7 @@ class Chunk{ throw new \InvalidArgumentException("Invalid subchunk Y coordinate $y"); } - $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX] = $subChunk ?? new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, []); + $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX] = $subChunk ?? new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [], new PalettedBlockArray(BiomeIds::OCEAN)); $this->setTerrainDirtyFlag(self::DIRTY_FLAG_BLOCKS, true); } @@ -322,7 +317,6 @@ class Chunk{ return clone $subChunk; }, $this->subChunks->toArray())); $this->heightMap = clone $this->heightMap; - $this->biomeIds = clone $this->biomeIds; } /** diff --git a/src/world/format/SubChunk.php b/src/world/format/SubChunk.php index d10968160..5dc221514 100644 --- a/src/world/format/SubChunk.php +++ b/src/world/format/SubChunk.php @@ -40,6 +40,7 @@ class SubChunk{ public function __construct( private int $emptyBlockId, private array $blockLayers, + private PalettedBlockArray $biomes, private ?LightArray $skyLight = null, private ?LightArray $blockLight = null ){} @@ -102,6 +103,8 @@ class SubChunk{ return null; //highest block not in this subchunk } + public function getBiomeArray() : PalettedBlockArray{ return $this->biomes; } + public function getBlockSkyLightArray() : LightArray{ return $this->skyLight ??= LightArray::fill(0); } @@ -137,6 +140,7 @@ class SubChunk{ unset($this->blockLayers[$k]); } $this->blockLayers = array_values($this->blockLayers); + $this->biomes->collectGarbage(); if($this->skyLight !== null && $this->skyLight->isUniform(0)){ $this->skyLight = null; @@ -150,6 +154,7 @@ class SubChunk{ $this->blockLayers = array_map(function(PalettedBlockArray $array) : PalettedBlockArray{ return clone $array; }, $this->blockLayers); + $this->biomes = clone $this->biomes; if($this->skyLight !== null){ $this->skyLight = clone $this->skyLight; diff --git a/src/world/format/io/ChunkUtils.php b/src/world/format/io/ChunkUtils.php index b9b366685..3692b2470 100644 --- a/src/world/format/io/ChunkUtils.php +++ b/src/world/format/io/ChunkUtils.php @@ -23,8 +23,11 @@ declare(strict_types=1); namespace pocketmine\world\format\io; +use pocketmine\world\format\PalettedBlockArray; use function chr; +use function ord; use function str_repeat; +use function strlen; class ChunkUtils{ @@ -42,4 +45,23 @@ class ChunkUtils{ return $result; } + /** + * Converts 2D biomes into a 3D biome palette. This palette can then be cloned for every subchunk. + */ + public static function extrapolate3DBiomes(string $biomes2d) : PalettedBlockArray{ + if(strlen($biomes2d) !== 256){ + throw new \InvalidArgumentException("Biome array is expected to be exactly 256 bytes"); + } + $biomePalette = new PalettedBlockArray(ord($biomes2d[0])); + for($x = 0; $x < 16; ++$x){ + for($z = 0; $z < 16; ++$z){ + $biomeId = ord($biomes2d[($z << 4) | $x]); + for($y = 0; $y < 16; ++$y){ + $biomePalette->set($x, $y, $z, $biomeId); + } + } + } + + return $biomePalette; + } } diff --git a/src/world/format/io/FastChunkSerializer.php b/src/world/format/io/FastChunkSerializer.php index 7da8c9ab4..6e18f27ac 100644 --- a/src/world/format/io/FastChunkSerializer.php +++ b/src/world/format/io/FastChunkSerializer.php @@ -25,7 +25,6 @@ namespace pocketmine\world\format\io; use pocketmine\utils\Binary; use pocketmine\utils\BinaryStream; -use pocketmine\world\format\BiomeArray; use pocketmine\world\format\Chunk; use pocketmine\world\format\PalettedBlockArray; use pocketmine\world\format\SubChunk; @@ -46,6 +45,17 @@ final class FastChunkSerializer{ //NOOP } + private static function serializePalettedArray(BinaryStream $stream, PalettedBlockArray $array) : void{ + $wordArray = $array->getWordArray(); + $palette = $array->getPalette(); + + $stream->putByte($array->getBitsPerBlock()); + $stream->put($wordArray); + $serialPalette = pack("L*", ...$palette); + $stream->putInt(strlen($serialPalette)); + $stream->put($serialPalette); + } + /** * Fast-serializes the chunk for passing between threads * TODO: tiles and entities @@ -67,23 +77,25 @@ final class FastChunkSerializer{ $layers = $subChunk->getBlockLayers(); $stream->putByte(count($layers)); foreach($layers as $blocks){ - $wordArray = $blocks->getWordArray(); - $palette = $blocks->getPalette(); - - $stream->putByte($blocks->getBitsPerBlock()); - $stream->put($wordArray); - $serialPalette = pack("L*", ...$palette); - $stream->putInt(strlen($serialPalette)); - $stream->put($serialPalette); + self::serializePalettedArray($stream, $blocks); } + self::serializePalettedArray($stream, $subChunk->getBiomeArray()); + } - //biomes - $stream->put($chunk->getBiomeIdArray()); - return $stream->getBuffer(); } + private static function deserializePalettedArray(BinaryStream $stream) : PalettedBlockArray{ + $bitsPerBlock = $stream->getByte(); + $words = $stream->get(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock)); + /** @var int[] $unpackedPalette */ + $unpackedPalette = unpack("L*", $stream->get($stream->getInt())); //unpack() will never fail here + $palette = array_values($unpackedPalette); + + return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette); + } + /** * Deserializes a fast-serialized chunk */ @@ -103,19 +115,12 @@ final class FastChunkSerializer{ /** @var PalettedBlockArray[] $layers */ $layers = []; for($i = 0, $layerCount = $stream->getByte(); $i < $layerCount; ++$i){ - $bitsPerBlock = $stream->getByte(); - $words = $stream->get(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock)); - /** @var int[] $unpackedPalette */ - $unpackedPalette = unpack("L*", $stream->get($stream->getInt())); //unpack() will never fail here - $palette = array_values($unpackedPalette); - - $layers[] = PalettedBlockArray::fromData($bitsPerBlock, $words, $palette); + $layers[] = self::deserializePalettedArray($stream); } - $subChunks[$y] = new SubChunk($airBlockId, $layers); + $biomeArray = self::deserializePalettedArray($stream); + $subChunks[$y] = new SubChunk($airBlockId, $layers, $biomeArray); } - $biomeIds = new BiomeArray($stream->get(256)); - - return new Chunk($subChunks, $biomeIds, $terrainPopulated); + return new Chunk($subChunks, $terrainPopulated); } } diff --git a/src/world/format/io/leveldb/LevelDB.php b/src/world/format/io/leveldb/LevelDB.php index 13514261b..c8bb35e73 100644 --- a/src/world/format/io/leveldb/LevelDB.php +++ b/src/world/format/io/leveldb/LevelDB.php @@ -35,7 +35,6 @@ use pocketmine\nbt\TreeRoot; use pocketmine\utils\Binary; use pocketmine\utils\BinaryDataException; use pocketmine\utils\BinaryStream; -use pocketmine\world\format\BiomeArray; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\BaseWorldProvider; use pocketmine\world\format\io\ChunkData; @@ -145,7 +144,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ /** * @throws CorruptedChunkException */ - protected function deserializePaletted(BinaryStream $stream) : PalettedBlockArray{ + protected function deserializeBlockPalette(BinaryStream $stream) : PalettedBlockArray{ $bitsPerBlock = $stream->getByte() >> 1; try{ @@ -188,6 +187,99 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette); } + /** + * @throws CorruptedChunkException + */ + private static function getExpected3dBiomesCount(int $chunkVersion) : int{ + return match(true){ + $chunkVersion >= ChunkVersion::v1_18_30 => 24, + $chunkVersion >= ChunkVersion::v1_18_0_25_beta => 25, + $chunkVersion >= ChunkVersion::v1_18_0_24_beta => 32, + $chunkVersion >= ChunkVersion::v1_18_0_22_beta => 65, + $chunkVersion >= ChunkVersion::v1_17_40_20_beta_experimental_caves_cliffs => 32, + default => throw new CorruptedChunkException("Chunk version $chunkVersion should not have 3D biomes") + }; + } + + /** + * @throws CorruptedChunkException + */ + private static function deserializeBiomePalette(BinaryStream $stream, int $bitsPerBlock) : PalettedBlockArray{ + try{ + $words = $stream->get(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock)); + }catch(\InvalidArgumentException $e){ + throw new CorruptedChunkException("Failed to deserialize paletted biomes: " . $e->getMessage(), 0, $e); + } + $palette = []; + $paletteSize = $bitsPerBlock === 0 ? 1 : $stream->getLInt(); + + for($i = 0; $i < $paletteSize; ++$i){ + $palette[] = $stream->getLInt(); + } + + //TODO: exceptions + return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette); + } + + private static function serializeBiomePalette(BinaryStream $stream, PalettedBlockArray $biomes) : void{ + $stream->putByte($biomes->getBitsPerBlock() << 1); + $stream->put($biomes->getWordArray()); + + $palette = $biomes->getPalette(); + if($biomes->getBitsPerBlock() !== 0){ + $stream->putLInt(count($palette)); + } + foreach($palette as $p){ + $stream->putLInt($p); + } + } + + /** + * @throws CorruptedChunkException + * @return PalettedBlockArray[] + * @phpstan-return array + */ + private static function deserialize3dBiomes(BinaryStream $stream, int $chunkVersion) : array{ + $previous = null; + $result = []; + $nextIndex = Chunk::MIN_SUBCHUNK_INDEX; + + $expectedCount = self::getExpected3dBiomesCount($chunkVersion); + for($i = 0; $i < $expectedCount; ++$i){ + try{ + $bitsPerBlock = $stream->getByte() >> 1; + if($bitsPerBlock === 127){ + if($previous === null){ + throw new CorruptedChunkException("Serialized biome palette $i has no previous palette to copy from"); + } + $decoded = clone $previous; + }else{ + $decoded = self::deserializeBiomePalette($stream, $bitsPerBlock); + } + $previous = $decoded; + if($nextIndex <= Chunk::MAX_SUBCHUNK_INDEX){ //older versions wrote additional superfluous biome palettes + $result[$nextIndex++] = $decoded; + } + }catch(BinaryDataException $e){ + throw new CorruptedChunkException("Failed to deserialize biome palette $i: " . $e->getMessage(), 0, $e); + } + } + if(!$stream->feof()){ + throw new CorruptedChunkException("3D biomes data contains extra unread data"); + } + + return $result; + } + + private static function serialize3dBiomes(BinaryStream $stream, Chunk $chunk) : void{ + //TODO: the server-side min/max may not coincide with the world storage min/max - we may need additional logic to handle this + for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; $y++){ + //TODO: is it worth trying to use the previous palette if it's the same as the current one? vanilla supports + //this, but it's not clear if it's worth the effort to implement. + self::serializeBiomePalette($stream, $chunk->getSubChunk($y)->getBiomeArray()); + } + } + /** * @phpstan-param-out int $x * @phpstan-param-out int $y @@ -280,9 +372,6 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ /** @var SubChunk[] $subChunks */ $subChunks = []; - /** @var BiomeArray|null $biomeArray */ - $biomeArray = null; - $hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION; $subChunkKeyOffset = self::hasOffsetCavesAndCliffsSubChunks($chunkVersion) ? self::CAVES_CLIFFS_EXPERIMENTAL_SUBCHUNK_KEY_OFFSET : 0; @@ -330,8 +419,37 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ case ChunkVersion::v1_0_0: $convertedLegacyExtraData = $this->deserializeLegacyExtraData($index, $chunkVersion); + $biomeArrays = []; + if(($maps2d = $this->db->get($index . ChunkDataKey::HEIGHTMAP_AND_2D_BIOMES)) !== false){ + $binaryStream = new BinaryStream($maps2d); + + try{ + $binaryStream->get(512); //heightmap, discard it + $biomes3d = ChunkUtils::extrapolate3DBiomes($binaryStream->get(256)); //never throws + }catch(BinaryDataException $e){ + throw new CorruptedChunkException($e->getMessage(), 0, $e); + } + for($i = Chunk::MIN_SUBCHUNK_INDEX; $i <= Chunk::MAX_SUBCHUNK_INDEX; ++$i){ + $biomeArrays[$i] = clone $biomes3d; + } + }elseif(($maps3d = $this->db->get($index . ChunkDataKey::HEIGHTMAP_AND_3D_BIOMES)) !== false){ + $binaryStream = new BinaryStream($maps3d); + + try{ + $binaryStream->get(512); + $biomeArrays = self::deserialize3dBiomes($binaryStream, $chunkVersion); + }catch(BinaryDataException $e){ + throw new CorruptedChunkException($e->getMessage(), 0, $e); + } + }else{ + for($i = Chunk::MIN_SUBCHUNK_INDEX; $i <= Chunk::MAX_SUBCHUNK_INDEX; ++$i){ + $biomeArrays[$i] = new PalettedBlockArray(BiomeIds::OCEAN); //polyfill + } + } + for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){ if(($data = $this->db->get($index . ChunkDataKey::SUBCHUNK . chr($y + $subChunkKeyOffset))) === false){ + $subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [], $biomeArrays[$y]); continue; } @@ -369,14 +487,14 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ $storages[] = $convertedLegacyExtraData[$y]; } - $subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, $storages); + $subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, $storages, $biomeArrays[$y]); break; case SubChunkVersion::PALETTED_SINGLE: - $storages = [$this->deserializePaletted($binaryStream)]; + $storages = [$this->deserializeBlockPalette($binaryStream)]; if(isset($convertedLegacyExtraData[$y])){ $storages[] = $convertedLegacyExtraData[$y]; } - $subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, $storages); + $subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, $storages, $biomeArrays[$y]); break; case SubChunkVersion::PALETTED_MULTI: case SubChunkVersion::PALETTED_MULTI_WITH_OFFSET: @@ -390,9 +508,9 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ $storages = []; for($k = 0; $k < $storageCount; ++$k){ - $storages[] = $this->deserializePaletted($binaryStream); + $storages[] = $this->deserializeBlockPalette($binaryStream); } - $subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, $storages); + $subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, $storages, $biomeArrays[$y]); } break; default: @@ -401,16 +519,6 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ } } - if(($maps2d = $this->db->get($index . ChunkDataKey::HEIGHTMAP_AND_2D_BIOMES)) !== false){ - $binaryStream = new BinaryStream($maps2d); - - try{ - $binaryStream->get(512); //heightmap, discard it - $biomeArray = new BiomeArray($binaryStream->get(256)); //never throws - }catch(BinaryDataException $e){ - throw new CorruptedChunkException($e->getMessage(), 0, $e); - } - } break; case ChunkVersion::v0_9_5: case ChunkVersion::v0_9_2: @@ -430,22 +538,30 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ throw new CorruptedChunkException($e->getMessage(), 0, $e); } + try{ + $binaryStream->get(256); //heightmap, discard it + /** @var int[] $unpackedBiomeArray */ + $unpackedBiomeArray = unpack("N*", $binaryStream->get(1024)); //unpack() will never fail here + $biomes3d = ChunkUtils::extrapolate3DBiomes(ChunkUtils::convertBiomeColors(array_values($unpackedBiomeArray))); //never throws + }catch(BinaryDataException $e){ + throw new CorruptedChunkException($e->getMessage(), 0, $e); + } + for($yy = 0; $yy < 8; ++$yy){ $storages = [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $yy)]; if(isset($convertedLegacyExtraData[$yy])){ $storages[] = $convertedLegacyExtraData[$yy]; } - $subChunks[$yy] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, $storages); + $subChunks[$yy] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, $storages, clone $biomes3d); } - try{ - $binaryStream->get(256); //heightmap, discard it - /** @var int[] $unpackedBiomeArray */ - $unpackedBiomeArray = unpack("N*", $binaryStream->get(1024)); //unpack() will never fail here - $biomeArray = new BiomeArray(ChunkUtils::convertBiomeColors(array_values($unpackedBiomeArray))); //never throws - }catch(BinaryDataException $e){ - throw new CorruptedChunkException($e->getMessage(), 0, $e); + //make sure extrapolated biomes get filled in correctly + for($yy = Chunk::MIN_SUBCHUNK_INDEX; $yy <= Chunk::MAX_SUBCHUNK_INDEX; ++$yy){ + if(!isset($subChunks[$yy])){ + $subChunks[$yy] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [], clone $biomes3d); + } } + break; default: //TODO: set chunks read-only so the version on disk doesn't get overwritten @@ -485,8 +601,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ //TODO: tile ticks, biome states (?) $chunk = new Chunk( - $subChunks, - $biomeArray ?? BiomeArray::fill(BiomeIds::OCEAN), //TODO: maybe missing biomes should be an error? + $subChunks, //TODO: maybe missing biomes should be an error? $terrainPopulated ); @@ -545,7 +660,11 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ } if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BIOMES)){ - $write->put($index . ChunkDataKey::HEIGHTMAP_AND_2D_BIOMES, str_repeat("\x00", 512) . $chunk->getBiomeIdArray()); + $write->delete($index . ChunkDataKey::HEIGHTMAP_AND_2D_BIOMES); + $stream = new BinaryStream(); + $stream->put(str_repeat("\x00", 512)); //fake heightmap + self::serialize3dBiomes($stream, $chunk); + $write->put($index . ChunkDataKey::HEIGHTMAP_AND_3D_BIOMES, $stream->getBuffer()); } //TODO: use this properly diff --git a/src/world/format/io/region/Anvil.php b/src/world/format/io/region/Anvil.php index 8012facd9..c1b6b9671 100644 --- a/src/world/format/io/region/Anvil.php +++ b/src/world/format/io/region/Anvil.php @@ -26,16 +26,17 @@ namespace pocketmine\world\format\io\region; use pocketmine\block\Block; use pocketmine\block\BlockTypeIds; use pocketmine\nbt\tag\CompoundTag; +use pocketmine\world\format\PalettedBlockArray; use pocketmine\world\format\SubChunk; class Anvil extends RegionWorldProvider{ use LegacyAnvilChunkTrait; - protected function deserializeSubChunk(CompoundTag $subChunk) : SubChunk{ + protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d) : SubChunk{ return new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [$this->palettizeLegacySubChunkYZX( self::readFixedSizeByteArray($subChunk, "Blocks", 4096), self::readFixedSizeByteArray($subChunk, "Data", 2048) - )]); + )], $biomes3d); //ignore legacy light information } diff --git a/src/world/format/io/region/LegacyAnvilChunkTrait.php b/src/world/format/io/region/LegacyAnvilChunkTrait.php index 8d4f8ddf4..05d4b0fb8 100644 --- a/src/world/format/io/region/LegacyAnvilChunkTrait.php +++ b/src/world/format/io/region/LegacyAnvilChunkTrait.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace pocketmine\world\format\io\region; +use pocketmine\block\Block; +use pocketmine\block\BlockTypeIds; use pocketmine\data\bedrock\BiomeIds; use pocketmine\nbt\BigEndianNbtSerializer; use pocketmine\nbt\NbtDataException; @@ -30,12 +32,13 @@ use pocketmine\nbt\tag\ByteArrayTag; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntArrayTag; use pocketmine\nbt\tag\ListTag; -use pocketmine\world\format\BiomeArray; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\ChunkData; use pocketmine\world\format\io\ChunkUtils; use pocketmine\world\format\io\exception\CorruptedChunkException; +use pocketmine\world\format\PalettedBlockArray; use pocketmine\world\format\SubChunk; +use function strlen; use function zlib_decode; /** @@ -67,34 +70,38 @@ trait LegacyAnvilChunkTrait{ throw new CorruptedChunkException("'Level' key is missing from chunk NBT"); } + $makeBiomeArray = function(string $biomeIds) : PalettedBlockArray{ + if(strlen($biomeIds) !== 256){ + throw new CorruptedChunkException("Expected biome array to be exactly 256 bytes, got " . strlen($biomeIds)); + } + //TODO: we may need to convert legacy biome IDs + return ChunkUtils::extrapolate3DBiomes($biomeIds); + }; + + if(($biomeColorsTag = $chunk->getTag("BiomeColors")) instanceof IntArrayTag){ + $biomes3d = $makeBiomeArray(ChunkUtils::convertBiomeColors($biomeColorsTag->getValue())); //Convert back to original format + }elseif(($biomesTag = $chunk->getTag("Biomes")) instanceof ByteArrayTag){ + $biomes3d = $makeBiomeArray($biomesTag->getValue()); + }else{ + $biomes3d = new PalettedBlockArray(BiomeIds::OCEAN); + } + $subChunks = []; $subChunksTag = $chunk->getListTag("Sections") ?? []; foreach($subChunksTag as $subChunk){ if($subChunk instanceof CompoundTag){ - $subChunks[$subChunk->getByte("Y")] = $this->deserializeSubChunk($subChunk); + $subChunks[$subChunk->getByte("Y")] = $this->deserializeSubChunk($subChunk, clone $biomes3d); } } - - $makeBiomeArray = function(string $biomeIds) : BiomeArray{ - try{ - return new BiomeArray($biomeIds); - }catch(\InvalidArgumentException $e){ - throw new CorruptedChunkException($e->getMessage(), 0, $e); + for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){ + if(!isset($subChunks[$y])){ + $subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [], clone $biomes3d); } - }; - $biomeArray = null; - if(($biomeColorsTag = $chunk->getTag("BiomeColors")) instanceof IntArrayTag){ - $biomeArray = $makeBiomeArray(ChunkUtils::convertBiomeColors($biomeColorsTag->getValue())); //Convert back to original format - }elseif(($biomesTag = $chunk->getTag("Biomes")) instanceof ByteArrayTag){ - $biomeArray = $makeBiomeArray($biomesTag->getValue()); - }else{ - $biomeArray = BiomeArray::fill(BiomeIds::OCEAN); } return new ChunkData( new Chunk( $subChunks, - $biomeArray, $chunk->getByte("TerrainPopulated", 0) !== 0 ), ($entitiesTag = $chunk->getTag("Entities")) instanceof ListTag ? self::getCompoundList("Entities", $entitiesTag) : [], @@ -102,6 +109,6 @@ trait LegacyAnvilChunkTrait{ ); } - abstract protected function deserializeSubChunk(CompoundTag $subChunk) : SubChunk; + abstract protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d) : SubChunk; } diff --git a/src/world/format/io/region/McRegion.php b/src/world/format/io/region/McRegion.php index 98e9f703e..f911d0043 100644 --- a/src/world/format/io/region/McRegion.php +++ b/src/world/format/io/region/McRegion.php @@ -33,12 +33,13 @@ use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntArrayTag; use pocketmine\nbt\tag\ListTag; -use pocketmine\world\format\BiomeArray; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\ChunkData; use pocketmine\world\format\io\ChunkUtils; use pocketmine\world\format\io\exception\CorruptedChunkException; +use pocketmine\world\format\PalettedBlockArray; use pocketmine\world\format\SubChunk; +use function strlen; use function zlib_decode; class McRegion extends RegionWorldProvider{ @@ -69,34 +70,37 @@ class McRegion extends RegionWorldProvider{ //trying to read it. return null; } + + $makeBiomeArray = function(string $biomeIds) : PalettedBlockArray{ + if(strlen($biomeIds) !== 256){ + throw new CorruptedChunkException("Expected biome array to be exactly 256 bytes, got " . strlen($biomeIds)); + } + return ChunkUtils::extrapolate3DBiomes($biomeIds); + }; + if(($biomeColorsTag = $chunk->getTag("BiomeColors")) instanceof IntArrayTag){ + $biomes3d = $makeBiomeArray(ChunkUtils::convertBiomeColors($biomeColorsTag->getValue())); //Convert back to original format + }elseif(($biomesTag = $chunk->getTag("Biomes")) instanceof ByteArrayTag){ + $biomes3d = $makeBiomeArray($biomesTag->getValue()); + }else{ + $biomes3d = new PalettedBlockArray(BiomeIds::OCEAN); + } + $subChunks = []; $fullIds = self::readFixedSizeByteArray($chunk, "Blocks", 32768); $fullData = self::readFixedSizeByteArray($chunk, "Data", 16384); for($y = 0; $y < 8; ++$y){ - $subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $y)]); + $subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $y)], clone $biomes3d); } - - $makeBiomeArray = function(string $biomeIds) : BiomeArray{ - try{ - return new BiomeArray($biomeIds); - }catch(\InvalidArgumentException $e){ - throw new CorruptedChunkException($e->getMessage(), 0, $e); + for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){ + if(!isset($subChunks[$y])){ + $subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [], clone $biomes3d); } - }; - $biomeIds = null; - if(($biomeColorsTag = $chunk->getTag("BiomeColors")) instanceof IntArrayTag){ - $biomeIds = $makeBiomeArray(ChunkUtils::convertBiomeColors($biomeColorsTag->getValue())); //Convert back to original format - }elseif(($biomesTag = $chunk->getTag("Biomes")) instanceof ByteArrayTag){ - $biomeIds = $makeBiomeArray($biomesTag->getValue()); - }else{ - $biomeIds = BiomeArray::fill(BiomeIds::OCEAN); } return new ChunkData( new Chunk( $subChunks, - $biomeIds, $chunk->getByte("TerrainPopulated", 0) !== 0 ), ($entitiesTag = $chunk->getTag("Entities")) instanceof ListTag ? self::getCompoundList("Entities", $entitiesTag) : [], diff --git a/src/world/format/io/region/PMAnvil.php b/src/world/format/io/region/PMAnvil.php index 29b47b352..8d31f73bc 100644 --- a/src/world/format/io/region/PMAnvil.php +++ b/src/world/format/io/region/PMAnvil.php @@ -26,6 +26,7 @@ namespace pocketmine\world\format\io\region; use pocketmine\block\Block; use pocketmine\block\BlockTypeIds; use pocketmine\nbt\tag\CompoundTag; +use pocketmine\world\format\PalettedBlockArray; use pocketmine\world\format\SubChunk; /** @@ -35,11 +36,11 @@ use pocketmine\world\format\SubChunk; class PMAnvil extends RegionWorldProvider{ use LegacyAnvilChunkTrait; - protected function deserializeSubChunk(CompoundTag $subChunk) : SubChunk{ + protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d) : SubChunk{ return new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [$this->palettizeLegacySubChunkXZY( self::readFixedSizeByteArray($subChunk, "Blocks", 4096), self::readFixedSizeByteArray($subChunk, "Data", 2048) - )]); + )], $biomes3d); } protected static function getRegionFileExtension() : string{ diff --git a/src/world/generator/Flat.php b/src/world/generator/Flat.php index 12dbc4bd0..9536cde03 100644 --- a/src/world/generator/Flat.php +++ b/src/world/generator/Flat.php @@ -25,7 +25,6 @@ namespace pocketmine\world\generator; use pocketmine\block\VanillaBlocks; use pocketmine\world\ChunkManager; -use pocketmine\world\format\BiomeArray; use pocketmine\world\format\Chunk; use pocketmine\world\format\SubChunk; use pocketmine\world\generator\object\OreType; @@ -67,7 +66,7 @@ class Flat extends Generator{ } protected function generateBaseChunk() : void{ - $this->chunk = new Chunk([], BiomeArray::fill($this->options->getBiomeId()), false); + $this->chunk = new Chunk([], false); $structure = $this->options->getStructure(); $count = count($structure); diff --git a/src/world/generator/PopulationTask.php b/src/world/generator/PopulationTask.php index b2e3d610c..e7e2b407c 100644 --- a/src/world/generator/PopulationTask.php +++ b/src/world/generator/PopulationTask.php @@ -23,10 +23,8 @@ declare(strict_types=1); namespace pocketmine\world\generator; -use pocketmine\data\bedrock\BiomeIds; use pocketmine\scheduler\AsyncTask; use pocketmine\utils\AssumptionFailedError; -use pocketmine\world\format\BiomeArray; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\FastChunkSerializer; use pocketmine\world\SimpleChunkManager; @@ -112,7 +110,7 @@ class PopulationTask extends AsyncTask{ } private static function setOrGenerateChunk(SimpleChunkManager $manager, Generator $generator, int $chunkX, int $chunkZ, ?Chunk $chunk) : Chunk{ - $manager->setChunk($chunkX, $chunkZ, $chunk ?? new Chunk([], BiomeArray::fill(BiomeIds::OCEAN), false)); + $manager->setChunk($chunkX, $chunkZ, $chunk ?? new Chunk([], false)); if($chunk === null){ $generator->generateChunk($manager, $chunkX, $chunkZ); $chunk = $manager->getChunk($chunkX, $chunkZ); diff --git a/src/world/generator/hell/Nether.php b/src/world/generator/hell/Nether.php index 61c8624a0..e00b22015 100644 --- a/src/world/generator/hell/Nether.php +++ b/src/world/generator/hell/Nether.php @@ -34,6 +34,7 @@ use pocketmine\world\generator\noise\Simplex; use pocketmine\world\generator\object\OreType; use pocketmine\world\generator\populator\Ore; use pocketmine\world\generator\populator\Populator; +use pocketmine\world\World; use function abs; class Nether extends Generator{ @@ -78,7 +79,9 @@ class Nether extends Generator{ for($x = 0; $x < Chunk::EDGE_LENGTH; ++$x){ for($z = 0; $z < Chunk::EDGE_LENGTH; ++$z){ - $chunk->setBiomeId($x, $z, BiomeIds::HELL); + for($y = World::Y_MIN; $y < World::Y_MAX; $y++){ + $chunk->setBiomeId($x, $y, $z, BiomeIds::HELL); + } for($y = 0; $y < 128; ++$y){ if($y === 0 || $y === 127){ @@ -109,7 +112,7 @@ class Nether extends Generator{ } $chunk = $world->getChunk($chunkX, $chunkZ); - $biome = BiomeRegistry::getInstance()->getBiome($chunk->getBiomeId(7, 7)); + $biome = BiomeRegistry::getInstance()->getBiome($chunk->getBiomeId(7, 7, 7)); $biome->populateChunk($world, $chunkX, $chunkZ, $this->random); } } diff --git a/src/world/generator/normal/Normal.php b/src/world/generator/normal/Normal.php index c03a06feb..107d147e9 100644 --- a/src/world/generator/normal/Normal.php +++ b/src/world/generator/normal/Normal.php @@ -160,7 +160,9 @@ class Normal extends Generator{ $weightSum = 0; $biome = $this->pickBiome($absoluteX, $absoluteZ); - $chunk->setBiomeId($x, $z, $biome->getId()); + for($y = World::Y_MIN; $y < World::Y_MAX; $y++){ + $chunk->setBiomeId($x, $y, $z, $biome->getId()); + } for($sx = -$this->gaussian->smoothSize; $sx <= $this->gaussian->smoothSize; ++$sx){ for($sz = -$this->gaussian->smoothSize; $sz <= $this->gaussian->smoothSize; ++$sz){ @@ -218,7 +220,7 @@ class Normal extends Generator{ } $chunk = $world->getChunk($chunkX, $chunkZ); - $biome = BiomeRegistry::getInstance()->getBiome($chunk->getBiomeId(7, 7)); + $biome = BiomeRegistry::getInstance()->getBiome($chunk->getBiomeId(7, 7, 7)); $biome->populateChunk($world, $chunkX, $chunkZ, $this->random); } } diff --git a/src/world/generator/populator/GroundCover.php b/src/world/generator/populator/GroundCover.php index 2aa6cca9b..16a1d95a0 100644 --- a/src/world/generator/populator/GroundCover.php +++ b/src/world/generator/populator/GroundCover.php @@ -41,7 +41,7 @@ class GroundCover implements Populator{ $biomeRegistry = BiomeRegistry::getInstance(); for($x = 0; $x < Chunk::EDGE_LENGTH; ++$x){ for($z = 0; $z < Chunk::EDGE_LENGTH; ++$z){ - $biome = $biomeRegistry->getBiome($chunk->getBiomeId($x, $z)); + $biome = $biomeRegistry->getBiome($chunk->getBiomeId($x, 0, $z)); $cover = $biome->getGroundCover(); if(count($cover) > 0){ $diffY = 0; diff --git a/tests/phpunit/world/format/ChunkTest.php b/tests/phpunit/world/format/ChunkTest.php index b285a6308..a8cca71b2 100644 --- a/tests/phpunit/world/format/ChunkTest.php +++ b/tests/phpunit/world/format/ChunkTest.php @@ -24,23 +24,22 @@ declare(strict_types=1); namespace pocketmine\world\format; use PHPUnit\Framework\TestCase; -use pocketmine\data\bedrock\BiomeIds; class ChunkTest extends TestCase{ public function testClone() : void{ - $chunk = new Chunk([], BiomeArray::fill(BiomeIds::OCEAN), false); + $chunk = new Chunk([], false); $chunk->setFullBlock(0, 0, 0, 1); - $chunk->setBiomeId(0, 0, 1); + $chunk->setBiomeId(0, 0, 0, 1); $chunk->setHeightMap(0, 0, 1); $chunk2 = clone $chunk; $chunk2->setFullBlock(0, 0, 0, 2); - $chunk2->setBiomeId(0, 0, 2); + $chunk2->setBiomeId(0, 0, 0, 2); $chunk2->setHeightMap(0, 0, 2); self::assertNotSame($chunk->getFullBlock(0, 0, 0), $chunk2->getFullBlock(0, 0, 0)); - self::assertNotSame($chunk->getBiomeId(0, 0), $chunk2->getBiomeId(0, 0)); + self::assertNotSame($chunk->getBiomeId(0, 0, 0), $chunk2->getBiomeId(0, 0, 0)); self::assertNotSame($chunk->getHeightMap(0, 0), $chunk2->getHeightMap(0, 0)); } } diff --git a/tests/phpunit/world/format/SubChunkTest.php b/tests/phpunit/world/format/SubChunkTest.php index 4f33a45fc..3b7861051 100644 --- a/tests/phpunit/world/format/SubChunkTest.php +++ b/tests/phpunit/world/format/SubChunkTest.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\world\format; use PHPUnit\Framework\TestCase; +use pocketmine\data\bedrock\BiomeIds; class SubChunkTest extends TestCase{ @@ -31,7 +32,7 @@ class SubChunkTest extends TestCase{ * Test that a cloned SubChunk instance doesn't influence the original */ public function testClone() : void{ - $sub1 = new SubChunk(0, []); + $sub1 = new SubChunk(0, [], new PalettedBlockArray(BiomeIds::OCEAN)); $sub1->setFullBlock(0, 0, 0, 1); $sub1->getBlockLightArray()->set(0, 0, 0, 1);