PocketMine-MP 5.21.2 git-b2aa6396c3cc2cafdd815eacc360e1ad89599899
Loading...
Searching...
No Matches
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
41use function assert;
42
43final class ItemDataUpgrader{
44 private const TAG_LEGACY_ID = "id"; //TAG_Short (or TAG_String for Java itemstacks)
45
46 public function __construct(
47 private ItemIdMetaUpgrader $idMetaUpgrader,
48 private LegacyItemIdToStringIdMap $legacyIntToStringIdMap,
49 private R12ItemIdToBlockIdMap $r12ItemIdToBlockIdMap,
50 private BlockDataUpgrader $blockDataUpgrader,
51 private BlockItemIdMap $blockItemIdMap,
52 private BlockStateDictionary $blockStateDictionary
53 ){}
54
63 public function upgradeItemTypeDataString(string $rawNameId, int $meta, int $count, ?CompoundTag $nbt) : SavedItemStackData{
64 if(($r12BlockId = $this->r12ItemIdToBlockIdMap->itemIdToBlockId($rawNameId)) !== null){
65 try{
66 $blockStateData = $this->blockDataUpgrader->upgradeStringIdMeta($r12BlockId, $meta);
68 throw new SavedDataLoadingException("Failed to deserialize blockstate for legacy blockitem: " . $e->getMessage(), 0, $e);
69 }
70 }else{
71 //probably a standard item
72 $blockStateData = null;
73 }
74
75 [$newNameId, $newMeta] = $this->idMetaUpgrader->upgrade($rawNameId, $meta);
76
77 //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?
78
79 return new SavedItemStackData(
80 new SavedItemData($newNameId, $newMeta, $blockStateData, $nbt),
81 $count,
82 null,
83 null,
84 [],
85 []
86 );
87 }
88
94 public function upgradeItemTypeDataInt(int $legacyNumericId, int $meta, int $count, ?CompoundTag $nbt) : SavedItemStackData{
95 //do not upgrade the ID beyond this initial step - we need the 1.12 ID for the item ID -> block ID map in the
96 //next step
97 $rawNameId = $this->legacyIntToStringIdMap->legacyToString($legacyNumericId);
98 if($rawNameId === null){
99 throw new SavedDataLoadingException("Unmapped legacy item ID $legacyNumericId");
100 }
101 return $this->upgradeItemTypeDataString($rawNameId, $meta, $count, $nbt);
102 }
103
107 private function upgradeItemTypeNbt(CompoundTag $tag) : ?SavedItemData{
108 if(($nameIdTag = $tag->getTag(SavedItemData::TAG_NAME)) instanceof StringTag){
109 //Bedrock 1.6+
110
111 $rawNameId = $nameIdTag->getValue();
112 }elseif(($idTag = $tag->getTag(self::TAG_LEGACY_ID)) instanceof ShortTag){
113 //Bedrock <= 1.5, PM <= 1.12
114
115 if($idTag->getValue() === 0){
116 //0 is a special case for air, which is not a valid item ID
117 //this isn't supposed to be saved, but this appears in some places due to bugs in older versions
118 return null;
119 }
120 $rawNameId = $this->legacyIntToStringIdMap->legacyToString($idTag->getValue());
121 if($rawNameId === null){
122 throw new SavedDataLoadingException("Legacy item ID " . $idTag->getValue() . " doesn't map to any modern string ID");
123 }
124 }elseif($idTag instanceof StringTag){
125 //PC item save format - best we can do here is hope the string IDs match
126
127 $rawNameId = $idTag->getValue();
128 }else{
129 throw new SavedDataLoadingException("Item stack data should have either a name ID or a legacy ID");
130 }
131
132 $meta = $tag->getShort(SavedItemData::TAG_DAMAGE, 0);
133
134 $blockStateNbt = $tag->getCompoundTag(SavedItemData::TAG_BLOCK);
135 if($blockStateNbt !== null){
136 try{
137 $blockStateData = $this->blockDataUpgrader->upgradeBlockStateNbt($blockStateNbt);
138 }catch(BlockStateDeserializeException $e){
139 throw new SavedDataLoadingException("Failed to deserialize blockstate for blockitem: " . $e->getMessage(), 0, $e);
140 }
141 }elseif(($r12BlockId = $this->r12ItemIdToBlockIdMap->itemIdToBlockId($rawNameId)) !== null){
142 //this is a legacy blockitem represented by ID + meta
143 try{
144 $blockStateData = $this->blockDataUpgrader->upgradeStringIdMeta($r12BlockId, $meta);
145 }catch(BlockStateDeserializeException $e){
146 throw new SavedDataLoadingException("Failed to deserialize blockstate for legacy blockitem: " . $e->getMessage(), 0, $e);
147 }
148 }else{
149 //probably a standard item
150 $blockStateData = null;
151 }
152
153 [$newNameId, $newMeta] = $this->idMetaUpgrader->upgrade($rawNameId, $meta);
154
155 //TODO: Dirty hack to load old skulls from disk: Put this into item upgrade schema's before Mojang makes something with a non 0 default state
156 if($blockStateData === null && ($blockId = $this->blockItemIdMap->lookupBlockId($newNameId)) !== null){
157 $networkRuntimeId = $this->blockStateDictionary->lookupStateIdFromIdMeta($blockId, 0);
158
159 if($networkRuntimeId === null){
160 throw new SavedDataLoadingException("Failed to find blockstate for blockitem $newNameId");
161 }
162
163 $blockStateData = $this->blockStateDictionary->generateDataFromStateId($networkRuntimeId);
164 }
165
166 //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?
167 //TODO: read version from VersionInfo::TAG_WORLD_DATA_VERSION - we may need it to fix up old items
168
169 return new SavedItemData($newNameId, $newMeta, $blockStateData, $tag->getCompoundTag(SavedItemData::TAG_TAG));
170 }
171
176 private static function deserializeListOfStrings(?ListTag $list, string $tagName) : array{
177 if($list === null){
178 return [];
179 }
180 if($list->getTagType() !== NBT::TAG_String){
181 throw new SavedDataLoadingException("Unexpected type of list for tag '$tagName', expected TAG_String");
182 }
183 $result = [];
184 foreach($list as $item){
185 assert($item instanceof StringTag);
186 $result[] = $item->getValue();
187 }
188
189 return $result;
190 }
191
196 $savedItemData = $this->upgradeItemTypeNbt($tag);
197 if($savedItemData === null){
198 //air - this isn't supposed to be saved, but older versions of PM saved it in some places
199 return null;
200 }
201 try{
202 //required
203 $count = Binary::unsignByte($tag->getByte(SavedItemStackData::TAG_COUNT));
204
205 //optional
206 $slot = ($slotTag = $tag->getTag(SavedItemStackData::TAG_SLOT)) instanceof ByteTag ? Binary::unsignByte($slotTag->getValue()) : null;
207 $wasPickedUp = ($wasPickedUpTag = $tag->getTag(SavedItemStackData::TAG_WAS_PICKED_UP)) instanceof ByteTag ? $wasPickedUpTag->getValue() : null;
208 $canPlaceOnList = $tag->getListTag(SavedItemStackData::TAG_CAN_PLACE_ON);
209 $canDestroyList = $tag->getListTag(SavedItemStackData::TAG_CAN_DESTROY);
210 }catch(NbtException $e){
211 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
212 }
213
214 return new SavedItemStackData(
215 $savedItemData,
216 $count,
217 $slot,
218 $wasPickedUp !== 0,
219 self::deserializeListOfStrings($canPlaceOnList, SavedItemStackData::TAG_CAN_PLACE_ON),
220 self::deserializeListOfStrings($canDestroyList, SavedItemStackData::TAG_CAN_DESTROY)
221 );
222 }
223
224 public function getIdMetaUpgrader() : ItemIdMetaUpgrader{ return $this->idMetaUpgrader; }
225}
upgradeItemTypeDataString(string $rawNameId, int $meta, int $count, ?CompoundTag $nbt)
upgradeItemTypeDataInt(int $legacyNumericId, int $meta, int $count, ?CompoundTag $nbt)