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