PocketMine-MP 5.35.1 git-e32e836dad793a3a3c8ddd8927c00e112b1e576a
Loading...
Searching...
No Matches
TypeConverter.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\network\mcpe\convert;
25
26use pmmp\encoding\ByteBufferReader;
27use pmmp\encoding\ByteBufferWriter;
57use pocketmine\player\GameMode;
60use pocketmine\utils\SingletonTrait;
63use function get_class;
64use function hash;
65
67 use SingletonTrait;
68
69 private const PM_ID_TAG = "___Id___";
70 private const PM_FULL_NBT_HASH_TAG = "___FullNbtHash___";
71
72 private const RECIPE_INPUT_WILDCARD_META = 0x7fff;
73
74 private BlockItemIdMap $blockItemIdMap;
75 private BlockTranslator $blockTranslator;
76 private ItemTranslator $itemTranslator;
77 private ItemTypeDictionary $itemTypeDictionary;
78 private int $shieldRuntimeId;
79
80 private SkinAdapter $skinAdapter;
81
82 public function __construct(){
83 //TODO: inject stuff via constructor
84 $this->blockItemIdMap = BlockItemIdMap::getInstance();
85
86 $canonicalBlockStatesRaw = Filesystem::fileGetContents(BedrockDataFiles::CANONICAL_BLOCK_STATES_NBT);
87 $metaMappingRaw = Filesystem::fileGetContents(BedrockDataFiles::BLOCK_STATE_META_MAP_JSON);
88 $this->blockTranslator = new BlockTranslator(
89 BlockStateDictionary::loadFromString($canonicalBlockStatesRaw, $metaMappingRaw),
90 GlobalBlockStateHandlers::getSerializer()
91 );
92
93 $this->itemTypeDictionary = ItemTypeDictionaryFromDataHelper::loadFromString(Filesystem::fileGetContents(BedrockDataFiles::REQUIRED_ITEM_LIST_JSON));
94 $this->shieldRuntimeId = $this->itemTypeDictionary->fromStringId(ItemTypeNames::SHIELD);
95
96 $this->itemTranslator = new ItemTranslator(
97 $this->itemTypeDictionary,
98 $this->blockTranslator->getBlockStateDictionary(),
99 GlobalItemDataHandlers::getSerializer(),
100 GlobalItemDataHandlers::getDeserializer(),
101 $this->blockItemIdMap
102 );
103
104 $this->skinAdapter = new LegacySkinAdapter();
105 }
106
107 public function getBlockTranslator() : BlockTranslator{ return $this->blockTranslator; }
108
109 public function getItemTypeDictionary() : ItemTypeDictionary{ return $this->itemTypeDictionary; }
110
111 public function getItemTranslator() : ItemTranslator{ return $this->itemTranslator; }
112
113 public function getSkinAdapter() : SkinAdapter{ return $this->skinAdapter; }
114
115 public function setSkinAdapter(SkinAdapter $skinAdapter) : void{
116 $this->skinAdapter = $skinAdapter;
117 }
118
125 public function coreGameModeToProtocol(GameMode $gamemode) : int{
126 return match($gamemode){
127 GameMode::SURVIVAL => ProtocolGameMode::SURVIVAL,
128 //TODO: native spectator support
129 GameMode::CREATIVE, GameMode::SPECTATOR => ProtocolGameMode::CREATIVE,
130 GameMode::ADVENTURE => ProtocolGameMode::ADVENTURE,
131 };
132 }
133
134 public function protocolGameModeToCore(int $gameMode) : ?GameMode{
135 return match($gameMode){
136 ProtocolGameMode::SURVIVAL => GameMode::SURVIVAL,
137 ProtocolGameMode::CREATIVE => GameMode::CREATIVE,
138 ProtocolGameMode::ADVENTURE => GameMode::ADVENTURE,
139 ProtocolGameMode::SURVIVAL_VIEWER, ProtocolGameMode::CREATIVE_VIEWER => GameMode::SPECTATOR,
140 //TODO: native spectator support
141 default => null,
142 };
143 }
144
145 public function coreRecipeIngredientToNet(?RecipeIngredient $ingredient) : ProtocolRecipeIngredient{
146 if($ingredient === null){
147 return new ProtocolRecipeIngredient(null, 0);
148 }
149 if($ingredient instanceof MetaWildcardRecipeIngredient){
150 $id = $this->itemTypeDictionary->fromStringId($ingredient->getItemId());
151 $meta = self::RECIPE_INPUT_WILDCARD_META;
152 $descriptor = new IntIdMetaItemDescriptor($id, $meta);
153 }elseif($ingredient instanceof ExactRecipeIngredient){
154 $item = $ingredient->getItem();
155 [$id, $meta, $blockRuntimeId] = $this->itemTranslator->toNetworkId($item);
156 if($blockRuntimeId !== null){
157 $meta = $this->blockTranslator->getBlockStateDictionary()->getMetaFromStateId($blockRuntimeId);
158 if($meta === null){
159 throw new AssumptionFailedError("Every block state should have an associated meta value");
160 }
161 }
162 $descriptor = new IntIdMetaItemDescriptor($id, $meta);
163 }elseif($ingredient instanceof TagWildcardRecipeIngredient){
164 $descriptor = new TagItemDescriptor($ingredient->getTagName());
165 }else{
166 throw new \LogicException("Unsupported recipe ingredient type " . get_class($ingredient) . ", only " . ExactRecipeIngredient::class . " and " . MetaWildcardRecipeIngredient::class . " are supported");
167 }
168
169 return new ProtocolRecipeIngredient($descriptor, 1);
170 }
171
172 public function netRecipeIngredientToCore(ProtocolRecipeIngredient $ingredient) : ?RecipeIngredient{
173 $descriptor = $ingredient->getDescriptor();
174 if($descriptor === null){
175 return null;
176 }
177
178 if($descriptor instanceof TagItemDescriptor){
179 return new TagWildcardRecipeIngredient($descriptor->getTag());
180 }
181
182 if($descriptor instanceof IntIdMetaItemDescriptor){
183 $stringId = $this->itemTypeDictionary->fromIntId($descriptor->getId());
184 $meta = $descriptor->getMeta();
185 }elseif($descriptor instanceof StringIdMetaItemDescriptor){
186 $stringId = $descriptor->getId();
187 $meta = $descriptor->getMeta();
188 }else{
189 throw new \LogicException("Unsupported conversion of recipe ingredient to core item stack");
190 }
191
192 if($meta === self::RECIPE_INPUT_WILDCARD_META){
193 return new MetaWildcardRecipeIngredient($stringId);
194 }
195
196 $blockRuntimeId = null;
197 if(($blockId = $this->blockItemIdMap->lookupBlockId($stringId)) !== null){
198 $blockRuntimeId = $this->blockTranslator->getBlockStateDictionary()->lookupStateIdFromIdMeta($blockId, $meta);
199 if($blockRuntimeId !== null){
200 $meta = 0;
201 }
202 }
203 $result = $this->itemTranslator->fromNetworkId(
204 $this->itemTypeDictionary->fromStringId($stringId),
205 $meta,
206 $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID
207 );
208 return new ExactRecipeIngredient($result);
209 }
210
215 protected function stripBlockEntityNBT(CompoundTag $tag) : bool{
216 if(($tag->getTag(Item::TAG_BLOCK_ENTITY_TAG)) !== null){
217 //client doesn't use this tag, so it's fine to delete completely
218 $tag->removeTag(Item::TAG_BLOCK_ENTITY_TAG);
219 return true;
220 }
221 return false;
222 }
223
229 protected function stripContainedItemNonVisualNBT(CompoundTag $tag) : bool{
230 try{
231 $blockEntityInventoryTag = $tag->getListTag(ContainerTile::TAG_ITEMS, CompoundTag::class);
233 return false;
234 }
235 if($blockEntityInventoryTag !== null && $blockEntityInventoryTag->count() > 0){
236 $stripped = new ListTag();
237
238 foreach($blockEntityInventoryTag as $itemTag){
239 try{
240 $containedItem = Item::nbtDeserialize($itemTag);
241 $customName = $containedItem->getCustomName();
242 $containedItem->clearNamedTag();
243 $containedItem->setCustomName($customName);
244 $stripped->push($containedItem->nbtSerialize());
245 }catch(SavedDataLoadingException){
246 continue;
247 }
248 }
249 $tag->setTag(ContainerTile::TAG_ITEMS, $stripped);
250 return true;
251 }
252 return false;
253 }
254
260 protected function hashNBT(Tag $tag) : string{
261 $encoded = (new LittleEndianNbtSerializer())->write(new TreeRoot($tag));
262 return hash('sha256', $encoded, binary: true);
263 }
264
273 protected function cleanupUnnecessaryItemNBT(CompoundTag $original) : CompoundTag{
274 $tag = clone $original;
275 $anythingStripped = false;
276 foreach([
277 $this->stripContainedItemNonVisualNBT($tag),
278 $this->stripBlockEntityNBT($tag)
279 ] as $stripped){
280 $anythingStripped = $anythingStripped || $stripped;
281 }
282
283 if($anythingStripped){
284 $tag->setByteArray(self::PM_FULL_NBT_HASH_TAG, $this->hashNBT($original));
285 }
286 return $tag;
287 }
288
289 public function coreItemStackToNet(Item $itemStack) : ItemStack{
290 if($itemStack->isNull()){
291 return ItemStack::null();
292 }
293 $nbt = $itemStack->getNamedTag();
294 if($nbt->count() === 0){
295 $nbt = null;
296 }else{
297 $nbt = $this->cleanupUnnecessaryItemNBT($nbt);
298 }
299
300 $idMeta = $this->itemTranslator->toNetworkIdQuiet($itemStack);
301 if($idMeta === null){
302 //Display unmapped items as INFO_UPDATE, but stick something in their NBT to make sure they don't stack with
303 //other unmapped items.
304 [$id, $meta, $blockRuntimeId] = $this->itemTranslator->toNetworkId(VanillaBlocks::INFO_UPDATE()->asItem());
305 if($nbt === null){
306 $nbt = new CompoundTag();
307 }
308 $nbt->setLong(self::PM_ID_TAG, $itemStack->getStateId());
309 }else{
310 [$id, $meta, $blockRuntimeId] = $idMeta;
311 }
312
313 $extraData = $id === $this->shieldRuntimeId ?
314 new ItemStackExtraDataShield($nbt, canPlaceOn: [], canDestroy: [], blockingTick: 0) :
315 new ItemStackExtraData($nbt, canPlaceOn: [], canDestroy: []);
316 $extraDataSerializer = new ByteBufferWriter();
317 $extraData->write($extraDataSerializer);
318
319 return new ItemStack(
320 $id,
321 $meta,
322 $itemStack->getCount(),
323 $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID,
324 $extraDataSerializer->getData(),
325 );
326 }
327
336 public function netItemStackToCore(ItemStack $itemStack) : Item{
337 if($itemStack->getId() === 0){
338 return VanillaItems::AIR();
339 }
340 $extraData = $this->deserializeItemStackExtraData($itemStack->getRawExtraData(), $itemStack->getId());
341
342 $compound = $extraData->getNbt();
343
344 $itemResult = $this->itemTranslator->fromNetworkId($itemStack->getId(), $itemStack->getMeta(), $itemStack->getBlockRuntimeId());
345
346 if($compound !== null){
347 $compound = clone $compound;
348 }
349
350 $itemResult->setCount($itemStack->getCount());
351 if($compound !== null){
352 try{
353 $itemResult->setNamedTag($compound);
354 }catch(NbtException $e){
355 throw TypeConversionException::wrap($e, "Bad itemstack NBT data");
356 }
357 }
358
359 return $itemResult;
360 }
361
362 public function deserializeItemStackExtraData(string $extraData, int $id) : ItemStackExtraData{
363 $extraDataDeserializer = new ByteBufferReader($extraData);
364 return $id === $this->shieldRuntimeId ?
365 ItemStackExtraDataShield::read($extraDataDeserializer) :
366 ItemStackExtraData::read($extraDataDeserializer);
367 }
368}
setTag(string $name, Tag $tag)