55 if(isset($data->count) && $data->count !== 1){
60 if(isset($data->tag)){
64 $meta = $data->meta ??
null;
65 if($meta === RecipeIngredientData::WILDCARD_META_VALUE){
72 $itemStack = self::deserializeItemStackFromFields(
76 $data->block_states ??
null,
81 if($itemStack ===
null){
90 return self::deserializeItemStackFromFields(
94 $data->block_states ??
null,
96 $data->can_place_on ?? [],
97 $data->can_destroy ?? []
105 private static function deserializeItemStackFromFields(
string $name, ?
int $meta, ?
int $count, ?
string $blockStatesRaw, ?
string $nbtRaw, array $canPlaceOn, array $canDestroy) : ?
Item{
109 $blockName = BlockItemIdMap::getInstance()->lookupBlockId($name);
110 if($blockName !==
null){
114 $blockStatesTag = $blockStatesRaw ===
null ?
117 ->read(ErrorToExceptionHandler::trapAndRemoveFalse(fn() => base64_decode($blockStatesRaw,
true)))
118 ->mustGetCompoundTag()
122 $blockStateData =
null;
126 ->read(ErrorToExceptionHandler::trapAndRemoveFalse(fn() => base64_decode($nbtRaw,
true)))
127 ->mustGetCompoundTag();
144 return GlobalItemDataHandlers::getDeserializer()->deserializeStack($itemStackData);
159 $recipes = json_decode(
Filesystem::fileGetContents($filePath));
160 if(!is_array($recipes)){
164 $mapper = new \JsonMapper();
165 $mapper->bStrictObjectTypeChecking =
true;
166 $mapper->bExceptionOnUndefinedProperty =
true;
167 $mapper->bExceptionOnMissingData =
true;
169 return self::loadJsonObjectListIntoModel($mapper, $modelCLass, $recipes);
177 private static function loadJsonObjectIntoModel(\JsonMapper $mapper,
string $modelClass,
object $data) : object{
180 return $mapper->map($data, (new \ReflectionClass($modelClass))->newInstanceWithoutConstructor());
181 }
catch(\JsonMapper_Exception $e){
182 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
194 private static function loadJsonObjectListIntoModel(\JsonMapper $mapper,
string $modelClass, array $data) : array{
196 foreach(Utils::promoteKeys($data) as $i => $item){
197 if(!is_object($item)){
198 throw new SavedDataLoadingException(
"Invalid entry at index $i: expected object, got " . get_debug_type($item));
201 $result[] = self::loadJsonObjectIntoModel($mapper, $modelClass, $item);
202 }
catch(SavedDataLoadingException $e){
203 throw new SavedDataLoadingException(
"Invalid entry at index $i: " . $e->getMessage(), 0, $e);
209 public static function make(
string $directoryPath) : CraftingManager{
210 $result = new CraftingManager();
212 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'shapeless_crafting.json'), ShapelessRecipeData::class) as $recipe){
213 $recipeType = match($recipe->block){
214 "crafting_table" => ShapelessRecipeType::CRAFTING,
215 "stonecutter" => ShapelessRecipeType::STONECUTTER,
216 "smithing_table" => ShapelessRecipeType::SMITHING,
217 "cartography_table" => ShapelessRecipeType::CARTOGRAPHY,
220 if($recipeType ===
null){
224 foreach($recipe->input as $inputData){
225 $input = self::deserializeIngredient($inputData);
232 foreach($recipe->output as $outputData){
233 $output = self::deserializeItemStack($outputData);
234 if($output === null){
237 $outputs[] = $output;
240 $result->registerShapelessRecipe(
new ShapelessRecipe(
246 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'shaped_crafting.json'), ShapedRecipeData::class) as $recipe){
247 if($recipe->block !==
"crafting_table"){
251 foreach(Utils::stringifyKeys($recipe->input) as $symbol => $inputData){
252 $input = self::deserializeIngredient($inputData);
256 $inputs[$symbol] = $input;
259 foreach($recipe->output as $outputData){
260 $output = self::deserializeItemStack($outputData);
261 if($output ===
null){
264 $outputs[] = $output;
267 $result->registerShapedRecipe(
new ShapedRecipe(
273 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'smelting.json'), FurnaceRecipeData::class) as $recipe){
274 $furnaceType = match ($recipe->block){
275 "furnace" => FurnaceType::FURNACE,
276 "blast_furnace" => FurnaceType::BLAST_FURNACE,
277 "smoker" => FurnaceType::SMOKER,
278 "campfire" => FurnaceType::CAMPFIRE,
279 "soul_campfire" => FurnaceType::SOUL_CAMPFIRE,
282 if($furnaceType ===
null){
285 $output = self::deserializeItemStack($recipe->output);
286 if($output ===
null){
289 $input = self::deserializeIngredient($recipe->input);
293 $result->getFurnaceRecipeManager($furnaceType)->register(
new FurnaceRecipe(
299 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'potion_type.json'), PotionTypeRecipeData::class) as $recipe){
300 $input = self::deserializeIngredient($recipe->input);
301 $ingredient = self::deserializeIngredient($recipe->ingredient);
302 $output = self::deserializeItemStack($recipe->output);
303 if($input ===
null || $ingredient ===
null || $output ===
null){
306 $result->registerPotionTypeRecipe(
new PotionTypeRecipe(
312 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'potion_container_change.json'), PotionContainerChangeRecipeData::class) as $recipe){
313 $ingredient = self::deserializeIngredient($recipe->ingredient);
314 if($ingredient ===
null){
318 $inputId = $recipe->input_item_name;
319 $outputId = $recipe->output_item_name;
323 self::deserializeItemStackFromFields($inputId,
null,
null,
null,
null, [], []) ===
null ||
324 self::deserializeItemStackFromFields($outputId,
null,
null,
null,
null, [], []) ===
null
329 $result->registerPotionContainerChangeRecipe(
new PotionContainerChangeRecipe(