PocketMine-MP 5.18.1 git-9381fc4172e5dce4cada1cb356050c8a2ab57b94
ItemDataUpgrader.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\data\bedrock\item\upgrade;
25
39use function assert;
40
41final class ItemDataUpgrader{
42 private const TAG_LEGACY_ID = "id"; //TAG_Short (or TAG_String for Java itemstacks)
43
44 public function __construct(
45 private ItemIdMetaUpgrader $idMetaUpgrader,
46 private LegacyItemIdToStringIdMap $legacyIntToStringIdMap,
47 private R12ItemIdToBlockIdMap $r12ItemIdToBlockIdMap,
48 private BlockDataUpgrader $blockDataUpgrader,
49 ){}
50
59 public function upgradeItemTypeDataString(string $rawNameId, int $meta, int $count, ?CompoundTag $nbt) : SavedItemStackData{
60 if(($r12BlockId = $this->r12ItemIdToBlockIdMap->itemIdToBlockId($rawNameId)) !== null){
61 try{
62 $blockStateData = $this->blockDataUpgrader->upgradeStringIdMeta($r12BlockId, $meta);
64 throw new SavedDataLoadingException("Failed to deserialize blockstate for legacy blockitem: " . $e->getMessage(), 0, $e);
65 }
66 }else{
67 //probably a standard item
68 $blockStateData = null;
69 }
70
71 [$newNameId, $newMeta] = $this->idMetaUpgrader->upgrade($rawNameId, $meta);
72
73 //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?
74
75 return new SavedItemStackData(
76 new SavedItemData($newNameId, $newMeta, $blockStateData, $nbt),
77 $count,
78 null,
79 null,
80 [],
81 []
82 );
83 }
84
90 public function upgradeItemTypeDataInt(int $legacyNumericId, int $meta, int $count, ?CompoundTag $nbt) : SavedItemStackData{
91 //do not upgrade the ID beyond this initial step - we need the 1.12 ID for the item ID -> block ID map in the
92 //next step
93 $rawNameId = $this->legacyIntToStringIdMap->legacyToString($legacyNumericId);
94 if($rawNameId === null){
95 throw new SavedDataLoadingException("Unmapped legacy item ID $legacyNumericId");
96 }
97 return $this->upgradeItemTypeDataString($rawNameId, $meta, $count, $nbt);
98 }
99
103 private function upgradeItemTypeNbt(CompoundTag $tag) : ?SavedItemData{
104 if(($nameIdTag = $tag->getTag(SavedItemData::TAG_NAME)) instanceof StringTag){
105 //Bedrock 1.6+
106
107 $rawNameId = $nameIdTag->getValue();
108 }elseif(($idTag = $tag->getTag(self::TAG_LEGACY_ID)) instanceof ShortTag){
109 //Bedrock <= 1.5, PM <= 1.12
110
111 if($idTag->getValue() === 0){
112 //0 is a special case for air, which is not a valid item ID
113 //this isn't supposed to be saved, but this appears in some places due to bugs in older versions
114 return null;
115 }
116 $rawNameId = $this->legacyIntToStringIdMap->legacyToString($idTag->getValue());
117 if($rawNameId === null){
118 throw new SavedDataLoadingException("Legacy item ID " . $idTag->getValue() . " doesn't map to any modern string ID");
119 }
120 }elseif($idTag instanceof StringTag){
121 //PC item save format - best we can do here is hope the string IDs match
122
123 $rawNameId = $idTag->getValue();
124 }else{
125 throw new SavedDataLoadingException("Item stack data should have either a name ID or a legacy ID");
126 }
127
128 $meta = $tag->getShort(SavedItemData::TAG_DAMAGE, 0);
129
130 $blockStateNbt = $tag->getCompoundTag(SavedItemData::TAG_BLOCK);
131 if($blockStateNbt !== null){
132 try{
133 $blockStateData = $this->blockDataUpgrader->upgradeBlockStateNbt($blockStateNbt);
134 }catch(BlockStateDeserializeException $e){
135 throw new SavedDataLoadingException("Failed to deserialize blockstate for blockitem: " . $e->getMessage(), 0, $e);
136 }
137 }elseif(($r12BlockId = $this->r12ItemIdToBlockIdMap->itemIdToBlockId($rawNameId)) !== null){
138 //this is a legacy blockitem represented by ID + meta
139 try{
140 $blockStateData = $this->blockDataUpgrader->upgradeStringIdMeta($r12BlockId, $meta);
141 }catch(BlockStateDeserializeException $e){
142 throw new SavedDataLoadingException("Failed to deserialize blockstate for legacy blockitem: " . $e->getMessage(), 0, $e);
143 }
144 }else{
145 //probably a standard item
146 $blockStateData = null;
147 }
148
149 [$newNameId, $newMeta] = $this->idMetaUpgrader->upgrade($rawNameId, $meta);
150
151 //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?
152 //TODO: read version from VersionInfo::TAG_WORLD_DATA_VERSION - we may need it to fix up old items
153
154 return new SavedItemData($newNameId, $newMeta, $blockStateData, $tag->getCompoundTag(SavedItemData::TAG_TAG));
155 }
156
161 private static function deserializeListOfStrings(?ListTag $list, string $tagName) : array{
162 if($list === null){
163 return [];
164 }
165 if($list->getTagType() !== NBT::TAG_String){
166 throw new SavedDataLoadingException("Unexpected type of list for tag '$tagName', expected TAG_String");
167 }
168 $result = [];
169 foreach($list as $item){
170 assert($item instanceof StringTag);
171 $result[] = $item->getValue();
172 }
173
174 return $result;
175 }
176
181 $savedItemData = $this->upgradeItemTypeNbt($tag);
182 if($savedItemData === null){
183 //air - this isn't supposed to be saved, but older versions of PM saved it in some places
184 return null;
185 }
186 try{
187 //required
188 $count = Binary::unsignByte($tag->getByte(SavedItemStackData::TAG_COUNT));
189
190 //optional
191 $slot = ($slotTag = $tag->getTag(SavedItemStackData::TAG_SLOT)) instanceof ByteTag ? Binary::unsignByte($slotTag->getValue()) : null;
192 $wasPickedUp = ($wasPickedUpTag = $tag->getTag(SavedItemStackData::TAG_WAS_PICKED_UP)) instanceof ByteTag ? $wasPickedUpTag->getValue() : null;
193 $canPlaceOnList = $tag->getListTag(SavedItemStackData::TAG_CAN_PLACE_ON);
194 $canDestroyList = $tag->getListTag(SavedItemStackData::TAG_CAN_DESTROY);
195 }catch(NbtException $e){
196 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
197 }
198
199 return new SavedItemStackData(
200 $savedItemData,
201 $count,
202 $slot,
203 $wasPickedUp !== 0,
204 self::deserializeListOfStrings($canPlaceOnList, SavedItemStackData::TAG_CAN_PLACE_ON),
205 self::deserializeListOfStrings($canDestroyList, SavedItemStackData::TAG_CAN_DESTROY)
206 );
207 }
208
209 public function getIdMetaUpgrader() : ItemIdMetaUpgrader{ return $this->idMetaUpgrader; }
210}
upgradeItemTypeDataString(string $rawNameId, int $meta, int $count, ?CompoundTag $nbt)
upgradeItemTypeDataInt(int $legacyNumericId, int $meta, int $count, ?CompoundTag $nbt)