Notice: This website is still under development. Please report any issues at https://github.com/QuiltMC/developer-wiki

Recipe Type

In this guide, we will create a simple recipe type.

A recipe type represents a group of recipes.

Creating a simple recipe type

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 value filling.

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;
}
// ...

Matching

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));
}
// ...

Outputs

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();
}
// ...

Serializer

The serializer of a recipe type specifies the serialization and deserialization of recipes of that type into JSON and packet representation.

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).

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;
}
// ...

Packet

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 desynchronizations.

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);
}
// ...

Recipe Type

Creating the recipe type is as simple as instantiating RecipeType, unfortunately it is an interface, so you have to subclass it.

From Recipes.java:

// ...
public static final RecipeType<MyRecipe> MY_RECIPE = new RecipeType<>() {}; // Subclasses it anonymously
// ...

Registration

You must register your serializer and your type, so Minecraft knows about them.

From Recipes.java:

// ...
Registry.register(Registry.RECIPE_TYPE, new Identifier(modId, "my_recipe"), MY_RECIPE);
Registry.register(Registry.RECIPE_SERIALIZER, new Identifier(modId, "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;
}
// ...

Using your recipe

Creating recipe JSON files

Now that you have created and registered your recipe type, 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": "recipes:my_recipe",
  "input": {
    "item": "minecraft:iron_ingot"
  },
  "output": "minecraft:apple",
  "count": 2
}

Retrieving your recipes in code

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 of 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.