Workaround items in blockentity NBT not being processed correctly in 1.19.10

closes #5154

this hack sends only the bare essential data to create the tiles in LevelChunkPacket,
and then separately sending the full tile data using BlockActorDataPacket afterwards.

This is necessary because the client doesn't handle items correctly in NBT when chunks are sent without using the SubChunkRequest system.
In 4.6 this is observed with incorrect items shown in item frames; in 5.0 it's seen with items simply not showing up at all (difference due to modernization of the serialization format in 5.0).
This commit is contained in:
Dylan K. Taylor 2022-07-14 21:54:01 +01:00
parent c7133bc2e6
commit 2b61c025c2
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
2 changed files with 33 additions and 1 deletions

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe;
use pocketmine\block\tile\Spawnable;
use pocketmine\data\bedrock\EffectIdMap;
use pocketmine\entity\Attribute;
use pocketmine\entity\effect\EffectInstance;
@ -58,6 +59,7 @@ use pocketmine\network\mcpe\handler\PreSpawnPacketHandler;
use pocketmine\network\mcpe\handler\ResourcePacksPacketHandler;
use pocketmine\network\mcpe\handler\SpawnResponsePacketHandler;
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
use pocketmine\network\mcpe\protocol\BlockActorDataPacket;
use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\DisconnectPacket;
@ -959,6 +961,22 @@ class NetworkSession{
try{
$this->queueCompressed($promise);
$onCompletion();
//TODO: HACK! we send the full tile data here, due to a bug in 1.19.10 which causes items in tiles
//(item frames, lecterns) to not load properly when they are sent in a chunk via the classic chunk
//sending mechanism. We workaround this bug by sending only bare essential data in LevelChunkPacket
//(enough to create the tiles, since BlockActorDataPacket can't create tiles by itself) and then
//send the actual tile properties here.
//TODO: maybe we can stuff these packets inside the cached batch alongside LevelChunkPacket?
$chunk = $currentWorld->getChunk($chunkX, $chunkZ);
if($chunk !== null){
foreach($chunk->getTiles() as $tile){
if(!($tile instanceof Spawnable)){
continue;
}
$this->sendDataPacket(BlockActorDataPacket::create(BlockPosition::fromVector3($tile->getPosition()), $tile->getSerializedSpawnCompound()));
}
}
}finally{
$world->timings->syncChunkSend->stopTiming();
}

View File

@ -24,8 +24,11 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\serializer;
use pocketmine\block\tile\Spawnable;
use pocketmine\block\tile\Tile;
use pocketmine\block\tile\TileFactory;
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\data\bedrock\LegacyBiomeIdToStringIdMap;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
@ -38,6 +41,7 @@ use pocketmine\world\format\PalettedBlockArray;
use pocketmine\world\format\SubChunk;
use function chr;
use function count;
use function get_class;
use function str_repeat;
final class ChunkSerializer{
@ -125,9 +129,19 @@ final class ChunkSerializer{
public static function serializeTiles(Chunk $chunk) : string{
$stream = new BinaryStream();
$nbtSerializer = new NetworkNbtSerializer();
foreach($chunk->getTiles() as $tile){
if($tile instanceof Spawnable){
$stream->put($tile->getSerializedSpawnCompound()->getEncodedNbt());
//TODO: HACK! we send only the bare essentials to create a tile in the chunk itself, due to a bug in
//1.19.10 which causes items in tiles (item frames, lecterns) to not load properly when they are sent in
//a chunk via the classic chunk sending mechanism. We workaround this bug by sendingBlockActorDataPacket
//in NetworkSession to set the actual tile properties after sending the LevelChunkPacket.
$nbt = CompoundTag::create()
->setString(Tile::TAG_ID, TileFactory::getInstance()->getSaveId(get_class($tile)))
->setInt(Tile::TAG_X, $tile->getPosition()->getFloorX())
->setInt(Tile::TAG_Y, $tile->getPosition()->getFloorY())
->setInt(Tile::TAG_Z, $tile->getPosition()->getFloorZ());
$stream->put($nbtSerializer->write(new TreeRoot($nbt)));
}
}