*/ private array $idMetaUpgradeSchemas = []; /** * @param ItemIdMetaUpgradeSchema[] $idMetaUpgradeSchemas * @phpstan-param array $idMetaUpgradeSchemas */ public function __construct( private LegacyItemIdToStringIdMap $legacyIntToStringIdMap, private R12ItemIdToBlockIdMap $r12ItemIdToBlockIdMap, private BlockDataUpgrader $blockDataUpgrader, array $idMetaUpgradeSchemas ){ foreach($idMetaUpgradeSchemas as $schema){ $this->addIdMetaUpgradeSchema($schema); } } public function addIdMetaUpgradeSchema(ItemIdMetaUpgradeSchema $schema) : void{ if(isset($this->idMetaUpgradeSchemas[$schema->getPriority()])){ throw new \InvalidArgumentException("Already have a schema with priority " . $schema->getPriority()); } $this->idMetaUpgradeSchemas[$schema->getPriority()] = $schema; ksort($this->idMetaUpgradeSchemas, SORT_NUMERIC); } /** * @throws SavedDataLoadingException */ private function upgradeItemTypeNbt(CompoundTag $tag) : SavedItemData{ if(($nameIdTag = $tag->getTag(SavedItemData::TAG_NAME)) instanceof StringTag){ //Bedrock 1.6+ $rawNameId = $nameIdTag->getValue(); }elseif(($idTag = $tag->getTag(self::TAG_LEGACY_ID)) instanceof ShortTag){ //Bedrock <= 1.5, PM <= 1.12 $rawNameId = $this->legacyIntToStringIdMap->legacyToString($idTag->getValue()); if($rawNameId === null){ throw new SavedDataLoadingException("Legacy item ID " . $idTag->getValue() . " doesn't map to any modern string ID"); } }elseif($idTag instanceof StringTag){ //PC item save format - best we can do here is hope the string IDs match $rawNameId = $idTag->getValue(); }else{ throw new SavedDataLoadingException("Item stack data should have either a name ID or a legacy ID"); } $meta = $tag->getShort(SavedItemData::TAG_DAMAGE, 0); $blockStateNbt = $tag->getCompoundTag(SavedItemData::TAG_BLOCK); if($blockStateNbt !== null){ $blockStateData = $this->blockDataUpgrader->upgradeBlockStateNbt($blockStateNbt); }elseif(($r12BlockId = $this->r12ItemIdToBlockIdMap->itemIdToBlockId($rawNameId)) !== null){ //this is a legacy blockitem represented by ID + meta $blockStateData = $this->blockDataUpgrader->upgradeStringIdMeta($r12BlockId, $meta); if($blockStateData === null){ throw new SavedDataLoadingException("Expected a blockstate to be associated with this block"); } }else{ //probably a standard item $blockStateData = null; } [$newNameId, $newMeta] = $this->upgradeItemStringIdMeta($rawNameId, $meta); //TODO: this won't account for spawn eggs from before 1.16.100 - perhaps we're lucky and they just left the meta in there anyway? return new SavedItemData($newNameId, $newMeta, $blockStateData, $tag->getCompoundTag(SavedItemData::TAG_TAG)); } /** * @return string[] * @throws SavedDataLoadingException */ private static function deserializeListOfStrings(?ListTag $list, string $tagName) : array{ if($list === null){ return []; } if($list->getTagType() !== NBT::TAG_String){ throw new SavedDataLoadingException("Unexpected type of list for tag '$tagName', expected TAG_String"); } $result = []; foreach($list as $item){ assert($item instanceof StringTag); $result[] = $item->getValue(); } return $result; } /** * @throws SavedDataLoadingException */ public function upgradeItemStackNbt(CompoundTag $tag) : SavedItemStackData{ $savedItemData = $this->upgradeItemTypeNbt($tag); try{ //required $count = Binary::unsignByte($tag->getByte(SavedItemStackData::TAG_COUNT)); //optional $slot = ($slotTag = $tag->getTag(SavedItemStackData::TAG_SLOT)) instanceof ByteTag ? Binary::unsignByte($slotTag->getValue()) : null; $wasPickedUp = ($wasPickedUpTag = $tag->getTag(SavedItemStackData::TAG_WAS_PICKED_UP)) instanceof ByteTag ? $wasPickedUpTag->getValue() : null; $canPlaceOnList = $tag->getListTag(SavedItemStackData::TAG_CAN_PLACE_ON); $canDestroyList = $tag->getListTag(SavedItemStackData::TAG_CAN_DESTROY); }catch(NbtException $e){ throw new SavedDataLoadingException($e->getMessage(), 0, $e); } return new SavedItemStackData( $savedItemData, $count, $slot, $wasPickedUp !== 0, self::deserializeListOfStrings($canPlaceOnList, SavedItemStackData::TAG_CAN_PLACE_ON), self::deserializeListOfStrings($canDestroyList, SavedItemStackData::TAG_CAN_DESTROY) ); } /** * @phpstan-return array{string, int} */ public function upgradeItemStringIdMeta(string $id, int $meta) : array{ $newId = $id; $newMeta = $meta; foreach($this->idMetaUpgradeSchemas as $schema){ if(($remappedMetaId = $schema->remapMeta($newId, $newMeta)) !== null){ $newId = $remappedMetaId; $newMeta = 0; }elseif(($renamedId = $schema->renameId($newId)) !== null){ $newId = $renamedId; } } return [$newId, $newMeta]; } }