diff --git a/src/pocketmine/level/format/Chunk.php b/src/pocketmine/level/format/Chunk.php index f55bb9862..a9ec75a0d 100644 --- a/src/pocketmine/level/format/Chunk.php +++ b/src/pocketmine/level/format/Chunk.php @@ -39,6 +39,8 @@ use pocketmine\utils\ChunkException; class Chunk{ + const MAX_SUBCHUNKS = 16; + /** @var LevelProvider */ protected $provider; @@ -53,7 +55,7 @@ class Chunk{ protected $terrainGenerated = false; protected $terrainPopulated = false; - protected $height = 16;//Chunk::MAX_SUBCHUNKS; + protected $height = Chunk::MAX_SUBCHUNKS; /** @var SubChunk[] */ protected $subChunks = []; @@ -137,10 +139,16 @@ class Chunk{ $this->NBTentities = $entities; } + /** + * @return int + */ public function getX() : int{ return $this->x; } + /** + * @return int + */ public function getZ() : int{ return $this->z; } @@ -149,26 +157,60 @@ class Chunk{ $this->x = $x; } + /** + * @param int $z + */ public function setZ(int $z){ $this->z = $z; } + /** + * @return LevelProvider|null + */ public function getProvider(){ return $this->provider; } + /** + * @param LevelProvider $provider + */ public function setProvider(LevelProvider $provider){ $this->provider = $provider; } + /** + * Returns the chunk height in count of subchunks. + * + * @return int + */ public function getHeight() : int{ return $this->height; } + /** + * Returns a bitmap of block ID and meta at the specified chunk block coordinates + * + * @param int $x 0-15 + * @param int $y + * @param int $z 0-15 + * + * @return int bitmap, (id << 4) | meta + */ public function getFullBlock(int $x, int $y, int $z) : int{ return $this->getSubChunk($y >> 4)->getFullBlock($x, $y & 0x0f, $z); } + /** + * Sets block ID and meta in one call at the specified chunk block coordinates + * + * @param int $x 0-15 + * @param int $y + * @param int $z 0-15 + * @param int|null $blockId 0-255 if null, does not change + * @param int|null $meta 0-15 if null, does not change + * + * @return bool + */ public function setBlock(int $x, int $y, int $z, $blockId = null, $meta = null) : bool{ if($this->getSubChunk($y >> 4, true)->setBlock($x, $y & 0x0f, $z, $blockId !== null ? ($blockId & 0xff) : null, $meta !== null ? ($meta & 0x0f) : null)){ $this->hasChanged = true; @@ -177,30 +219,81 @@ class Chunk{ return false; } + /** + * Returns the block ID at the specified chunk block coordinates + * + * @param int $x 0-15 + * @param int $y + * @param int $z 0-15 + * + * @return int 0-255 + */ public function getBlockId(int $x, int $y, int $z) : int{ return $this->getSubChunk($y >> 4)->getBlockId($x, $y & 0x0f, $z); } + /** + * Sets the block ID at the specified chunk block coordinates + * + * @param int $x 0-15 + * @param int $y + * @param int $z 0-15 + * @param int $id 0-255 + */ public function setBlockId(int $x, int $y, int $z, int $id){ if($this->getSubChunk($y >> 4, true)->setBlockId($x, $y & 0x0f, $z, $id)){ $this->hasChanged = true; } } + /** + * Returns the block meta value at the specified chunk block coordinates + * + * @param int $x 0-15 + * @param int $y + * @param int $z 0-15 + * + * @return int 0-15 + */ public function getBlockData(int $x, int $y, int $z) : int{ return $this->getSubChunk($y >> 4)->getBlockData($x, $y & 0x0f, $z); } + /** + * Sets the block meta value at the specified chunk block coordinates + * + * @param int $x 0-15 + * @param int $y + * @param int $z 0-15 + * @param int $data 0-15 + */ public function setBlockData(int $x, int $y, int $z, int $data){ if($this->getSubChunk($y >> 4)->setBlockData($x, $y & 0x0f, $z, $data)){ $this->hasChanged = true; } } + /** + * Returns the raw block extra data value at the specified chunk block coordinates, or 0 if no data exists + * + * @param int $x 0-15 + * @param int $y + * @param int $z 0-15 + * + * @return int bitmap, (meta << 8) | id + */ public function getBlockExtraData(int $x, int $y, int $z) : int{ return $this->extraData[Chunk::chunkBlockHash($x, $y, $z)] ?? 0; } + /** + * Sets the raw block extra data value at the specified chunk block coordinates + * + * @param int $x 0-15 + * @param int $y + * @param int $z 0-15 + * @param int $data bitmap, (meta << 8) | id + */ public function setBlockExtraData(int $x, int $y, int $z, int $data){ if($data === 0){ unset($this->extraData[Chunk::chunkBlockHash($x, $y, $z)]); @@ -211,26 +304,69 @@ class Chunk{ $this->hasChanged = true; } + /** + * Returns the sky light level at the specified chunk block coordinates + * + * @param int $x 0-15 + * @param int $y + * @param int $z 0-15 + * + * @return int 0-15 + */ public function getBlockSkyLight(int $x, int $y, int $z) : int{ return $this->getSubChunk($y >> 4)->getBlockSkyLight($x, $y & 0x0f, $z); } + /** + * Sets the sky light level at the specified chunk block coordinates + * + * @param int $x 0-15 + * @param int $y + * @param int $z 0-15 + * @param int $level 0-15 + */ public function setBlockSkyLight(int $x, int $y, int $z, int $level){ if($this->getSubChunk($y >> 4)->setBlockSkyLight($x, $y & 0x0f, $z, $level)){ $this->hasChanged = true; } } + /** + * Returns the block light level at the specified chunk block coordinates + * + * @param int $x 0-15 + * @param int $y 0-15 + * @param int $z 0-15 + * + * @return int 0-15 + */ public function getBlockLight(int $x, int $y, int $z) : int{ return $this->getSubChunk($y >> 4)->getBlockLight($x, $y & 0x0f, $z); } + /** + * Sets the block light level at the specified chunk block coordinates + * + * @param int $x 0-15 + * @param int $y 0-15 + * @param int $z 0-15 + * @param int $level 0-15 + */ public function setBlockLight(int $x, int $y, int $z, int $level){ if($this->getSubChunk($y >> 4)->setBlockLight($x, $y & 0x0f, $z, $level)){ $this->hasChanged = true; } } + /** + * Returns the Y coordinate of the highest non-air block at the specified X/Z chunk block coordinates + * + * @param int $x 0-15 + * @param int $z 0-15 + * @param bool $useHeightMap whether to use pre-calculated heightmap values or not + * + * @return int + */ public function getHighestBlockAt(int $x, int $z, bool $useHeightMap = true) : int{ if($useHeightMap){ $height = $this->getHeightMap($x, $z); @@ -258,14 +394,31 @@ class Chunk{ return $height; } + /** + * Returns the heightmap value at the specified X/Z chunk block coordinates + * + * @param int $x 0-15 + * @param int $z 0-15 + * + * @return int + */ public function getHeightMap(int $x, int $z) : int{ return $this->heightMap[($z << 4) | $x]; } + /** + * Returns the heightmap value at the specified X/Z chunk block coordinates + * @param int $x 0-15 + * @param int $z 0-15 + * @param int $value + */ public function setHeightMap(int $x, int $z, int $value){ $this->heightMap[($z << 4) | $x] = $value; } + /** + * Recalculates the heightmap for the whole chunk. + */ public function recalculateHeightMap(){ for($z = 0; $z < 16; ++$z){ for($x = 0; $x < 16; ++$x){ @@ -274,8 +427,12 @@ class Chunk{ } } + /** + * Performs basic sky light population on the chunk. + * + * TODO: rewrite this, use block light filters and diffusion, actual proper sky light population + */ public function populateSkyLight(){ - //TODO: rewrite this, use block light filters and diffusion, actual proper sky light population for($x = 0; $x < 16; ++$x){ for($z = 0; $z < 16; ++$z){ $heightMap = $this->getHeightMap($x, $z); @@ -299,15 +456,37 @@ class Chunk{ } } + /** + * Returns the biome ID at the specified X/Z chunk block coordinates + * + * @param int $x 0-15 + * @param int $z 0-15 + * + * @return int 0-255 + */ public function getBiomeId(int $x, int $z) : int{ return ord($this->biomeIds{($z << 4) | $x}); } + /** + * Sets the biome ID at the specified X/Z chunk block coordinates + * + * @param int $x 0-15 + * @param int $z 0-15 + * @param int $biomeId 0-255 + */ public function setBiomeId(int $x, int $z, int $biomeId){ $this->hasChanged = true; $this->biomeIds{($z << 4) | $x} = chr($biomeId & 0xff); } + /** + * Returns a column of block IDs from bottom to top at the specified X/Z chunk block coordinates. + * @param int $x 0-15 + * @param int $z 0-15 + * + * @return string + */ public function getBlockIdColumn(int $x, int $z) : string{ $result = ""; foreach($this->subChunks as $subChunk){ @@ -316,6 +495,13 @@ class Chunk{ return $result; } + /** + * Returns a column of block meta values from bottom to top at the specified X/Z chunk block coordinates. + * @param int $x 0-15 + * @param int $z 0-15 + * + * @return string + */ public function getBlockDataColumn(int $x, int $z) : string{ $result = ""; foreach($this->subChunks as $subChunk){ @@ -324,6 +510,13 @@ class Chunk{ return $result; } + /** + * Returns a column of sky light values from bottom to top at the specified X/Z chunk block coordinates. + * @param int $x 0-15 + * @param int $z 0-15 + * + * @return string + */ public function getBlockSkyLightColumn(int $x, int $z) : string{ $result = ""; foreach($this->subChunks as $subChunk){ @@ -332,6 +525,13 @@ class Chunk{ return $result; } + /** + * Returns a column of block light values from bottom to top at the specified X/Z chunk block coordinates. + * @param int $x 0-15 + * @param int $z 0-15 + * + * @return string + */ public function getBlockLightColumn(int $x, int $z) : string{ $result = ""; foreach($this->subChunks as $subChunk){ @@ -340,30 +540,51 @@ class Chunk{ return $result; } + /** + * @return bool + */ public function isLightPopulated() : bool{ return $this->lightPopulated; } + /** + * @param bool $value + */ public function setLightPopulated(bool $value = true){ $this->lightPopulated = $value; } + /** + * @return bool + */ public function isPopulated() : bool{ return $this->terrainPopulated; } + /** + * @param bool $value + */ public function setPopulated(bool $value = true){ $this->terrainPopulated = $value; } + /** + * @return bool + */ public function isGenerated() : bool{ return $this->terrainGenerated; } + /** + * @param bool $value + */ public function setGenerated(bool $value = true){ $this->terrainGenerated = $value; } + /** + * @param Entity $entity + */ public function addEntity(Entity $entity){ $this->entities[$entity->getId()] = $entity; if(!($entity instanceof Player) and $this->isInit){ @@ -371,6 +592,9 @@ class Chunk{ } } + /** + * @param Entity $entity + */ public function removeEntity(Entity $entity){ unset($this->entities[$entity->getId()]); if(!($entity instanceof Player) and $this->isInit){ @@ -378,6 +602,9 @@ class Chunk{ } } + /** + * @param Tile $tile + */ public function addTile(Tile $tile){ $this->tiles[$tile->getId()] = $tile; if(isset($this->tileList[$index = (($tile->x & 0x0f) << 12) | (($tile->z & 0x0f) << 8) | ($tile->y & 0xff)]) and $this->tileList[$index] !== $tile){ @@ -389,6 +616,9 @@ class Chunk{ } } + /** + * @param Tile $tile + */ public function removeTile(Tile $tile){ unset($this->tiles[$tile->getId()]); unset($this->tileList[(($tile->x & 0x0f) << 12) | (($tile->z & 0x0f) << 8) | ($tile->y & 0xff)]); @@ -397,27 +627,60 @@ class Chunk{ } } + /** + * Returns an array of entities currently using this chunk. + * + * @return Entity[] + */ public function getEntities() : array{ return $this->entities; } + /** + * @return Tile[] + */ public function getTiles() : array{ return $this->tiles; } + /** + * Returns the tile at the specified chunk block coordinates, or null if no tile exists. + * + * @param int $x 0-15 + * @param int $y + * @param int $z 0-15 + * + * @return Tile|null + */ public function getTile(int $x, int $y, int $z){ $index = ($x << 12) | ($z << 8) | $y; return $this->tileList[$index] ?? null; } + /** + * @return bool + */ public function isLoaded() : bool{ return $this->getProvider() === null ? false : $this->getProvider()->isChunkLoaded($this->getX(), $this->getZ()); } + /** + * @param bool $generate + * + * @return bool + */ public function load(bool $generate = true) : bool{ return $this->getProvider() === null ? false : $this->getProvider()->getChunk($this->getX(), $this->getZ(), true) instanceof Chunk; } + /** + * Unloads the chunk, closing entities and tiles. + * + * @param bool $save + * @param bool $safe Whether to check if there are still players using this chunk + * + * @return bool + */ public function unload(bool $save = true, bool $safe = true) : bool{ $level = $this->getProvider(); if($level === null){ @@ -447,6 +710,10 @@ class Chunk{ return true; } + /** + * Deserializes tiles and entities from NBT + * TODO: remove this + */ public function initChunk(){ if($this->getProvider() instanceof LevelProvider and !$this->isInit){ $changed = false; @@ -506,26 +773,49 @@ class Chunk{ } } + /** + * @return string + */ public function getBiomeIdArray() : string{ return $this->biomeIds; } + /** + * @return int[] + */ public function getHeightMapArray() : array{ return $this->heightMap; } + /** + * @return int[] + */ public function getBlockExtraDataArray() : array{ return $this->extraData; } + /** + * @return bool + */ public function hasChanged() : bool{ return $this->hasChanged; } + /** + * @param bool $value + */ public function setChanged(bool $value = true){ $this->hasChanged = $value; } + /** + * Returns the subchunk at the specified subchunk Y coordinate, or an empty, unmodifiable stub if it does not exist or the coordinate is out of range. + * + * @param int $y + * @param bool $generateNew Whether to create a new, modifiable subchunk if there is not one in place + * + * @return SubChunk|EmptySubChunk + */ public function getSubChunk(int $y, bool $generateNew = false) : SubChunk{ if($y < 0 or $y >= $this->height){ return $this->emptySubChunk; @@ -536,6 +826,14 @@ class Chunk{ return $this->subChunks[$y]; } + /** + * Sets a subchunk in the chunk index + * @param int $y + * @param SubChunk|null $subChunk + * @param bool $allowEmpty Whether to check if the chunk is empty, and if so replace it with an empty stub + * + * @return bool + */ public function setSubChunk(int $y, SubChunk $subChunk = null, bool $allowEmpty = false) : bool{ if($y < 0 or $y >= $this->height){ return false; @@ -549,10 +847,18 @@ class Chunk{ return true; } + /** + * @return SubChunk[] + */ public function getSubChunks() : array{ return $this->subChunks; } + /** + * Returns the Y coordinate of the highest non-empty subchunk in this chunk. + * + * @return int + */ public function getHighestSubChunkIndex() : int{ for($y = count($this->subChunks) - 1; $y >= 0; --$y){ if($this->subChunks[$y] === null or $this->subChunks[$y] instanceof EmptySubChunk){ @@ -565,10 +871,18 @@ class Chunk{ return $y; } + /** + * Returns the count of subchunks that need sending to players + * + * @return int + */ public function getSubChunkSendCount() : int{ return $this->getHighestSubChunkIndex() + 1; } + /** + * Disposes of empty subchunks + */ public function pruneEmptySubChunks(){ foreach($this->subChunks as $y => $subChunk){ if($y < 0 or $y >= $this->height){ @@ -585,6 +899,11 @@ class Chunk{ } } + /** + * Serializes the chunk for sending to players + * + * @return string + */ public function networkSerialize() : string{ $result = ""; $subChunkCount = $this->getSubChunkSendCount(); @@ -620,6 +939,12 @@ class Chunk{ return $result; } + /** + * Fast-serializes the chunk for passing between threads + * TODO: tiles and entities + * + * @return string + */ public function fastSerialize() : string{ $stream = new BinaryStream(); $stream->putInt($this->x); @@ -638,10 +963,17 @@ class Chunk{ $stream->put(pack("C*", ...$this->heightMap) . $this->biomeIds . chr(($this->lightPopulated ? 1 << 2 : 0) | ($this->terrainPopulated ? 1 << 1 : 0) | ($this->terrainGenerated ? 1 : 0))); - //TODO: tiles and entities return $stream->getBuffer(); } + /** + * Deserializes a fast-serialized chunk + * + * @param string $data + * @param LevelProvider|null $provider + * + * @return Chunk + */ public static function fastDeserialize(string $data, LevelProvider $provider = null){ $stream = new BinaryStream(); $stream->setBuffer($data);