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 ?
118 ->mustGetCompoundTag()
122 $blockStateData =
null;
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 =
false;
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,
218 "furnace" => FurnaceType::FURNACE,
219 "blast_furnace" => FurnaceType::BLAST_FURNACE,
220 "smoker" => FurnaceType::SMOKER,
221 "campfire" => FurnaceType::CAMPFIRE,
222 "soul_campfire" => FurnaceType::SOUL_CAMPFIRE,
225 if($recipeType ===
null){
229 foreach($recipe->input as $inputData){
230 $input = self::deserializeIngredient($inputData);
237 foreach($recipe->output as $outputData){
238 $output = self::deserializeItemStack($outputData);
239 if($output === null){
242 $outputs[] = $output;
246 if($recipeType instanceof FurnaceType){
247 if(count($inputs) !== 1 || count($outputs) !== 1){
248 throw new SavedDataLoadingException(
"Furnace recipes must have exactly 1 input and 1 output");
251 $result->getFurnaceRecipeManager($recipeType)->register(
new FurnaceRecipe(
256 $result->registerShapelessRecipe(
new ShapelessRecipe(
263 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'shaped_crafting.json'), ShapedRecipeData::class) as $recipe){
264 if($recipe->block !==
"crafting_table"){
268 foreach(Utils::stringifyKeys($recipe->input) as $symbol => $inputData){
269 $input = self::deserializeIngredient($inputData);
273 $inputs[$symbol] = $input;
276 foreach($recipe->output as $outputData){
277 $output = self::deserializeItemStack($outputData);
278 if($output ===
null){
281 $outputs[] = $output;
284 $result->registerShapedRecipe(
new ShapedRecipe(
291 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'potion_type.json'), PotionTypeRecipeData::class) as $recipe){
292 $input = self::deserializeIngredient($recipe->input);
293 $ingredient = self::deserializeIngredient($recipe->ingredient);
294 $output = self::deserializeItemStack($recipe->output);
295 if($input ===
null || $ingredient ===
null || $output ===
null){
298 $result->registerPotionTypeRecipe(
new PotionTypeRecipe(
304 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'potion_container_change.json'), PotionContainerChangeRecipeData::class) as $recipe){
305 $ingredient = self::deserializeIngredient($recipe->ingredient);
306 if($ingredient ===
null){
310 $inputId = $recipe->input_item_name;
311 $outputId = $recipe->output_item_name;
315 self::deserializeItemStackFromFields($inputId,
null,
null,
null,
null, [], []) ===
null ||
316 self::deserializeItemStackFromFields($outputId,
null,
null,
null,
null, [], []) ===
null
321 $result->registerPotionContainerChangeRecipe(
new PotionContainerChangeRecipe(