PocketMine-MP 5.21.2 git-a6534ecbbbcf369264567d27e5ed70f7f5be9816
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
48use pocketmine\player\GameMode;
51use pocketmine\utils\SingletonTrait;
54use function get_class;
55
57 use SingletonTrait;
58
59 private const PM_ID_TAG = "___Id___";
60
61 private const RECIPE_INPUT_WILDCARD_META = 0x7fff;
62
63 private BlockItemIdMap $blockItemIdMap;
64 private BlockTranslator $blockTranslator;
65 private ItemTranslator $itemTranslator;
66 private ItemTypeDictionary $itemTypeDictionary;
67 private int $shieldRuntimeId;
68
69 private SkinAdapter $skinAdapter;
70
71 public function __construct(){
72 //TODO: inject stuff via constructor
73 $this->blockItemIdMap = BlockItemIdMap::getInstance();
74
75 $canonicalBlockStatesRaw = Filesystem::fileGetContents(BedrockDataFiles::CANONICAL_BLOCK_STATES_NBT);
76 $metaMappingRaw = Filesystem::fileGetContents(BedrockDataFiles::BLOCK_STATE_META_MAP_JSON);
77 $this->blockTranslator = new BlockTranslator(
78 BlockStateDictionary::loadFromString($canonicalBlockStatesRaw, $metaMappingRaw),
79 GlobalBlockStateHandlers::getSerializer()
80 );
81
82 $this->itemTypeDictionary = ItemTypeDictionaryFromDataHelper::loadFromString(Filesystem::fileGetContents(BedrockDataFiles::REQUIRED_ITEM_LIST_JSON));
83 $this->shieldRuntimeId = $this->itemTypeDictionary->fromStringId(ItemTypeNames::SHIELD);
84
85 $this->itemTranslator = new ItemTranslator(
86 $this->itemTypeDictionary,
87 $this->blockTranslator->getBlockStateDictionary(),
88 GlobalItemDataHandlers::getSerializer(),
89 GlobalItemDataHandlers::getDeserializer(),
90 $this->blockItemIdMap
91 );
92
93 $this->skinAdapter = new LegacySkinAdapter();
94 }
95
96 public function getBlockTranslator() : BlockTranslator{ return $this->blockTranslator; }
97
98 public function getItemTypeDictionary() : ItemTypeDictionary{ return $this->itemTypeDictionary; }
99
100 public function getItemTranslator() : ItemTranslator{ return $this->itemTranslator; }
101
102 public function getSkinAdapter() : SkinAdapter{ return $this->skinAdapter; }
103
104 public function setSkinAdapter(SkinAdapter $skinAdapter) : void{
105 $this->skinAdapter = $skinAdapter;
106 }
107
114 public function coreGameModeToProtocol(GameMode $gamemode) : int{
115 return match($gamemode){
116 GameMode::SURVIVAL => ProtocolGameMode::SURVIVAL,
117 //TODO: native spectator support
118 GameMode::CREATIVE, GameMode::SPECTATOR => ProtocolGameMode::CREATIVE,
119 GameMode::ADVENTURE => ProtocolGameMode::ADVENTURE,
120 };
121 }
122
123 public function protocolGameModeToCore(int $gameMode) : ?GameMode{
124 return match($gameMode){
125 ProtocolGameMode::SURVIVAL => GameMode::SURVIVAL,
126 ProtocolGameMode::CREATIVE => GameMode::CREATIVE,
127 ProtocolGameMode::ADVENTURE => GameMode::ADVENTURE,
128 ProtocolGameMode::SURVIVAL_VIEWER, ProtocolGameMode::CREATIVE_VIEWER => GameMode::SPECTATOR,
129 //TODO: native spectator support
130 default => null,
131 };
132 }
133
134 public function coreRecipeIngredientToNet(?RecipeIngredient $ingredient) : ProtocolRecipeIngredient{
135 if($ingredient === null){
136 return new ProtocolRecipeIngredient(null, 0);
137 }
138 if($ingredient instanceof MetaWildcardRecipeIngredient){
139 $id = $this->itemTypeDictionary->fromStringId($ingredient->getItemId());
140 $meta = self::RECIPE_INPUT_WILDCARD_META;
141 $descriptor = new IntIdMetaItemDescriptor($id, $meta);
142 }elseif($ingredient instanceof ExactRecipeIngredient){
143 $item = $ingredient->getItem();
144 [$id, $meta, $blockRuntimeId] = $this->itemTranslator->toNetworkId($item);
145 if($blockRuntimeId !== null){
146 $meta = $this->blockTranslator->getBlockStateDictionary()->getMetaFromStateId($blockRuntimeId);
147 if($meta === null){
148 throw new AssumptionFailedError("Every block state should have an associated meta value");
149 }
150 }
151 $descriptor = new IntIdMetaItemDescriptor($id, $meta);
152 }elseif($ingredient instanceof TagWildcardRecipeIngredient){
153 $descriptor = new TagItemDescriptor($ingredient->getTagName());
154 }else{
155 throw new \LogicException("Unsupported recipe ingredient type " . get_class($ingredient) . ", only " . ExactRecipeIngredient::class . " and " . MetaWildcardRecipeIngredient::class . " are supported");
156 }
157
158 return new ProtocolRecipeIngredient($descriptor, 1);
159 }
160
161 public function netRecipeIngredientToCore(ProtocolRecipeIngredient $ingredient) : ?RecipeIngredient{
162 $descriptor = $ingredient->getDescriptor();
163 if($descriptor === null){
164 return null;
165 }
166
167 if($descriptor instanceof TagItemDescriptor){
168 return new TagWildcardRecipeIngredient($descriptor->getTag());
169 }
170
171 if($descriptor instanceof IntIdMetaItemDescriptor){
172 $stringId = $this->itemTypeDictionary->fromIntId($descriptor->getId());
173 $meta = $descriptor->getMeta();
174 }elseif($descriptor instanceof StringIdMetaItemDescriptor){
175 $stringId = $descriptor->getId();
176 $meta = $descriptor->getMeta();
177 }else{
178 throw new \LogicException("Unsupported conversion of recipe ingredient to core item stack");
179 }
180
181 if($meta === self::RECIPE_INPUT_WILDCARD_META){
182 return new MetaWildcardRecipeIngredient($stringId);
183 }
184
185 $blockRuntimeId = null;
186 if(($blockId = $this->blockItemIdMap->lookupBlockId($stringId)) !== null){
187 $blockRuntimeId = $this->blockTranslator->getBlockStateDictionary()->lookupStateIdFromIdMeta($blockId, $meta);
188 if($blockRuntimeId !== null){
189 $meta = 0;
190 }
191 }
192 $result = $this->itemTranslator->fromNetworkId(
193 $this->itemTypeDictionary->fromStringId($stringId),
194 $meta,
195 $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID
196 );
197 return new ExactRecipeIngredient($result);
198 }
199
200 public function coreItemStackToNet(Item $itemStack) : ItemStack{
201 if($itemStack->isNull()){
202 return ItemStack::null();
203 }
204 $nbt = $itemStack->getNamedTag();
205 if($nbt->count() === 0){
206 $nbt = null;
207 }else{
208 $nbt = clone $nbt;
209 }
210
211 $idMeta = $this->itemTranslator->toNetworkIdQuiet($itemStack);
212 if($idMeta === null){
213 //Display unmapped items as INFO_UPDATE, but stick something in their NBT to make sure they don't stack with
214 //other unmapped items.
215 [$id, $meta, $blockRuntimeId] = $this->itemTranslator->toNetworkId(VanillaBlocks::INFO_UPDATE()->asItem());
216 if($nbt === null){
217 $nbt = new CompoundTag();
218 }
219 $nbt->setLong(self::PM_ID_TAG, $itemStack->getStateId());
220 }else{
221 [$id, $meta, $blockRuntimeId] = $idMeta;
222 }
223
224 $extraData = $id === $this->shieldRuntimeId ?
225 new ItemStackExtraDataShield($nbt, canPlaceOn: [], canDestroy: [], blockingTick: 0) :
226 new ItemStackExtraData($nbt, canPlaceOn: [], canDestroy: []);
227 $extraDataSerializer = PacketSerializer::encoder();
228 $extraData->write($extraDataSerializer);
229
230 return new ItemStack(
231 $id,
232 $meta,
233 $itemStack->getCount(),
234 $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID,
235 $extraDataSerializer->getBuffer(),
236 );
237 }
238
247 public function netItemStackToCore(ItemStack $itemStack) : Item{
248 if($itemStack->getId() === 0){
249 return VanillaItems::AIR();
250 }
251 $extraData = $this->deserializeItemStackExtraData($itemStack->getRawExtraData(), $itemStack->getId());
252
253 $compound = $extraData->getNbt();
254
255 $itemResult = $this->itemTranslator->fromNetworkId($itemStack->getId(), $itemStack->getMeta(), $itemStack->getBlockRuntimeId());
256
257 if($compound !== null){
258 $compound = clone $compound;
259 }
260
261 $itemResult->setCount($itemStack->getCount());
262 if($compound !== null){
263 try{
264 $itemResult->setNamedTag($compound);
265 }catch(NbtException $e){
266 throw TypeConversionException::wrap($e, "Bad itemstack NBT data");
267 }
268 }
269
270 return $itemResult;
271 }
272
273 public function deserializeItemStackExtraData(string $extraData, int $id) : ItemStackExtraData{
274 $extraDataDeserializer = PacketSerializer::decoder($extraData, 0);
275 return $id === $this->shieldRuntimeId ?
276 ItemStackExtraDataShield::read($extraDataDeserializer) :
277 ItemStackExtraData::read($extraDataDeserializer);
278 }
279}