From 5c85aa6e58ecb3f5a56adf7dcb350e81818d1564 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 5 Jun 2022 21:49:51 +0100 Subject: [PATCH] Eliminate remaining usages of legacy block ID+meta on disk flower pots loaded from vanilla worlds should now correctly display the plant inside --- src/block/tile/FlowerPot.php | 30 +++++++--- src/entity/object/FallingBlock.php | 41 +++++++++---- src/entity/projectile/Projectile.php | 59 ++++++++----------- .../format/io/GlobalBlockStateHandlers.php | 21 +++++++ src/world/format/io/leveldb/LevelDB.php | 16 +---- 5 files changed, 98 insertions(+), 69 deletions(-) diff --git a/src/block/tile/FlowerPot.php b/src/block/tile/FlowerPot.php index 294e4fe41..3534bedf5 100644 --- a/src/block/tile/FlowerPot.php +++ b/src/block/tile/FlowerPot.php @@ -26,9 +26,12 @@ namespace pocketmine\block\tile; use pocketmine\block\Air; use pocketmine\block\Block; use pocketmine\block\BlockFactory; +use pocketmine\data\bedrock\blockstate\BlockStateDeserializeException; +use pocketmine\data\SavedDataLoadingException; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\ShortTag; +use pocketmine\world\format\io\GlobalBlockStateHandlers; /** * @deprecated @@ -37,25 +40,35 @@ use pocketmine\nbt\tag\ShortTag; class FlowerPot extends Spawnable{ private const TAG_ITEM = "item"; private const TAG_ITEM_DATA = "mData"; + private const TAG_PLANT_BLOCK = "PlantBlock"; private ?Block $plant = null; public function readSaveData(CompoundTag $nbt) : void{ + $blockStateData = null; if(($itemIdTag = $nbt->getTag(self::TAG_ITEM)) instanceof ShortTag && ($itemMetaTag = $nbt->getTag(self::TAG_ITEM_DATA)) instanceof IntTag){ + $blockStateData = GlobalBlockStateHandlers::getLegacyBlockStateMapper()->fromIntIdMeta($itemIdTag->getValue(), $itemMetaTag->getValue()); + }elseif(($plantBlockTag = $nbt->getCompoundTag(self::TAG_PLANT_BLOCK)) !== null){ try{ - $this->setPlant(BlockFactory::getInstance()->get($itemIdTag->getValue(), $itemMetaTag->getValue())); - }catch(\InvalidArgumentException $e){ - //noop + $blockStateData = GlobalBlockStateHandlers::nbtToBlockStateData($plantBlockTag); + }catch(BlockStateDeserializeException $e){ + throw new SavedDataLoadingException("Error loading " . self::TAG_PLANT_BLOCK . " tag for flower pot: " . $e->getMessage(), 0, $e); } - }else{ - //TODO: new PlantBlock tag + } + + if($blockStateData !== null){ + try{ + $blockStateId = GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData); + }catch(BlockStateDeserializeException $e){ + throw new SavedDataLoadingException("Error deserializing plant for flower pot: " . $e->getMessage(), 0, $e); + } + $this->setPlant(BlockFactory::getInstance()->fromFullBlock($blockStateId)); } } protected function writeSaveData(CompoundTag $nbt) : void{ if($this->plant !== null){ - $nbt->setShort(self::TAG_ITEM, $this->plant->getId()); - $nbt->setInt(self::TAG_ITEM_DATA, $this->plant->getMeta()); + $nbt->setTag(self::TAG_PLANT_BLOCK, GlobalBlockStateHandlers::getSerializer()->serialize($this->plant->getFullId())->toNbt()); } } @@ -73,8 +86,7 @@ class FlowerPot extends Spawnable{ protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ if($this->plant !== null){ - $nbt->setShort(self::TAG_ITEM, $this->plant->getId()); - $nbt->setInt(self::TAG_ITEM_DATA, $this->plant->getMeta()); + $nbt->setTag(self::TAG_PLANT_BLOCK, GlobalBlockStateHandlers::getSerializer()->serialize($this->plant->getFullId())->toNbt()); } } } diff --git a/src/entity/object/FallingBlock.php b/src/entity/object/FallingBlock.php index 1a61a2fba..02c0249f8 100644 --- a/src/entity/object/FallingBlock.php +++ b/src/entity/object/FallingBlock.php @@ -26,6 +26,7 @@ namespace pocketmine\entity\object; use pocketmine\block\Block; use pocketmine\block\BlockFactory; use pocketmine\block\utils\Fallable; +use pocketmine\data\bedrock\blockstate\BlockStateDeserializeException; use pocketmine\data\SavedDataLoadingException; use pocketmine\entity\Entity; use pocketmine\entity\EntitySizeInfo; @@ -40,9 +41,11 @@ use pocketmine\network\mcpe\convert\RuntimeBlockMapping; use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; +use pocketmine\world\format\io\GlobalBlockStateHandlers; use function abs; class FallingBlock extends Entity{ + private const TAG_FALLING_BLOCK = "FallingBlock"; //TAG_Compound public static function getNetworkTypeId() : string{ return EntityIds::FALLING_BLOCK; } @@ -60,22 +63,37 @@ class FallingBlock extends Entity{ protected function getInitialGravity() : float{ return 0.04; } public static function parseBlockNBT(BlockFactory $factory, CompoundTag $nbt) : Block{ - $blockId = 0; //TODO: 1.8+ save format - if(($tileIdTag = $nbt->getTag("TileID")) instanceof IntTag){ - $blockId = $tileIdTag->getValue(); - }elseif(($tileTag = $nbt->getTag("Tile")) instanceof ByteTag){ - $blockId = $tileTag->getValue(); + if(($fallingBlockTag = $nbt->getCompoundTag(self::TAG_FALLING_BLOCK)) !== null){ + try{ + $blockStateData = GlobalBlockStateHandlers::nbtToBlockStateData($fallingBlockTag); + }catch(BlockStateDeserializeException $e){ + throw new SavedDataLoadingException($e->getMessage(), 0, $e); + } + }else{ + if(($tileIdTag = $nbt->getTag("TileID")) instanceof IntTag){ + $blockId = $tileIdTag->getValue(); + }elseif(($tileTag = $nbt->getTag("Tile")) instanceof ByteTag){ + $blockId = $tileTag->getValue(); + }else{ + throw new SavedDataLoadingException("Missing legacy falling block info"); + } + $damage = $nbt->getByte("Data", 0); + + $blockStateData = GlobalBlockStateHandlers::getLegacyBlockStateMapper()->fromIntIdMeta($blockId, $damage); + if($blockStateData === null){ + throw new SavedDataLoadingException("Invalid legacy falling block"); + } } - if($blockId === 0){ - throw new SavedDataLoadingException("Missing block info from NBT"); + try{ + $blockStateId = GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData); + }catch(BlockStateDeserializeException $e){ + throw new SavedDataLoadingException($e->getMessage(), 0, $e); } - $damage = $nbt->getByte("Data", 0); - - return $factory->get($blockId, $damage); + return $factory->fromFullBlock($blockStateId); } public function canCollideWith(Entity $entity) : bool{ @@ -137,8 +155,7 @@ class FallingBlock extends Entity{ public function saveNBT() : CompoundTag{ $nbt = parent::saveNBT(); - $nbt->setInt("TileID", $this->block->getId()); - $nbt->setByte("Data", $this->block->getMeta()); + $nbt->setTag(self::TAG_FALLING_BLOCK, GlobalBlockStateHandlers::getSerializer()->serialize($this->block->getFullId())->toNbt()); return $nbt; } diff --git a/src/entity/projectile/Projectile.php b/src/entity/projectile/Projectile.php index 40e7cb45d..dd0b50c75 100644 --- a/src/entity/projectile/Projectile.php +++ b/src/entity/projectile/Projectile.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\entity\projectile; use pocketmine\block\Block; -use pocketmine\block\BlockFactory; +use pocketmine\data\SavedDataLoadingException; use pocketmine\entity\Entity; use pocketmine\entity\Living; use pocketmine\entity\Location; @@ -38,21 +38,24 @@ use pocketmine\event\entity\ProjectileHitEvent; use pocketmine\math\RayTraceResult; use pocketmine\math\Vector3; use pocketmine\math\VoxelRayTrace; -use pocketmine\nbt\tag\ByteTag; +use pocketmine\nbt\NBT; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; +use pocketmine\nbt\tag\ListTag; use pocketmine\timings\Timings; use function assert; use function atan2; use function ceil; +use function count; use function sqrt; use const M_PI; use const PHP_INT_MAX; abstract class Projectile extends Entity{ + private const TAG_STUCK_ON_BLOCK_POS = "StuckToBlockPos"; protected float $damage = 0.0; - protected ?Block $blockHit = null; + protected ?Vector3 $blockHit = null; public function __construct(Location $location, ?Entity $shootingEntity, ?CompoundTag $nbt = null){ parent::__construct($location, $nbt); @@ -74,28 +77,18 @@ abstract class Projectile extends Entity{ $this->setHealth(1); $this->damage = $nbt->getDouble("damage", $this->damage); - (function() use ($nbt) : void{ - if(($tileXTag = $nbt->getTag("tileX")) instanceof IntTag && ($tileYTag = $nbt->getTag("tileY")) instanceof IntTag && ($tileZTag = $nbt->getTag("tileZ")) instanceof IntTag){ - $blockPos = new Vector3($tileXTag->getValue(), $tileYTag->getValue(), $tileZTag->getValue()); - }else{ - return; + if(($stuckOnBlockPosTag = $nbt->getListTag(self::TAG_STUCK_ON_BLOCK_POS)) !== null){ + if($stuckOnBlockPosTag->getTagType() !== NBT::TAG_Int || count($stuckOnBlockPosTag) !== 3){ + throw new SavedDataLoadingException(self::TAG_STUCK_ON_BLOCK_POS . " tag should be a list of 3 TAG_Int"); } - if(($blockIdTag = $nbt->getTag("blockId")) instanceof IntTag){ - $blockId = $blockIdTag->getValue(); - }else{ - return; - } + /** @var IntTag[] $values */ + $values = $stuckOnBlockPosTag->getValue(); - if(($blockDataTag = $nbt->getTag("blockData")) instanceof ByteTag){ - $blockData = $blockDataTag->getValue(); - }else{ - return; - } - - $this->blockHit = BlockFactory::getInstance()->get($blockId, $blockData); - $this->blockHit->position($this->getWorld(), $blockPos->getFloorX(), $blockPos->getFloorY(), $blockPos->getFloorZ()); - })(); + $this->blockHit = new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue()); + }elseif(($tileXTag = $nbt->getTag("tileX")) instanceof IntTag && ($tileYTag = $nbt->getTag("tileY")) instanceof IntTag && ($tileZTag = $nbt->getTag("tileZ")) instanceof IntTag){ + $this->blockHit = new Vector3($tileXTag->getValue(), $tileYTag->getValue(), $tileZTag->getValue()); + } } public function canCollideWith(Entity $entity) : bool{ @@ -134,14 +127,11 @@ abstract class Projectile extends Entity{ $nbt->setDouble("damage", $this->damage); if($this->blockHit !== null){ - $pos = $this->blockHit->getPosition(); - $nbt->setInt("tileX", $pos->x); - $nbt->setInt("tileY", $pos->y); - $nbt->setInt("tileZ", $pos->z); - - //we intentionally use different ones to PC because we don't have stringy IDs - $nbt->setInt("blockId", $this->blockHit->getId()); - $nbt->setByte("blockData", $this->blockHit->getMeta()); + $nbt->setTag(self::TAG_STUCK_ON_BLOCK_POS, new ListTag([ + new IntTag($this->blockHit->getFloorX()), + new IntTag($this->blockHit->getFloorY()), + new IntTag($this->blockHit->getFloorZ()) + ])); } return $nbt; @@ -152,8 +142,11 @@ abstract class Projectile extends Entity{ } public function onNearbyBlockChange() : void{ - if($this->blockHit !== null && $this->getWorld()->isInLoadedTerrain($this->blockHit->getPosition()) && !$this->blockHit->isSameState($this->getWorld()->getBlock($this->blockHit->getPosition()))){ - $this->blockHit = null; + if($this->blockHit !== null && $this->getWorld()->isInLoadedTerrain($this->blockHit)){ + $blockHit = $this->getWorld()->getBlock($this->blockHit); + if(!$blockHit->collidesWithBB($this->getBoundingBox()->expandedCopy(0.001, 0.001, 0.001))){ + $this->blockHit = null; + } } parent::onNearbyBlockChange(); @@ -312,6 +305,6 @@ abstract class Projectile extends Entity{ * Called when the projectile collides with a Block. */ protected function onHitBlock(Block $blockHit, RayTraceResult $hitResult) : void{ - $this->blockHit = clone $blockHit; + $this->blockHit = $blockHit->getPosition()->asVector3(); } } diff --git a/src/world/format/io/GlobalBlockStateHandlers.php b/src/world/format/io/GlobalBlockStateHandlers.php index 3ea54b01c..096f116f5 100644 --- a/src/world/format/io/GlobalBlockStateHandlers.php +++ b/src/world/format/io/GlobalBlockStateHandlers.php @@ -26,6 +26,7 @@ namespace pocketmine\world\format\io; use pocketmine\data\bedrock\blockstate\BlockStateData; use pocketmine\data\bedrock\blockstate\BlockStateDeserializer; use pocketmine\data\bedrock\blockstate\BlockStateSerializer; +use pocketmine\data\bedrock\blockstate\BlockTypeNames; use pocketmine\data\bedrock\blockstate\CachingBlockStateDeserializer; use pocketmine\data\bedrock\blockstate\CachingBlockStateSerializer; use pocketmine\data\bedrock\blockstate\convert\BlockObjectToBlockStateSerializer; @@ -36,6 +37,7 @@ use pocketmine\data\bedrock\blockstate\upgrade\LegacyBlockStateMapper; use pocketmine\data\bedrock\blockstate\UpgradingBlockStateDeserializer; use pocketmine\data\bedrock\LegacyBlockIdToStringIdMap; use pocketmine\errorhandler\ErrorToExceptionHandler; +use pocketmine\nbt\tag\CompoundTag; use Webmozart\PathUtil\Path; use function file_get_contents; use const pocketmine\BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH; @@ -79,4 +81,23 @@ final class GlobalBlockStateHandlers{ LegacyBlockIdToStringIdMap::getInstance() ); } + + public static function nbtToBlockStateData(CompoundTag $tag) : BlockStateData{ + if($tag->getTag("name") !== null && $tag->getTag("val") !== null){ + //Legacy (pre-1.13) blockstate - upgrade it to a version we understand + $id = $tag->getString("name"); + $data = $tag->getShort("val"); + + $blockStateData = GlobalBlockStateHandlers::getLegacyBlockStateMapper()->fromStringIdMeta($id, $data); + if($blockStateData === null){ + //unknown block, invalid ID + $blockStateData = new BlockStateData(BlockTypeNames::INFO_UPDATE, CompoundTag::create(), BlockStateData::CURRENT_VERSION); + } + }else{ + //Modern (post-1.13) blockstate + $blockStateData = BlockStateData::fromNbt($tag); + } + + return $blockStateData; + } } diff --git a/src/world/format/io/leveldb/LevelDB.php b/src/world/format/io/leveldb/LevelDB.php index 5be930660..1a25be18a 100644 --- a/src/world/format/io/leveldb/LevelDB.php +++ b/src/world/format/io/leveldb/LevelDB.php @@ -165,23 +165,9 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ try{ $offset = $stream->getOffset(); - $tag = $nbt->read($stream->getBuffer(), $offset)->mustGetCompoundTag(); + $blockStateData = GlobalBlockStateHandlers::nbtToBlockStateData($nbt->read($stream->getBuffer(), $offset)->mustGetCompoundTag()); $stream->setOffset($offset); - if($tag->getTag("name") !== null && $tag->getTag("val") !== null){ - //Legacy (pre-1.13) blockstate - upgrade it to a version we understand - $id = $tag->getString("name"); - $data = $tag->getShort("val"); - - $blockStateData = GlobalBlockStateHandlers::getLegacyBlockStateMapper()->fromStringIdMeta($id, $data); - if($blockStateData === null){ - $blockStateData = new BlockStateData(BlockTypeNames::INFO_UPDATE, CompoundTag::create(), BlockStateData::CURRENT_VERSION); - } - }else{ - //Modern (post-1.13) blockstate - $blockStateData = BlockStateData::fromNbt($tag); - } - try{ $palette[] = $blockStateDeserializer->deserialize($blockStateData); }catch(BlockStateDeserializeException){