22declare(strict_types=1);
24namespace pocketmine\crafting;
45use Symfony\Component\Filesystem\Path;
46use
function base64_decode;
47use
function get_debug_type;
49use
function is_object;
50use
function json_decode;
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 =
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());
182 throw new SavedDataLoadingException($e->getMessage(), 0, $e);
194 private static function loadJsonObjectListIntoModel(\
JsonMapper $mapper,
string $modelClass, array $data) : array{
196 foreach($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;
239 $result->registerShapelessRecipe(
new ShapelessRecipe(
245 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'shaped_crafting.json'), ShapedRecipeData::class) as $recipe){
246 if($recipe->block !==
"crafting_table"){
250 foreach(Utils::stringifyKeys($recipe->input) as $symbol => $inputData){
251 $input = self::deserializeIngredient($inputData);
255 $inputs[$symbol] = $input;
258 foreach($recipe->output as $outputData){
259 $output = self::deserializeItemStack($outputData);
260 if($output ===
null){
263 $outputs[] = $output;
265 $result->registerShapedRecipe(
new ShapedRecipe(
271 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'smelting.json'), FurnaceRecipeData::class) as $recipe){
272 $furnaceType = match ($recipe->block){
273 "furnace" => FurnaceType::FURNACE,
274 "blast_furnace" => FurnaceType::BLAST_FURNACE,
275 "smoker" => FurnaceType::SMOKER,
279 if($furnaceType ===
null){
282 $output = self::deserializeItemStack($recipe->output);
283 if($output ===
null){
286 $input = self::deserializeIngredient($recipe->input);
290 $result->getFurnaceRecipeManager($furnaceType)->register(
new FurnaceRecipe(
296 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'potion_type.json'), PotionTypeRecipeData::class) as $recipe){
297 $input = self::deserializeIngredient($recipe->input);
298 $ingredient = self::deserializeIngredient($recipe->ingredient);
299 $output = self::deserializeItemStack($recipe->output);
300 if($input ===
null || $ingredient ===
null || $output ===
null){
303 $result->registerPotionTypeRecipe(
new PotionTypeRecipe(
309 foreach(self::loadJsonArrayOfObjectsFile(Path::join($directoryPath,
'potion_container_change.json'), PotionContainerChangeRecipeData::class) as $recipe){
310 $ingredient = self::deserializeIngredient($recipe->ingredient);
311 if($ingredient ===
null){
315 $inputId = $recipe->input_item_name;
316 $outputId = $recipe->output_item_name;
320 self::deserializeItemStackFromFields($inputId,
null,
null,
null,
null, [], []) ===
null ||
321 self::deserializeItemStackFromFields($outputId,
null,
null,
null,
null, [], []) ===
null
326 $result->registerPotionContainerChangeRecipe(
new PotionContainerChangeRecipe(
static loadJsonArrayOfObjectsFile(string $filePath, string $modelCLass)
static current(string $name, array $states)
static trapAndRemoveFalse(\Closure $closure, int $levels=E_WARNING|E_NOTICE)