67 private const PM_ID_TAG =
"___Id___";
68 private const PM_FULL_NBT_HASH_TAG =
"___FullNbtHash___";
70 private const RECIPE_INPUT_WILDCARD_META = 0x7fff;
76 private int $shieldRuntimeId;
80 public function __construct(){
82 $this->blockItemIdMap = BlockItemIdMap::getInstance();
84 $canonicalBlockStatesRaw = Filesystem::fileGetContents(BedrockDataFiles::CANONICAL_BLOCK_STATES_NBT);
85 $metaMappingRaw = Filesystem::fileGetContents(BedrockDataFiles::BLOCK_STATE_META_MAP_JSON);
87 BlockStateDictionary::loadFromString($canonicalBlockStatesRaw, $metaMappingRaw),
88 GlobalBlockStateHandlers::getSerializer()
91 $this->itemTypeDictionary = ItemTypeDictionaryFromDataHelper::loadFromString(Filesystem::fileGetContents(BedrockDataFiles::REQUIRED_ITEM_LIST_JSON));
92 $this->shieldRuntimeId = $this->itemTypeDictionary->fromStringId(ItemTypeNames::SHIELD);
95 $this->itemTypeDictionary,
96 $this->blockTranslator->getBlockStateDictionary(),
97 GlobalItemDataHandlers::getSerializer(),
98 GlobalItemDataHandlers::getDeserializer(),
105 public function getBlockTranslator() :
BlockTranslator{
return $this->blockTranslator; }
107 public function getItemTypeDictionary() :
ItemTypeDictionary{
return $this->itemTypeDictionary; }
109 public function getItemTranslator() :
ItemTranslator{
return $this->itemTranslator; }
111 public function getSkinAdapter() :
SkinAdapter{
return $this->skinAdapter; }
113 public function setSkinAdapter(
SkinAdapter $skinAdapter) :
void{
114 $this->skinAdapter = $skinAdapter;
124 return match($gamemode){
125 GameMode::SURVIVAL => ProtocolGameMode::SURVIVAL,
127 GameMode::CREATIVE, GameMode::SPECTATOR => ProtocolGameMode::CREATIVE,
128 GameMode::ADVENTURE => ProtocolGameMode::ADVENTURE,
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,
143 public function coreRecipeIngredientToNet(?RecipeIngredient $ingredient) : ProtocolRecipeIngredient{
144 if($ingredient === null){
145 return new ProtocolRecipeIngredient(
null, 0);
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);
157 throw new AssumptionFailedError(
"Every block state should have an associated meta value");
160 $descriptor =
new IntIdMetaItemDescriptor($id, $meta);
161 }elseif($ingredient instanceof TagWildcardRecipeIngredient){
162 $descriptor =
new TagItemDescriptor($ingredient->getTagName());
164 throw new \LogicException(
"Unsupported recipe ingredient type " . get_class($ingredient) .
", only " . ExactRecipeIngredient::class .
" and " . MetaWildcardRecipeIngredient::class .
" are supported");
167 return new ProtocolRecipeIngredient($descriptor, 1);
170 public function netRecipeIngredientToCore(ProtocolRecipeIngredient $ingredient) : ?RecipeIngredient{
171 $descriptor = $ingredient->getDescriptor();
172 if($descriptor ===
null){
176 if($descriptor instanceof TagItemDescriptor){
177 return new TagWildcardRecipeIngredient($descriptor->getTag());
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();
187 throw new \LogicException(
"Unsupported conversion of recipe ingredient to core item stack");
190 if($meta === self::RECIPE_INPUT_WILDCARD_META){
191 return new MetaWildcardRecipeIngredient($stringId);
194 $blockRuntimeId =
null;
195 if(($blockId = $this->blockItemIdMap->lookupBlockId($stringId)) !==
null){
196 $blockRuntimeId = $this->blockTranslator->getBlockStateDictionary()->lookupStateIdFromIdMeta($blockId, $meta);
197 if($blockRuntimeId !==
null){
201 $result = $this->itemTranslator->fromNetworkId(
202 $this->itemTypeDictionary->fromStringId($stringId),
204 $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID
206 return new ExactRecipeIngredient($result);
214 if(($tag->getTag(
Item::TAG_BLOCK_ENTITY_TAG)) !== null){
216 $tag->
removeTag(Item::TAG_BLOCK_ENTITY_TAG);
229 ($blockEntityInventoryTag = $tag->getTag(
ContainerTile::TAG_ITEMS)) !== null &&
230 $blockEntityInventoryTag instanceof
ListTag &&
231 $blockEntityInventoryTag->getTagType() ===
NBT::TAG_Compound &&
232 $blockEntityInventoryTag->count() > 0
237 foreach($blockEntityInventoryTag as $itemTag){
239 $containedItem = Item::nbtDeserialize($itemTag);
240 $customName = $containedItem->getCustomName();
241 $containedItem->clearNamedTag();
242 $containedItem->setCustomName($customName);
243 $stripped->push($containedItem->nbtSerialize());
248 $tag->
setTag(ContainerTile::TAG_ITEMS, $stripped);
261 return hash(
'sha256', $encoded, binary:
true);
273 $tag = clone $original;
274 $anythingStripped =
false;
276 $this->stripContainedItemNonVisualNBT($tag),
277 $this->stripBlockEntityNBT($tag)
279 $anythingStripped = $anythingStripped || $stripped;
282 if($anythingStripped){
283 $tag->setByteArray(self::PM_FULL_NBT_HASH_TAG, $this->hashNBT($original));
288 public function coreItemStackToNet(Item $itemStack) : ItemStack{
289 if($itemStack->isNull()){
290 return ItemStack::null();
292 $nbt = $itemStack->getNamedTag();
293 if($nbt->count() === 0){
296 $nbt = $this->cleanupUnnecessaryItemNBT($nbt);
299 $idMeta = $this->itemTranslator->toNetworkIdQuiet($itemStack);
300 if($idMeta ===
null){
303 [$id, $meta, $blockRuntimeId] = $this->itemTranslator->toNetworkId(VanillaBlocks::INFO_UPDATE()->asItem());
305 $nbt =
new CompoundTag();
307 $nbt->setLong(self::PM_ID_TAG, $itemStack->getStateId());
309 [$id, $meta, $blockRuntimeId] = $idMeta;
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);
318 return new ItemStack(
321 $itemStack->getCount(),
322 $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID,
323 $extraDataSerializer->getBuffer(),
336 if($itemStack->getId() === 0){
337 return VanillaItems::AIR();
339 $extraData = $this->deserializeItemStackExtraData($itemStack->getRawExtraData(), $itemStack->getId());
341 $compound = $extraData->getNbt();
343 $itemResult = $this->itemTranslator->fromNetworkId($itemStack->getId(), $itemStack->getMeta(), $itemStack->getBlockRuntimeId());
345 if($compound !==
null){
346 $compound = clone $compound;
349 $itemResult->setCount($itemStack->getCount());
350 if($compound !==
null){
352 $itemResult->setNamedTag($compound);
353 }
catch(NbtException $e){
354 throw TypeConversionException::wrap($e,
"Bad itemstack NBT data");
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);