mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-07-11 04:17:48 +00:00
First look at 3D biome support
This commit is contained in:
parent
3a13f5cf5f
commit
7abfc46567
@ -36,9 +36,7 @@ use pocketmine\utils\BinaryStream;
|
|||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\PalettedBlockArray;
|
use pocketmine\world\format\PalettedBlockArray;
|
||||||
use pocketmine\world\format\SubChunk;
|
use pocketmine\world\format\SubChunk;
|
||||||
use function chr;
|
|
||||||
use function count;
|
use function count;
|
||||||
use function str_repeat;
|
|
||||||
|
|
||||||
final class ChunkSerializer{
|
final class ChunkSerializer{
|
||||||
private function __construct(){
|
private function __construct(){
|
||||||
@ -64,13 +62,16 @@ final class ChunkSerializer{
|
|||||||
$stream = PacketSerializer::encoder($encoderContext);
|
$stream = PacketSerializer::encoder($encoderContext);
|
||||||
|
|
||||||
$subChunkCount = self::getSubChunkCount($chunk);
|
$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);
|
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
|
$biomeIdMap = LegacyBiomeIdToStringIdMap::getInstance();
|
||||||
$encodedBiomePalette = self::serializeBiomesAsPalette($chunk);
|
//all biomes must always be written :(
|
||||||
$stream->put(str_repeat($encodedBiomePalette, 24));
|
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
|
$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.
|
//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{
|
public static function serializeTiles(Chunk $chunk) : string{
|
||||||
$stream = new BinaryStream();
|
$stream = new BinaryStream();
|
||||||
foreach($chunk->getTiles() as $tile){
|
foreach($chunk->getTiles() as $tile){
|
||||||
@ -133,39 +156,4 @@ final class ChunkSerializer{
|
|||||||
|
|
||||||
return $stream->getBuffer();
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
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){
|
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)
|
return BiomeIds::OCEAN; //TODO: this should probably throw instead (terrain not generated yet)
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBiome(int $x, int $z) : Biome{
|
public function getBiome(int $x, int $y, int $z) : Biome{
|
||||||
return BiomeRegistry::getInstance()->getBiome($this->getBiomeId($x, $z));
|
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;
|
$chunkX = $x >> Chunk::COORD_BIT_SIZE;
|
||||||
$chunkZ = $z >> Chunk::COORD_BIT_SIZE;
|
$chunkZ = $z >> Chunk::COORD_BIT_SIZE;
|
||||||
$this->unlockChunk($chunkX, $chunkZ, null);
|
$this->unlockChunk($chunkX, $chunkZ, null);
|
||||||
if(($chunk = $this->loadChunk($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{
|
}else{
|
||||||
//if we allowed this, the modifications would be lost when the chunk is created
|
//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");
|
throw new WorldException("Cannot set biome in a non-generated chunk");
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* ____ _ _ __ __ _ __ __ ____
|
|
||||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
|
||||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
|
||||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
|
||||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* @author PocketMine Team
|
|
||||||
* @link http://www.pocketmine.net/
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace pocketmine\world\format;
|
|
||||||
|
|
||||||
use function chr;
|
|
||||||
use function ord;
|
|
||||||
use function str_repeat;
|
|
||||||
use function strlen;
|
|
||||||
|
|
||||||
final class BiomeArray{
|
|
||||||
private string $payload;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $payload ZZZZXXXX key bits
|
|
||||||
*/
|
|
||||||
public function __construct(string $payload){
|
|
||||||
if(strlen($payload) !== 256){
|
|
||||||
throw new \InvalidArgumentException("Biome array is expected to be exactly 256 bytes");
|
|
||||||
}
|
|
||||||
$this->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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,6 +29,7 @@ namespace pocketmine\world\format;
|
|||||||
use pocketmine\block\Block;
|
use pocketmine\block\Block;
|
||||||
use pocketmine\block\BlockTypeIds;
|
use pocketmine\block\BlockTypeIds;
|
||||||
use pocketmine\block\tile\Tile;
|
use pocketmine\block\tile\Tile;
|
||||||
|
use pocketmine\data\bedrock\BiomeIds;
|
||||||
use function array_map;
|
use function array_map;
|
||||||
|
|
||||||
class Chunk{
|
class Chunk{
|
||||||
@ -59,21 +60,19 @@ class Chunk{
|
|||||||
|
|
||||||
protected HeightArray $heightMap;
|
protected HeightArray $heightMap;
|
||||||
|
|
||||||
protected BiomeArray $biomeIds;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param SubChunk[] $subChunks
|
* @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);
|
$this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS);
|
||||||
|
|
||||||
foreach($this->subChunks as $y => $null){
|
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;
|
$val = (self::MAX_SUBCHUNK_INDEX + 1) * SubChunk::EDGE_LENGTH;
|
||||||
$this->heightMap = HeightArray::fill($val); //TODO: what about lazily initializing this?
|
$this->heightMap = HeightArray::fill($val); //TODO: what about lazily initializing this?
|
||||||
$this->biomeIds = $biomeIds;
|
|
||||||
|
|
||||||
$this->terrainPopulated = $terrainPopulated;
|
$this->terrainPopulated = $terrainPopulated;
|
||||||
}
|
}
|
||||||
@ -153,8 +152,8 @@ class Chunk{
|
|||||||
*
|
*
|
||||||
* @return int 0-255
|
* @return int 0-255
|
||||||
*/
|
*/
|
||||||
public function getBiomeId(int $x, int $z) : int{
|
public function getBiomeId(int $x, int $y, int $z) : int{
|
||||||
return $this->biomeIds->get($x, $z);
|
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 $z 0-15
|
||||||
* @param int $biomeId 0-255
|
* @param int $biomeId 0-255
|
||||||
*/
|
*/
|
||||||
public function setBiomeId(int $x, int $z, int $biomeId) : void{
|
public function setBiomeId(int $x, int $y, int $z, int $biomeId) : void{
|
||||||
$this->biomeIds->set($x, $z, $biomeId);
|
$this->getSubChunk($y >> SubChunk::COORD_BIT_SIZE)->getBiomeArray()->set($x, $y, $z, $biomeId);
|
||||||
$this->terrainDirtyFlags |= self::DIRTY_FLAG_BIOMES;
|
$this->terrainDirtyFlags |= self::DIRTY_FLAG_BIOMES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,10 +229,6 @@ class Chunk{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBiomeIdArray() : string{
|
|
||||||
return $this->biomeIds->getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int[]
|
* @return int[]
|
||||||
*/
|
*/
|
||||||
@ -291,7 +286,7 @@ class Chunk{
|
|||||||
throw new \InvalidArgumentException("Invalid subchunk Y coordinate $y");
|
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);
|
$this->setTerrainDirtyFlag(self::DIRTY_FLAG_BLOCKS, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,7 +317,6 @@ class Chunk{
|
|||||||
return clone $subChunk;
|
return clone $subChunk;
|
||||||
}, $this->subChunks->toArray()));
|
}, $this->subChunks->toArray()));
|
||||||
$this->heightMap = clone $this->heightMap;
|
$this->heightMap = clone $this->heightMap;
|
||||||
$this->biomeIds = clone $this->biomeIds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,6 +40,7 @@ class SubChunk{
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private int $emptyBlockId,
|
private int $emptyBlockId,
|
||||||
private array $blockLayers,
|
private array $blockLayers,
|
||||||
|
private PalettedBlockArray $biomes,
|
||||||
private ?LightArray $skyLight = null,
|
private ?LightArray $skyLight = null,
|
||||||
private ?LightArray $blockLight = null
|
private ?LightArray $blockLight = null
|
||||||
){}
|
){}
|
||||||
@ -102,6 +103,8 @@ class SubChunk{
|
|||||||
return null; //highest block not in this subchunk
|
return null; //highest block not in this subchunk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getBiomeArray() : PalettedBlockArray{ return $this->biomes; }
|
||||||
|
|
||||||
public function getBlockSkyLightArray() : LightArray{
|
public function getBlockSkyLightArray() : LightArray{
|
||||||
return $this->skyLight ??= LightArray::fill(0);
|
return $this->skyLight ??= LightArray::fill(0);
|
||||||
}
|
}
|
||||||
@ -137,6 +140,7 @@ class SubChunk{
|
|||||||
unset($this->blockLayers[$k]);
|
unset($this->blockLayers[$k]);
|
||||||
}
|
}
|
||||||
$this->blockLayers = array_values($this->blockLayers);
|
$this->blockLayers = array_values($this->blockLayers);
|
||||||
|
$this->biomes->collectGarbage();
|
||||||
|
|
||||||
if($this->skyLight !== null && $this->skyLight->isUniform(0)){
|
if($this->skyLight !== null && $this->skyLight->isUniform(0)){
|
||||||
$this->skyLight = null;
|
$this->skyLight = null;
|
||||||
@ -150,6 +154,7 @@ class SubChunk{
|
|||||||
$this->blockLayers = array_map(function(PalettedBlockArray $array) : PalettedBlockArray{
|
$this->blockLayers = array_map(function(PalettedBlockArray $array) : PalettedBlockArray{
|
||||||
return clone $array;
|
return clone $array;
|
||||||
}, $this->blockLayers);
|
}, $this->blockLayers);
|
||||||
|
$this->biomes = clone $this->biomes;
|
||||||
|
|
||||||
if($this->skyLight !== null){
|
if($this->skyLight !== null){
|
||||||
$this->skyLight = clone $this->skyLight;
|
$this->skyLight = clone $this->skyLight;
|
||||||
|
@ -23,8 +23,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\world\format\io;
|
namespace pocketmine\world\format\io;
|
||||||
|
|
||||||
|
use pocketmine\world\format\PalettedBlockArray;
|
||||||
use function chr;
|
use function chr;
|
||||||
|
use function ord;
|
||||||
use function str_repeat;
|
use function str_repeat;
|
||||||
|
use function strlen;
|
||||||
|
|
||||||
class ChunkUtils{
|
class ChunkUtils{
|
||||||
|
|
||||||
@ -42,4 +45,23 @@ class ChunkUtils{
|
|||||||
return $result;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ namespace pocketmine\world\format\io;
|
|||||||
|
|
||||||
use pocketmine\utils\Binary;
|
use pocketmine\utils\Binary;
|
||||||
use pocketmine\utils\BinaryStream;
|
use pocketmine\utils\BinaryStream;
|
||||||
use pocketmine\world\format\BiomeArray;
|
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\PalettedBlockArray;
|
use pocketmine\world\format\PalettedBlockArray;
|
||||||
use pocketmine\world\format\SubChunk;
|
use pocketmine\world\format\SubChunk;
|
||||||
@ -46,6 +45,17 @@ final class FastChunkSerializer{
|
|||||||
//NOOP
|
//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
|
* Fast-serializes the chunk for passing between threads
|
||||||
* TODO: tiles and entities
|
* TODO: tiles and entities
|
||||||
@ -67,23 +77,25 @@ final class FastChunkSerializer{
|
|||||||
$layers = $subChunk->getBlockLayers();
|
$layers = $subChunk->getBlockLayers();
|
||||||
$stream->putByte(count($layers));
|
$stream->putByte(count($layers));
|
||||||
foreach($layers as $blocks){
|
foreach($layers as $blocks){
|
||||||
$wordArray = $blocks->getWordArray();
|
self::serializePalettedArray($stream, $blocks);
|
||||||
$palette = $blocks->getPalette();
|
|
||||||
|
|
||||||
$stream->putByte($blocks->getBitsPerBlock());
|
|
||||||
$stream->put($wordArray);
|
|
||||||
$serialPalette = pack("L*", ...$palette);
|
|
||||||
$stream->putInt(strlen($serialPalette));
|
|
||||||
$stream->put($serialPalette);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
self::serializePalettedArray($stream, $subChunk->getBiomeArray());
|
||||||
|
|
||||||
//biomes
|
}
|
||||||
$stream->put($chunk->getBiomeIdArray());
|
|
||||||
|
|
||||||
return $stream->getBuffer();
|
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
|
* Deserializes a fast-serialized chunk
|
||||||
*/
|
*/
|
||||||
@ -103,19 +115,12 @@ final class FastChunkSerializer{
|
|||||||
/** @var PalettedBlockArray[] $layers */
|
/** @var PalettedBlockArray[] $layers */
|
||||||
$layers = [];
|
$layers = [];
|
||||||
for($i = 0, $layerCount = $stream->getByte(); $i < $layerCount; ++$i){
|
for($i = 0, $layerCount = $stream->getByte(); $i < $layerCount; ++$i){
|
||||||
$bitsPerBlock = $stream->getByte();
|
$layers[] = self::deserializePalettedArray($stream);
|
||||||
$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);
|
|
||||||
}
|
}
|
||||||
$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, $terrainPopulated);
|
||||||
|
|
||||||
return new Chunk($subChunks, $biomeIds, $terrainPopulated);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ use pocketmine\nbt\TreeRoot;
|
|||||||
use pocketmine\utils\Binary;
|
use pocketmine\utils\Binary;
|
||||||
use pocketmine\utils\BinaryDataException;
|
use pocketmine\utils\BinaryDataException;
|
||||||
use pocketmine\utils\BinaryStream;
|
use pocketmine\utils\BinaryStream;
|
||||||
use pocketmine\world\format\BiomeArray;
|
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\io\BaseWorldProvider;
|
use pocketmine\world\format\io\BaseWorldProvider;
|
||||||
use pocketmine\world\format\io\ChunkData;
|
use pocketmine\world\format\io\ChunkData;
|
||||||
@ -145,7 +144,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
|||||||
/**
|
/**
|
||||||
* @throws CorruptedChunkException
|
* @throws CorruptedChunkException
|
||||||
*/
|
*/
|
||||||
protected function deserializePaletted(BinaryStream $stream) : PalettedBlockArray{
|
protected function deserializeBlockPalette(BinaryStream $stream) : PalettedBlockArray{
|
||||||
$bitsPerBlock = $stream->getByte() >> 1;
|
$bitsPerBlock = $stream->getByte() >> 1;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
@ -188,6 +187,99 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
|||||||
return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette);
|
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<int, PalettedBlockArray>
|
||||||
|
*/
|
||||||
|
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 $x
|
||||||
* @phpstan-param-out int $y
|
* @phpstan-param-out int $y
|
||||||
@ -280,9 +372,6 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
|||||||
/** @var SubChunk[] $subChunks */
|
/** @var SubChunk[] $subChunks */
|
||||||
$subChunks = [];
|
$subChunks = [];
|
||||||
|
|
||||||
/** @var BiomeArray|null $biomeArray */
|
|
||||||
$biomeArray = null;
|
|
||||||
|
|
||||||
$hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION;
|
$hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION;
|
||||||
|
|
||||||
$subChunkKeyOffset = self::hasOffsetCavesAndCliffsSubChunks($chunkVersion) ? self::CAVES_CLIFFS_EXPERIMENTAL_SUBCHUNK_KEY_OFFSET : 0;
|
$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:
|
case ChunkVersion::v1_0_0:
|
||||||
$convertedLegacyExtraData = $this->deserializeLegacyExtraData($index, $chunkVersion);
|
$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){
|
for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
|
||||||
if(($data = $this->db->get($index . ChunkDataKey::SUBCHUNK . chr($y + $subChunkKeyOffset))) === false){
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,14 +487,14 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
|||||||
$storages[] = $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;
|
break;
|
||||||
case SubChunkVersion::PALETTED_SINGLE:
|
case SubChunkVersion::PALETTED_SINGLE:
|
||||||
$storages = [$this->deserializePaletted($binaryStream)];
|
$storages = [$this->deserializeBlockPalette($binaryStream)];
|
||||||
if(isset($convertedLegacyExtraData[$y])){
|
if(isset($convertedLegacyExtraData[$y])){
|
||||||
$storages[] = $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;
|
break;
|
||||||
case SubChunkVersion::PALETTED_MULTI:
|
case SubChunkVersion::PALETTED_MULTI:
|
||||||
case SubChunkVersion::PALETTED_MULTI_WITH_OFFSET:
|
case SubChunkVersion::PALETTED_MULTI_WITH_OFFSET:
|
||||||
@ -390,9 +508,9 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
|||||||
$storages = [];
|
$storages = [];
|
||||||
|
|
||||||
for($k = 0; $k < $storageCount; ++$k){
|
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;
|
break;
|
||||||
default:
|
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;
|
break;
|
||||||
case ChunkVersion::v0_9_5:
|
case ChunkVersion::v0_9_5:
|
||||||
case ChunkVersion::v0_9_2:
|
case ChunkVersion::v0_9_2:
|
||||||
@ -430,22 +538,30 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
|||||||
throw new CorruptedChunkException($e->getMessage(), 0, $e);
|
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){
|
for($yy = 0; $yy < 8; ++$yy){
|
||||||
$storages = [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $yy)];
|
$storages = [$this->palettizeLegacySubChunkFromColumn($fullIds, $fullData, $yy)];
|
||||||
if(isset($convertedLegacyExtraData[$yy])){
|
if(isset($convertedLegacyExtraData[$yy])){
|
||||||
$storages[] = $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{
|
//make sure extrapolated biomes get filled in correctly
|
||||||
$binaryStream->get(256); //heightmap, discard it
|
for($yy = Chunk::MIN_SUBCHUNK_INDEX; $yy <= Chunk::MAX_SUBCHUNK_INDEX; ++$yy){
|
||||||
/** @var int[] $unpackedBiomeArray */
|
if(!isset($subChunks[$yy])){
|
||||||
$unpackedBiomeArray = unpack("N*", $binaryStream->get(1024)); //unpack() will never fail here
|
$subChunks[$yy] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [], clone $biomes3d);
|
||||||
$biomeArray = new BiomeArray(ChunkUtils::convertBiomeColors(array_values($unpackedBiomeArray))); //never throws
|
|
||||||
}catch(BinaryDataException $e){
|
|
||||||
throw new CorruptedChunkException($e->getMessage(), 0, $e);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
//TODO: set chunks read-only so the version on disk doesn't get overwritten
|
//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 (?)
|
//TODO: tile ticks, biome states (?)
|
||||||
|
|
||||||
$chunk = new Chunk(
|
$chunk = new Chunk(
|
||||||
$subChunks,
|
$subChunks, //TODO: maybe missing biomes should be an error?
|
||||||
$biomeArray ?? BiomeArray::fill(BiomeIds::OCEAN), //TODO: maybe missing biomes should be an error?
|
|
||||||
$terrainPopulated
|
$terrainPopulated
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -545,7 +660,11 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BIOMES)){
|
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
|
//TODO: use this properly
|
||||||
|
@ -26,16 +26,17 @@ namespace pocketmine\world\format\io\region;
|
|||||||
use pocketmine\block\Block;
|
use pocketmine\block\Block;
|
||||||
use pocketmine\block\BlockTypeIds;
|
use pocketmine\block\BlockTypeIds;
|
||||||
use pocketmine\nbt\tag\CompoundTag;
|
use pocketmine\nbt\tag\CompoundTag;
|
||||||
|
use pocketmine\world\format\PalettedBlockArray;
|
||||||
use pocketmine\world\format\SubChunk;
|
use pocketmine\world\format\SubChunk;
|
||||||
|
|
||||||
class Anvil extends RegionWorldProvider{
|
class Anvil extends RegionWorldProvider{
|
||||||
use LegacyAnvilChunkTrait;
|
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(
|
return new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [$this->palettizeLegacySubChunkYZX(
|
||||||
self::readFixedSizeByteArray($subChunk, "Blocks", 4096),
|
self::readFixedSizeByteArray($subChunk, "Blocks", 4096),
|
||||||
self::readFixedSizeByteArray($subChunk, "Data", 2048)
|
self::readFixedSizeByteArray($subChunk, "Data", 2048)
|
||||||
)]);
|
)], $biomes3d);
|
||||||
//ignore legacy light information
|
//ignore legacy light information
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\world\format\io\region;
|
namespace pocketmine\world\format\io\region;
|
||||||
|
|
||||||
|
use pocketmine\block\Block;
|
||||||
|
use pocketmine\block\BlockTypeIds;
|
||||||
use pocketmine\data\bedrock\BiomeIds;
|
use pocketmine\data\bedrock\BiomeIds;
|
||||||
use pocketmine\nbt\BigEndianNbtSerializer;
|
use pocketmine\nbt\BigEndianNbtSerializer;
|
||||||
use pocketmine\nbt\NbtDataException;
|
use pocketmine\nbt\NbtDataException;
|
||||||
@ -30,12 +32,13 @@ use pocketmine\nbt\tag\ByteArrayTag;
|
|||||||
use pocketmine\nbt\tag\CompoundTag;
|
use pocketmine\nbt\tag\CompoundTag;
|
||||||
use pocketmine\nbt\tag\IntArrayTag;
|
use pocketmine\nbt\tag\IntArrayTag;
|
||||||
use pocketmine\nbt\tag\ListTag;
|
use pocketmine\nbt\tag\ListTag;
|
||||||
use pocketmine\world\format\BiomeArray;
|
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\io\ChunkData;
|
use pocketmine\world\format\io\ChunkData;
|
||||||
use pocketmine\world\format\io\ChunkUtils;
|
use pocketmine\world\format\io\ChunkUtils;
|
||||||
use pocketmine\world\format\io\exception\CorruptedChunkException;
|
use pocketmine\world\format\io\exception\CorruptedChunkException;
|
||||||
|
use pocketmine\world\format\PalettedBlockArray;
|
||||||
use pocketmine\world\format\SubChunk;
|
use pocketmine\world\format\SubChunk;
|
||||||
|
use function strlen;
|
||||||
use function zlib_decode;
|
use function zlib_decode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,34 +70,38 @@ trait LegacyAnvilChunkTrait{
|
|||||||
throw new CorruptedChunkException("'Level' key is missing from chunk NBT");
|
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 = [];
|
$subChunks = [];
|
||||||
$subChunksTag = $chunk->getListTag("Sections") ?? [];
|
$subChunksTag = $chunk->getListTag("Sections") ?? [];
|
||||||
foreach($subChunksTag as $subChunk){
|
foreach($subChunksTag as $subChunk){
|
||||||
if($subChunk instanceof CompoundTag){
|
if($subChunk instanceof CompoundTag){
|
||||||
$subChunks[$subChunk->getByte("Y")] = $this->deserializeSubChunk($subChunk);
|
$subChunks[$subChunk->getByte("Y")] = $this->deserializeSubChunk($subChunk, clone $biomes3d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
|
||||||
$makeBiomeArray = function(string $biomeIds) : BiomeArray{
|
if(!isset($subChunks[$y])){
|
||||||
try{
|
$subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [], clone $biomes3d);
|
||||||
return new BiomeArray($biomeIds);
|
|
||||||
}catch(\InvalidArgumentException $e){
|
|
||||||
throw new CorruptedChunkException($e->getMessage(), 0, $e);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
$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(
|
return new ChunkData(
|
||||||
new Chunk(
|
new Chunk(
|
||||||
$subChunks,
|
$subChunks,
|
||||||
$biomeArray,
|
|
||||||
$chunk->getByte("TerrainPopulated", 0) !== 0
|
$chunk->getByte("TerrainPopulated", 0) !== 0
|
||||||
),
|
),
|
||||||
($entitiesTag = $chunk->getTag("Entities")) instanceof ListTag ? self::getCompoundList("Entities", $entitiesTag) : [],
|
($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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,12 +33,13 @@ use pocketmine\nbt\tag\ByteTag;
|
|||||||
use pocketmine\nbt\tag\CompoundTag;
|
use pocketmine\nbt\tag\CompoundTag;
|
||||||
use pocketmine\nbt\tag\IntArrayTag;
|
use pocketmine\nbt\tag\IntArrayTag;
|
||||||
use pocketmine\nbt\tag\ListTag;
|
use pocketmine\nbt\tag\ListTag;
|
||||||
use pocketmine\world\format\BiomeArray;
|
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\io\ChunkData;
|
use pocketmine\world\format\io\ChunkData;
|
||||||
use pocketmine\world\format\io\ChunkUtils;
|
use pocketmine\world\format\io\ChunkUtils;
|
||||||
use pocketmine\world\format\io\exception\CorruptedChunkException;
|
use pocketmine\world\format\io\exception\CorruptedChunkException;
|
||||||
|
use pocketmine\world\format\PalettedBlockArray;
|
||||||
use pocketmine\world\format\SubChunk;
|
use pocketmine\world\format\SubChunk;
|
||||||
|
use function strlen;
|
||||||
use function zlib_decode;
|
use function zlib_decode;
|
||||||
|
|
||||||
class McRegion extends RegionWorldProvider{
|
class McRegion extends RegionWorldProvider{
|
||||||
@ -69,34 +70,37 @@ class McRegion extends RegionWorldProvider{
|
|||||||
//trying to read it.
|
//trying to read it.
|
||||||
return null;
|
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 = [];
|
$subChunks = [];
|
||||||
$fullIds = self::readFixedSizeByteArray($chunk, "Blocks", 32768);
|
$fullIds = self::readFixedSizeByteArray($chunk, "Blocks", 32768);
|
||||||
$fullData = self::readFixedSizeByteArray($chunk, "Data", 16384);
|
$fullData = self::readFixedSizeByteArray($chunk, "Data", 16384);
|
||||||
|
|
||||||
for($y = 0; $y < 8; ++$y){
|
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);
|
||||||
}
|
}
|
||||||
|
for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
|
||||||
$makeBiomeArray = function(string $biomeIds) : BiomeArray{
|
if(!isset($subChunks[$y])){
|
||||||
try{
|
$subChunks[$y] = new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [], clone $biomes3d);
|
||||||
return new BiomeArray($biomeIds);
|
|
||||||
}catch(\InvalidArgumentException $e){
|
|
||||||
throw new CorruptedChunkException($e->getMessage(), 0, $e);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
$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(
|
return new ChunkData(
|
||||||
new Chunk(
|
new Chunk(
|
||||||
$subChunks,
|
$subChunks,
|
||||||
$biomeIds,
|
|
||||||
$chunk->getByte("TerrainPopulated", 0) !== 0
|
$chunk->getByte("TerrainPopulated", 0) !== 0
|
||||||
),
|
),
|
||||||
($entitiesTag = $chunk->getTag("Entities")) instanceof ListTag ? self::getCompoundList("Entities", $entitiesTag) : [],
|
($entitiesTag = $chunk->getTag("Entities")) instanceof ListTag ? self::getCompoundList("Entities", $entitiesTag) : [],
|
||||||
|
@ -26,6 +26,7 @@ namespace pocketmine\world\format\io\region;
|
|||||||
use pocketmine\block\Block;
|
use pocketmine\block\Block;
|
||||||
use pocketmine\block\BlockTypeIds;
|
use pocketmine\block\BlockTypeIds;
|
||||||
use pocketmine\nbt\tag\CompoundTag;
|
use pocketmine\nbt\tag\CompoundTag;
|
||||||
|
use pocketmine\world\format\PalettedBlockArray;
|
||||||
use pocketmine\world\format\SubChunk;
|
use pocketmine\world\format\SubChunk;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,11 +36,11 @@ use pocketmine\world\format\SubChunk;
|
|||||||
class PMAnvil extends RegionWorldProvider{
|
class PMAnvil extends RegionWorldProvider{
|
||||||
use LegacyAnvilChunkTrait;
|
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(
|
return new SubChunk(BlockTypeIds::AIR << Block::INTERNAL_STATE_DATA_BITS, [$this->palettizeLegacySubChunkXZY(
|
||||||
self::readFixedSizeByteArray($subChunk, "Blocks", 4096),
|
self::readFixedSizeByteArray($subChunk, "Blocks", 4096),
|
||||||
self::readFixedSizeByteArray($subChunk, "Data", 2048)
|
self::readFixedSizeByteArray($subChunk, "Data", 2048)
|
||||||
)]);
|
)], $biomes3d);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function getRegionFileExtension() : string{
|
protected static function getRegionFileExtension() : string{
|
||||||
|
@ -25,7 +25,6 @@ namespace pocketmine\world\generator;
|
|||||||
|
|
||||||
use pocketmine\block\VanillaBlocks;
|
use pocketmine\block\VanillaBlocks;
|
||||||
use pocketmine\world\ChunkManager;
|
use pocketmine\world\ChunkManager;
|
||||||
use pocketmine\world\format\BiomeArray;
|
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\SubChunk;
|
use pocketmine\world\format\SubChunk;
|
||||||
use pocketmine\world\generator\object\OreType;
|
use pocketmine\world\generator\object\OreType;
|
||||||
@ -67,7 +66,7 @@ class Flat extends Generator{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function generateBaseChunk() : void{
|
protected function generateBaseChunk() : void{
|
||||||
$this->chunk = new Chunk([], BiomeArray::fill($this->options->getBiomeId()), false);
|
$this->chunk = new Chunk([], false);
|
||||||
|
|
||||||
$structure = $this->options->getStructure();
|
$structure = $this->options->getStructure();
|
||||||
$count = count($structure);
|
$count = count($structure);
|
||||||
|
@ -23,10 +23,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\world\generator;
|
namespace pocketmine\world\generator;
|
||||||
|
|
||||||
use pocketmine\data\bedrock\BiomeIds;
|
|
||||||
use pocketmine\scheduler\AsyncTask;
|
use pocketmine\scheduler\AsyncTask;
|
||||||
use pocketmine\utils\AssumptionFailedError;
|
use pocketmine\utils\AssumptionFailedError;
|
||||||
use pocketmine\world\format\BiomeArray;
|
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\io\FastChunkSerializer;
|
use pocketmine\world\format\io\FastChunkSerializer;
|
||||||
use pocketmine\world\SimpleChunkManager;
|
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{
|
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){
|
if($chunk === null){
|
||||||
$generator->generateChunk($manager, $chunkX, $chunkZ);
|
$generator->generateChunk($manager, $chunkX, $chunkZ);
|
||||||
$chunk = $manager->getChunk($chunkX, $chunkZ);
|
$chunk = $manager->getChunk($chunkX, $chunkZ);
|
||||||
|
@ -34,6 +34,7 @@ use pocketmine\world\generator\noise\Simplex;
|
|||||||
use pocketmine\world\generator\object\OreType;
|
use pocketmine\world\generator\object\OreType;
|
||||||
use pocketmine\world\generator\populator\Ore;
|
use pocketmine\world\generator\populator\Ore;
|
||||||
use pocketmine\world\generator\populator\Populator;
|
use pocketmine\world\generator\populator\Populator;
|
||||||
|
use pocketmine\world\World;
|
||||||
use function abs;
|
use function abs;
|
||||||
|
|
||||||
class Nether extends Generator{
|
class Nether extends Generator{
|
||||||
@ -78,7 +79,9 @@ class Nether extends Generator{
|
|||||||
|
|
||||||
for($x = 0; $x < Chunk::EDGE_LENGTH; ++$x){
|
for($x = 0; $x < Chunk::EDGE_LENGTH; ++$x){
|
||||||
for($z = 0; $z < Chunk::EDGE_LENGTH; ++$z){
|
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){
|
for($y = 0; $y < 128; ++$y){
|
||||||
if($y === 0 || $y === 127){
|
if($y === 0 || $y === 127){
|
||||||
@ -109,7 +112,7 @@ class Nether extends Generator{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$chunk = $world->getChunk($chunkX, $chunkZ);
|
$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);
|
$biome->populateChunk($world, $chunkX, $chunkZ, $this->random);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,9 @@ class Normal extends Generator{
|
|||||||
$weightSum = 0;
|
$weightSum = 0;
|
||||||
|
|
||||||
$biome = $this->pickBiome($absoluteX, $absoluteZ);
|
$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($sx = -$this->gaussian->smoothSize; $sx <= $this->gaussian->smoothSize; ++$sx){
|
||||||
for($sz = -$this->gaussian->smoothSize; $sz <= $this->gaussian->smoothSize; ++$sz){
|
for($sz = -$this->gaussian->smoothSize; $sz <= $this->gaussian->smoothSize; ++$sz){
|
||||||
@ -218,7 +220,7 @@ class Normal extends Generator{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$chunk = $world->getChunk($chunkX, $chunkZ);
|
$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);
|
$biome->populateChunk($world, $chunkX, $chunkZ, $this->random);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ class GroundCover implements Populator{
|
|||||||
$biomeRegistry = BiomeRegistry::getInstance();
|
$biomeRegistry = BiomeRegistry::getInstance();
|
||||||
for($x = 0; $x < Chunk::EDGE_LENGTH; ++$x){
|
for($x = 0; $x < Chunk::EDGE_LENGTH; ++$x){
|
||||||
for($z = 0; $z < Chunk::EDGE_LENGTH; ++$z){
|
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();
|
$cover = $biome->getGroundCover();
|
||||||
if(count($cover) > 0){
|
if(count($cover) > 0){
|
||||||
$diffY = 0;
|
$diffY = 0;
|
||||||
|
@ -24,23 +24,22 @@ declare(strict_types=1);
|
|||||||
namespace pocketmine\world\format;
|
namespace pocketmine\world\format;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use pocketmine\data\bedrock\BiomeIds;
|
|
||||||
|
|
||||||
class ChunkTest extends TestCase{
|
class ChunkTest extends TestCase{
|
||||||
|
|
||||||
public function testClone() : void{
|
public function testClone() : void{
|
||||||
$chunk = new Chunk([], BiomeArray::fill(BiomeIds::OCEAN), false);
|
$chunk = new Chunk([], false);
|
||||||
$chunk->setFullBlock(0, 0, 0, 1);
|
$chunk->setFullBlock(0, 0, 0, 1);
|
||||||
$chunk->setBiomeId(0, 0, 1);
|
$chunk->setBiomeId(0, 0, 0, 1);
|
||||||
$chunk->setHeightMap(0, 0, 1);
|
$chunk->setHeightMap(0, 0, 1);
|
||||||
|
|
||||||
$chunk2 = clone $chunk;
|
$chunk2 = clone $chunk;
|
||||||
$chunk2->setFullBlock(0, 0, 0, 2);
|
$chunk2->setFullBlock(0, 0, 0, 2);
|
||||||
$chunk2->setBiomeId(0, 0, 2);
|
$chunk2->setBiomeId(0, 0, 0, 2);
|
||||||
$chunk2->setHeightMap(0, 0, 2);
|
$chunk2->setHeightMap(0, 0, 2);
|
||||||
|
|
||||||
self::assertNotSame($chunk->getFullBlock(0, 0, 0), $chunk2->getFullBlock(0, 0, 0));
|
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));
|
self::assertNotSame($chunk->getHeightMap(0, 0), $chunk2->getHeightMap(0, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
|||||||
namespace pocketmine\world\format;
|
namespace pocketmine\world\format;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use pocketmine\data\bedrock\BiomeIds;
|
||||||
|
|
||||||
class SubChunkTest extends TestCase{
|
class SubChunkTest extends TestCase{
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ class SubChunkTest extends TestCase{
|
|||||||
* Test that a cloned SubChunk instance doesn't influence the original
|
* Test that a cloned SubChunk instance doesn't influence the original
|
||||||
*/
|
*/
|
||||||
public function testClone() : void{
|
public function testClone() : void{
|
||||||
$sub1 = new SubChunk(0, []);
|
$sub1 = new SubChunk(0, [], new PalettedBlockArray(BiomeIds::OCEAN));
|
||||||
|
|
||||||
$sub1->setFullBlock(0, 0, 0, 1);
|
$sub1->setFullBlock(0, 0, 0, 1);
|
||||||
$sub1->getBlockLightArray()->set(0, 0, 0, 1);
|
$sub1->getBlockLightArray()->set(0, 0, 0, 1);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user