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(Container::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);