Recipes are a data-driven Minecraft system that allows for matching between inventories and item inputs, resulting in outputs.
Recipe - Specifies the behavior and logic of a recipe - its inputs, outputs and matching.RecipeType - Represents a group of recipes.RecipeSerializer - Deserialization of JSON files to a recipe object, and serialization and deserialization of a recipe to a packet.To start to create a recipe, create a class that implements the Recipe interface, the generic type of it represents the kind of Inventory where this recipe will work.
The conventional way of specifying inputs and output, is to utilize Ingredient and ItemStack respectively - Ingredient is a generalized object that can match against stacks being backed by either a stack or tags.
You should also store the recipe identifier for identification and filling values.
From MyRecipe.java:
// ...
private final Identifier id;
private final Ingredient input;
private final ItemStack output;
public MyRecipe(Identifier id, Ingredient input, ItemStack output) {
this.id = id;
this.input = input;
this.output = output;
}
@Override
public Identifier getId() {
return this.id;
}
// ...
The method matches returns whether this current recipe should be matched when retrieved via the RecipeManager - Match the inventory contents against the inputs here.
From MyRecipe.java:
// ...
@Override
public boolean matches(Inventory inventory, World world) {
return this.input.test(inventory.getStack(0));
}
// ...
Outputting has two parts, getOutput and craft, the first being a view of the output and the latter being a copy of the output that may be modified.
From MyRecipe.java:
// ...
@Override
public ItemStack getOutput() {
return this.output;
}
@Override
public ItemStack craft(Inventory inventory) {
return this.output.copy();
}
// ...
To start to create a serializer, create a class that implements QuiltRecipeSerializer (An extended version of RecipeSerializer that allows for serialization of recipe objects into JSON).
In read you must read the JSON object and convert it into a new recipe object of your type.
In toJson you must convert the recipe object into a valid equivalent JSON object - You can check if it's being serialized correctly via dumping the recipes with the quilt.recipe.dump property (-Dquilt.recipe.dump=true in the VM options) and checking the outputted recipes.
From MyRecipe.java:
// ...
@Override
public MyRecipe read(Identifier id, JsonObject json) {
// Gets the object specified by the "input" key
var inputObject = json.getAsJsonObject("input");
// Helper method to read Ingredient from JsonElement
var input = Ingredient.fromJson(inputObject);
// Gets the string in the "output" key
var outputIdentifier = json.get("output").getAsString();
// Gets the integer in the "count" key, fallbacking to 1 if it does not exist
var count = JsonHelper.getInt(json, "count", 1);
// Attempts to get the item in "output" with the registry and creates a stack with it
var output = new ItemStack(Registry.ITEM.getOrEmpty(new Identifier(outputIdentifier))
.orElseThrow(() -> new IllegalStateException("Item " + outputIdentifier + " does not exist")), count);
return new MyRecipe(id, input, output);
}
@Override
public JsonObject toJson(MyRecipe recipe) {
var obj = new JsonObject();
// Puts the serialized ingredient json element into the "input" key
obj.add("input", recipe.input.toJson());
// Checks if the output count is higher than the default, and if it is, adds into the "count" key
if (recipe.output.getCount() > 1) {
obj.addProperty("count", recipe.output.getCount());
}
// Gets the output identifier from the registry and adds it into the "output" key
obj.addProperty("output", Registry.ITEM.getId(recipe.output.getItem()).toString());
return obj;
}
// ...
You must create and read recipe packets, so the server is able to send the recipes to the clients, and be converted correctly, failing to implement the packet reading and writing might result in desyncs.
It is imperative that you read values in the same order that you wrote them.
From MyRecipe.java:
// ...
@Override
public MyRecipe read(Identifier id, PacketByteBuf buf) {
var input = Ingredient.fromPacket(buf);
var output = buf.readItemStack();
return new MyRecipe(id, input, output);
}
@Override
public void write(PacketByteBuf buf, MyRecipe recipe) {
recipe.input.write(buf);
buf.writeItemStack(recipe.output);
}
// ...
Creating a recipe type is as simple as instantiating RecipeType, unfortunately it is an interface, so you must subclass it. From Recipes.java:
// ...
public static final RecipeType<MyRecipe> MY_RECIPE = new RecipeType<>() {}; // Subclasses it anonymously
// ...
You must register your serializer and your type, so Minecraft knows about them. From Recipes.java:
// ...
Registry.register(Registry.RECIPE_TYPE, new Identifier("example", "my_recipe"), MY_RECIPE);
Registry.register(Registry.RECIPE_SERIALIZER, new Identifier("example", "my_recipe"), MY_RECIPE_SERIALIZER);
// ...
Additionally, you also have to specify them in your recipe class. From MyRecipe.java:
// ...
@Override
public RecipeSerializer<?> getSerializer() {
return Recipes.MY_RECIPE_SERIALIZER;
}
@Override
public RecipeType<?> getType() {
return Recipes.MY_RECIPE;
}
// ...
Now that you have created and registered your recipe, it's time to make a few recipes.
Recipes should be located inside resources/data/[namespace]/recipes; its id will be composed of the namespace to which it was added, and the file path with its file name.
All recipes have different structures, the only common denominator between all of them is the field "type", which specifies the identifier of the recipe type - so in our case, "example:my_recipe"; The remaining values are dependent on the serializer.
For instance, a recipe of ours that inputs an iron ingot and outputs two apples would be: From fun.json:
{
"type": "example:my_recipe",
"input": {
"item": "minecraft:iron_ingot"
},
"output": "minecraft:apple",
"count": 2
}
Recipes must be retrieved from the RecipeManager, which can be fetched from either a World or the MinecraftServer. The recipe manager has a few methods for recipe retrieving, such as:
getFirstMatch - Gets the first recipe of a type that returned true for matches.listAllOfType - Gets all recipes from a type.getAllMatches - Gets all recipes of a type that returned true for matches.The methods that get matching recipes require you to pass an inventory that matches the specified in the recipe generic.