59 private const PM_ID_TAG =
"___Id___";
61 private const RECIPE_INPUT_WILDCARD_META = 0x7fff;
67 private int $shieldRuntimeId;
71 public function __construct(){
73 $this->blockItemIdMap = BlockItemIdMap::getInstance();
75 $canonicalBlockStatesRaw = Filesystem::fileGetContents(BedrockDataFiles::CANONICAL_BLOCK_STATES_NBT);
76 $metaMappingRaw = Filesystem::fileGetContents(BedrockDataFiles::BLOCK_STATE_META_MAP_JSON);
78 BlockStateDictionary::loadFromString($canonicalBlockStatesRaw, $metaMappingRaw),
79 GlobalBlockStateHandlers::getSerializer()
82 $this->itemTypeDictionary = ItemTypeDictionaryFromDataHelper::loadFromString(Filesystem::fileGetContents(BedrockDataFiles::REQUIRED_ITEM_LIST_JSON));
83 $this->shieldRuntimeId = $this->itemTypeDictionary->fromStringId(ItemTypeNames::SHIELD);
86 $this->itemTypeDictionary,
87 $this->blockTranslator->getBlockStateDictionary(),
88 GlobalItemDataHandlers::getSerializer(),
89 GlobalItemDataHandlers::getDeserializer(),
96 public function getBlockTranslator() :
BlockTranslator{
return $this->blockTranslator; }
98 public function getItemTypeDictionary() :
ItemTypeDictionary{
return $this->itemTypeDictionary; }
100 public function getItemTranslator() :
ItemTranslator{
return $this->itemTranslator; }
102 public function getSkinAdapter() :
SkinAdapter{
return $this->skinAdapter; }
104 public function setSkinAdapter(
SkinAdapter $skinAdapter) :
void{
105 $this->skinAdapter = $skinAdapter;
115 return match($gamemode){
116 GameMode::SURVIVAL => ProtocolGameMode::SURVIVAL,
118 GameMode::CREATIVE, GameMode::SPECTATOR => ProtocolGameMode::CREATIVE,
119 GameMode::ADVENTURE => ProtocolGameMode::ADVENTURE,
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,
134 public function coreRecipeIngredientToNet(?RecipeIngredient $ingredient) : ProtocolRecipeIngredient{
135 if($ingredient === null){
136 return new ProtocolRecipeIngredient(
null, 0);
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);
148 throw new AssumptionFailedError(
"Every block state should have an associated meta value");
151 $descriptor =
new IntIdMetaItemDescriptor($id, $meta);
152 }elseif($ingredient instanceof TagWildcardRecipeIngredient){
153 $descriptor =
new TagItemDescriptor($ingredient->getTagName());
155 throw new \LogicException(
"Unsupported recipe ingredient type " . get_class($ingredient) .
", only " . ExactRecipeIngredient::class .
" and " . MetaWildcardRecipeIngredient::class .
" are supported");
158 return new ProtocolRecipeIngredient($descriptor, 1);
161 public function netRecipeIngredientToCore(ProtocolRecipeIngredient $ingredient) : ?RecipeIngredient{
162 $descriptor = $ingredient->getDescriptor();
163 if($descriptor ===
null){
167 if($descriptor instanceof TagItemDescriptor){
168 return new TagWildcardRecipeIngredient($descriptor->getTag());
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();
178 throw new \LogicException(
"Unsupported conversion of recipe ingredient to core item stack");
181 if($meta === self::RECIPE_INPUT_WILDCARD_META){
182 return new MetaWildcardRecipeIngredient($stringId);
185 $blockRuntimeId =
null;
186 if(($blockId = $this->blockItemIdMap->lookupBlockId($stringId)) !==
null){
187 $blockRuntimeId = $this->blockTranslator->getBlockStateDictionary()->lookupStateIdFromIdMeta($blockId, $meta);
188 if($blockRuntimeId !==
null){
192 $result = $this->itemTranslator->fromNetworkId(
193 $this->itemTypeDictionary->fromStringId($stringId),
195 $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID
197 return new ExactRecipeIngredient($result);
200 public function coreItemStackToNet(Item $itemStack) : ItemStack{
201 if($itemStack->isNull()){
202 return ItemStack::null();
204 $nbt = $itemStack->getNamedTag();
205 if($nbt->count() === 0){
211 $idMeta = $this->itemTranslator->toNetworkIdQuiet($itemStack);
212 if($idMeta ===
null){
215 [$id, $meta, $blockRuntimeId] = $this->itemTranslator->toNetworkId(VanillaBlocks::INFO_UPDATE()->asItem());
217 $nbt =
new CompoundTag();
219 $nbt->setLong(self::PM_ID_TAG, $itemStack->getStateId());
221 [$id, $meta, $blockRuntimeId] = $idMeta;
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);
230 return new ItemStack(
233 $itemStack->getCount(),
234 $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID,
235 $extraDataSerializer->getBuffer(),
248 if($itemStack->getId() === 0){
249 return VanillaItems::AIR();
251 $extraData = $this->deserializeItemStackExtraData($itemStack->getRawExtraData(), $itemStack->getId());
253 $compound = $extraData->getNbt();
255 $itemResult = $this->itemTranslator->fromNetworkId($itemStack->getId(), $itemStack->getMeta(), $itemStack->getBlockRuntimeId());
257 if($compound !==
null){
258 $compound = clone $compound;
261 $itemResult->setCount($itemStack->getCount());
262 if($compound !==
null){
264 $itemResult->setNamedTag($compound);
265 }
catch(NbtException $e){
266 throw TypeConversionException::wrap($e,
"Bad itemstack NBT data");
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);