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

Recipes

Recipes are a data-driven Minecraft system that allows for matching between inventories and item inputs, resulting in outputs.

Concepts

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

Creating a simple recipe

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

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

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

Recipe Type

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

Registration

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

Using your recipe

Creating recipe JSON files

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
}

Retrieving your recipes

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.