getSubChunks()); $y >= Chunk::MIN_SUBCHUNK_INDEX; --$y, --$count){ if($chunk->getSubChunk($y)->isEmptyFast()){ continue; } return $count; } return 0; } public static function serializeFullChunk(Chunk $chunk, RuntimeBlockMapping $blockMapper, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{ $stream = PacketSerializer::encoder($encoderContext); //TODO: HACK! fill in fake subchunks to make up for the new negative space client-side for($y = 0; $y < self::LOWER_PADDING_SIZE; $y++){ $stream->putByte(8); //subchunk version 8 $stream->putByte(0); //0 layers - client will treat this as all-air } $subChunkCount = self::getSubChunkCount($chunk); for($y = Chunk::MIN_SUBCHUNK_INDEX, $writtenCount = 0; $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, 25)); $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. if($tiles !== null){ $stream->put($tiles); }else{ $stream->put(self::serializeTiles($chunk)); } return $stream->getBuffer(); } public static function serializeSubChunk(SubChunk $subChunk, RuntimeBlockMapping $blockMapper, PacketSerializer $stream, bool $persistentBlockStates) : void{ $layers = $subChunk->getBlockLayers(); $stream->putByte(8); //version $stream->putByte(count($layers)); foreach($layers as $blocks){ $bitsPerBlock = $blocks->getBitsPerBlock(); $words = $blocks->getWordArray(); $stream->putByte(($bitsPerBlock << 1) | ($persistentBlockStates ? 0 : 1)); $stream->put($words); $palette = $blocks->getPalette(); if($bitsPerBlock !== 0){ //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. $stream->putUnsignedVarInt(count($palette) << 1); //yes, this is intentionally zigzag } if($persistentBlockStates){ $nbtSerializer = new NetworkNbtSerializer(); foreach($palette as $p){ $stream->put($nbtSerializer->write(new TreeRoot($blockMapper->getBedrockKnownStates()[$blockMapper->toRuntimeId($p)]))); } }else{ foreach($palette as $p){ $stream->put(Binary::writeUnsignedVarInt($blockMapper->toRuntimeId($p) << 1)); } } } } public static function serializeTiles(Chunk $chunk) : string{ $stream = new BinaryStream(); foreach($chunk->getTiles() as $tile){ if($tile instanceof Spawnable){ $stream->put($tile->getSerializedSpawnCompound()->getEncodedNbt()); } } 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; } }