69 private const PM_ID_TAG =
"___Id___";
70 private const PM_FULL_NBT_HASH_TAG =
"___FullNbtHash___";
72 private const RECIPE_INPUT_WILDCARD_META = 0x7fff;
78 private int $shieldRuntimeId;
82 public function __construct(){
84 $this->blockItemIdMap = BlockItemIdMap::getInstance();
86 $canonicalBlockStatesRaw = Filesystem::fileGetContents(BedrockDataFiles::CANONICAL_BLOCK_STATES_NBT);
87 $metaMappingRaw = Filesystem::fileGetContents(BedrockDataFiles::BLOCK_STATE_META_MAP_JSON);
89 BlockStateDictionary::loadFromString($canonicalBlockStatesRaw, $metaMappingRaw),
90 GlobalBlockStateHandlers::getSerializer()
93 $this->itemTypeDictionary = ItemTypeDictionaryFromDataHelper::loadFromString(Filesystem::fileGetContents(BedrockDataFiles::REQUIRED_ITEM_LIST_JSON));
94 $this->shieldRuntimeId = $this->itemTypeDictionary->fromStringId(ItemTypeNames::SHIELD);
97 $this->itemTypeDictionary,
98 $this->blockTranslator->getBlockStateDictionary(),
99 GlobalItemDataHandlers::getSerializer(),
100 GlobalItemDataHandlers::getDeserializer(),
101 $this->blockItemIdMap
107 public function getBlockTranslator() :
BlockTranslator{
return $this->blockTranslator; }
109 public function getItemTypeDictionary() :
ItemTypeDictionary{
return $this->itemTypeDictionary; }
111 public function getItemTranslator() :
ItemTranslator{
return $this->itemTranslator; }
113 public function getSkinAdapter() :
SkinAdapter{
return $this->skinAdapter; }
115 public function setSkinAdapter(
SkinAdapter $skinAdapter) :
void{
116 $this->skinAdapter = $skinAdapter;
126 return match($gamemode){
127 GameMode::SURVIVAL => ProtocolGameMode::SURVIVAL,
129 GameMode::CREATIVE, GameMode::SPECTATOR => ProtocolGameMode::CREATIVE,
130 GameMode::ADVENTURE => ProtocolGameMode::ADVENTURE,
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,
145 public function coreRecipeIngredientToNet(?RecipeIngredient $ingredient) : ProtocolRecipeIngredient{
146 if($ingredient === null){
147 return new ProtocolRecipeIngredient(
null, 0);
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);
159 throw new AssumptionFailedError(
"Every block state should have an associated meta value");
162 $descriptor =
new IntIdMetaItemDescriptor($id, $meta);
163 }elseif($ingredient instanceof TagWildcardRecipeIngredient){
164 $descriptor =
new TagItemDescriptor($ingredient->getTagName());
166 throw new \LogicException(
"Unsupported recipe ingredient type " . get_class($ingredient) .
", only " . ExactRecipeIngredient::class .
" and " . MetaWildcardRecipeIngredient::class .
" are supported");
169 return new ProtocolRecipeIngredient($descriptor, 1);
172 public function netRecipeIngredientToCore(ProtocolRecipeIngredient $ingredient) : ?RecipeIngredient{
173 $descriptor = $ingredient->getDescriptor();
174 if($descriptor ===
null){
178 if($descriptor instanceof TagItemDescriptor){
179 return new TagWildcardRecipeIngredient($descriptor->getTag());
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();
189 throw new \LogicException(
"Unsupported conversion of recipe ingredient to core item stack");
192 if($meta === self::RECIPE_INPUT_WILDCARD_META){
193 return new MetaWildcardRecipeIngredient($stringId);
196 $blockRuntimeId =
null;
197 if(($blockId = $this->blockItemIdMap->lookupBlockId($stringId)) !==
null){
198 $blockRuntimeId = $this->blockTranslator->getBlockStateDictionary()->lookupStateIdFromIdMeta($blockId, $meta);
199 if($blockRuntimeId !==
null){
203 $result = $this->itemTranslator->fromNetworkId(
204 $this->itemTypeDictionary->fromStringId($stringId),
206 $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID
208 return new ExactRecipeIngredient($result);
216 if(($tag->getTag(
Item::TAG_BLOCK_ENTITY_TAG)) !== null){
218 $tag->
removeTag(Item::TAG_BLOCK_ENTITY_TAG);
235 if($blockEntityInventoryTag !==
null && $blockEntityInventoryTag->count() > 0){
236 $stripped = new ListTag();
238 foreach($blockEntityInventoryTag as $itemTag){
240 $containedItem = Item::nbtDeserialize($itemTag);
241 $customName = $containedItem->getCustomName();
242 $containedItem->clearNamedTag();
243 $containedItem->setCustomName($customName);
244 $stripped->push($containedItem->nbtSerialize());
245 }catch(SavedDataLoadingException){
249 $tag->
setTag(ContainerTile::TAG_ITEMS, $stripped);
262 return hash(
'sha256', $encoded, binary:
true);
274 $tag = clone $original;
275 $anythingStripped =
false;
277 $this->stripContainedItemNonVisualNBT($tag),
278 $this->stripBlockEntityNBT($tag)
280 $anythingStripped = $anythingStripped || $stripped;
283 if($anythingStripped){
284 $tag->setByteArray(self::PM_FULL_NBT_HASH_TAG, $this->hashNBT($original));
289 public function coreItemStackToNet(Item $itemStack) : ItemStack{
290 if($itemStack->isNull()){
291 return ItemStack::null();
293 $nbt = $itemStack->getNamedTag();
294 if($nbt->count() === 0){
297 $nbt = $this->cleanupUnnecessaryItemNBT($nbt);
300 $idMeta = $this->itemTranslator->toNetworkIdQuiet($itemStack);
301 if($idMeta ===
null){
304 [$id, $meta, $blockRuntimeId] = $this->itemTranslator->toNetworkId(VanillaBlocks::INFO_UPDATE()->asItem());
306 $nbt =
new CompoundTag();
308 $nbt->setLong(self::PM_ID_TAG, $itemStack->getStateId());
310 [$id, $meta, $blockRuntimeId] = $idMeta;
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);
319 return new ItemStack(
322 $itemStack->getCount(),
323 $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID,
324 $extraDataSerializer->getData(),
337 if($itemStack->getId() === 0){
338 return VanillaItems::AIR();
340 $extraData = $this->deserializeItemStackExtraData($itemStack->getRawExtraData(), $itemStack->getId());
342 $compound = $extraData->getNbt();
344 $itemResult = $this->itemTranslator->fromNetworkId($itemStack->getId(), $itemStack->getMeta(), $itemStack->getBlockRuntimeId());
346 if($compound !==
null){
347 $compound = clone $compound;
350 $itemResult->setCount($itemStack->getCount());
351 if($compound !==
null){
353 $itemResult->setNamedTag($compound);
354 }
catch(NbtException $e){
355 throw TypeConversionException::wrap($e,
"Bad itemstack NBT data");
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);