Minecraft Modding: Recipes

Introduction


Note: Most information on this page is related to the 1.12+ version JSON recipe system. At the end of the page is some tips related to the older system.

One of the basic ideas of Minecraft game play is the idea that you mine "ore" and then use that to smelt and craft things.

Many mods want to add variety and so might create variations on things, but still want them to act similarly to vanilla "ore" when used in recipes. This isn't as simple as simply creating classes that extend those vanilla things (although you might want to do that) because a lot of functionality in Minecraft will compare for exact match with the specific instance including metadata.

Types Of Recipes


There are actually many types of recipes in Minecraft:

  • Furnace "smelting" recipes:
    • These need a furnace block and are used for making ingots out of ore, cooking meat, etc.
    • The actual recipes are contained in the FurnaceRecipe.instance() and you can add recipes by calling the addSmeltingRecipe() method from that instance.
    • A furnace recipe takes a single ItemStack in, gives a single ItemStack out and gives some experience for doing the smelting.
    • Note that while vanilla furnace recipes give a one for one transformation -- i.e. one beef will create one cooked beef, it is possible to have different numbers of inputs and outputs based on the ItemStack passed into the addSmeltingRecipe() method.
    • Tip: I put my smelting recipes in the initialize() method in my item registry class. This is guaranteed to run after all your blocks and items for your mod are registered.
  • Shapeless crafting recipes:
    • These allow the ingredients to be placed in any position within the crafting grid. Most are simpler recipes and usually fit in the 2x2 player crafting grid.
    • Examples are dyed wool and granite blocks.
  • Shaped crafting recipes:
    • These require the ingredients to be placed in specific positions within the crafting grid. 
    • Many are somewhat complex and require the 3x3 crafting table.
    • Many can be flipped horizontally and if they don't fill the 3x3 grid can also be shifted within the grid.
    • Examples are weapons, armor and tools.
  • Brewing recipes:
    • These require a brewing stand.
    • Brewing recipes can be added using the BrewingRecipeRegistry.addRecipe() method.
Since adding smelting and brewing recipes is straightforward and already described above, the rest of the information below relates to crafting recipes (both shaped and shapeless).

Recipe System In 1.12


The system has changed significantly in 1.12, now using JSON files and such. With the JSON system, you can add recipes without any modding code but rather use a resource pack approach -- simply create a recipes folder in your assets and put a properly formed JSON recipe there. They will be automatically registered. You can name the files whatever you want although the vanilla convention is to name it after the output item, and it is used as the registration key but it does not affect the operation of the recipe.

However, if you wish to explicitly register recipes using a coding approach consider this explanation (thanks to Choonster).

Instead of storing a collection of items and comparing the contents of a crafting grid against them, recipes now store a collection of Ingredient objects and ask each one whether the contents of the crafting grid match.

Recipes are now stored in a Forge registry, so each one has a unique registry name. This also means that they should be registered in the corresponding registry event using IForgeRegistry#register.

GameRegistry.addShapedRecipe works much like it did before, but now it requires you to specify the registry name (the first parameter) and an optional recipe book group (the second parameter). Recipes with the same group will be shown together in the recipe book. The vararg parameter should contain the shape strings followed by the character and ingredient pairs, just like before. The ingredients can be ItemStack, Item, Block, String (for ore dictionary matching) or Ingredient type.

Unlocking Custom Recipes In Recipe Book


As you know, when you first obtain items that are ingredients to a vanilla recipe it will immediately unlock the recipe in the recipe book. To get that to happen with custom recipes, you need to use the advancement system to create an advancement that does the same thing for each of your ingredients. This is the same way that vanilla does it. The advancement does not have to be visible in the advancement tree, but it has to exist.

Tip: Look in the advancements.recipes assets folder in Minecraft to see examples of how the recipe advancements should be written.

Get A List Of Ingredients For An Existing Recipe


Thanks to Choonster for this tip.

In 1.12+, use the IRecipe#getIngredients() method to get a list of ingredients in a recipe and Ingredient#getMatchingStacks() to get a list of item stacks matched by an Ingredient.

Note: IRecipe#getIngredients() method will return an empty list for special recipes with hard-coded ingredients (e.g. RecipeBookCloning, RecipeFireworks, etc.), it only returns a non-empty list for recipes that use a collection of Ingredient objects (e.g. ShapedRecipe, ShapelessRecipe, ShapedOreRecipe and ShapelessOreRecipe).

Multiple Recipes For Same Item


It is allowed to have multiple recipes for the same item. Simply put the multiple recipes into the folder and they will all be active.

Making Recipes Output More Than One Item Type (e.g. Emptying Buckets)


You may want to create recipes that not only output the result but also transform the ingredients. For example, when you use milk buckets to make a cake the milk buckets become empty buckets. This explains how to do that.

Thanks to Choonster for this tip.

Create a class that extends the IRecipe class you're currently using and then @Override the IRecipe#getRemainingItems() method to return the remaining item for each slot. Use ForgeHooks.defaultRecipeGetRemainingItems to get the default remaining items for every slot or ForgeHooks.getContainerItem to get the default remaining item for a single slot.

Create a class that implements IRecipeFactory and parses your recipe from the provided JSON object. Specify this class in the recipes/_factories.json. You can see the IRecipeFactory implementations for the vanilla and Forge recipe classes in the CraftingHelper.init() method.

In your recipe file, set the type property to ":", where is your mod ID and is the name you specified in recipes/_factories.json.

Replacing (Or Removing) Vanilla Recipes


Thanks to Choonster for this information and code examples.

JSON recipes always have your modid as the domain of their registry name, so they can't directly override a vanilla recipe. But you can implement your own IRecipe directly in code that can be

Removing a vanilla recipe directly is also difficult. Because IRecipes are now in a registry you might think you can simply remove them (provided you reference them correctly). For example, the following code (ideally placed in your recipe registry event handling method) would remove the vanilla wooden_button recipe.

((IForgeRegistryModifiable) event.getRegistry()).modRegistry.remove(new ResourceLocation("minecraft:wooden_button"));

However, this will cause errors with the Advancement system because technically all the unlocking of recipes is implemented as advancements.

Therefore, instead of removing the recipe it is better to replace it with a "dummy recipe". For example here (thanks again to Choonster) are several different methods used to remove recipes based on output or class of recipe:

    private static void removeRecipes(final Block output) 
    {
        removeRecipes(Item.getItemFromBlock(output));
    }

    private static void removeRecipes(final Item output) 
    {
        removeRecipes(recipe -> 
        {
            final ItemStack recipeOutput = recipe.getRecipeOutput();
            return !recipeOutput.isEmpty() && recipeOutput.getItem() == output;
        });
    }

    private static void removeRecipes(final Class recipeClass) 
    {
        final int recipesRemoved = removeRecipes(recipeClass::isInstance);
    }

    private static int removeRecipes(final Predicate predicate) 
    {
        int recipesRemoved = 0;

        final IForgeRegistry registry = ForgeRegistries.RECIPES;
        final List toRemove = new ArrayList<>();

        for (final IRecipe recipe : registry) 
        {
            if (predicate.test(recipe)) 
            {
                toRemove.add(recipe);
                recipesRemoved++;
            }
        }

        toRemove.forEach(recipe -> 
        {
            final IRecipe replacement = 
                new DummyRecipe().setRegistryName(recipe.getRegistryName());
            registry.register(replacement);
        });

        return recipesRemoved;
    }

The first two methods will remove all recipes that have an output of the block or item passed in. The third method does the work where it takes a Predicate (a class where you can test to see if a condition is true) and replaces all recipes that match with a DummyRecipe whose code is:

public class DummyRecipe extends IForgeRegistryEntry.Impl implements IRecipe 
{
    @Override
    public boolean matches(final InventoryCrafting inv, final World worldIn) 
    {
        return false;
    }

    @Override
    public ItemStack getCraftingResult(final InventoryCrafting inv) 
    {
        return ItemStack.EMPTY;
    }

    @Override
    public boolean canFit(final int width, final int height) 
    {
        return false;
    }

    @Override
    public ItemStack getRecipeOutput() 
    {
        return ItemStack.EMPTY;
    }
}

Using the "replace with dummy" method above prevents the advancement errors, but instead spams the log with "Dangerous alternative prefix" warnings because you need to set the registry names of the dummy recipes to the registry names of the vanilla recipes they override. There's currently no way to avoid this. But it is just a warning, not really an error -- a warning is just intended to alert you to confirm that you really intended it.

If you only want to remove the recipe, the above is sufficient. If you want to replace it, then you could proceed to register your own recipe with the exact same name (your JSON would need the matching name as well). The modded version will take precedence over the vanilla one with the same name.

Adding Conditions To When A Recipe Is Valid


Thanks to dieseiben07 for this information.

Implement IConditionFactory and implement your condition there. A condition is really just something that returns a boolean, so you'd return whether or not your recipe should be active.
Then create a file assets//recipes/_factories.json. Its structure is like so:
{
    "conditions": {
        "": "
    }
}

You can specify recipe and ingredient factories, too.

Now in your recipe JSON file you can specify conditions like so:
{
    "conditions": [
        {
            "type": ":"
        }
    ]
}

The JSON object in the conditions array will be passed to your IConditionFactory, so you can specify additional data here.

Warning: Note that this is only updated once at startup, conditions are not dynamically checked.

Note: Conditions only apply to the recipe, not to individual ingredients. You need to specify the conditions property in the top-level object. If you want conditional ingredients, you'll need to create an IIngredientFactory and specify it in _factories.json. In the IIngredientFactory, you can use CraftingHelper.processConditions to check if the conditions are met. If they are, return an Ingredient instance for the specified item; if they aren't, return Ingredient.EMPTY (an Ingredient that never matches any ItemStack). You can see an example implementation by Choonster here.

Changing Recipes Based On Configuration Settings


You may be interested in changing recipes based on a configuration setting. That is easy to accomplish since the registry events fire after the configuration file loads. So you can create a configuration (make sure to set it as type that requires restart to take effect) and then in the recipe registration event handling method you can check the configuration and register your recipes accordingly.

Remove A Smelting Recipe From Furnace


The list has public scope so you can edit it directly with standard methods, in this case remove():

FurnaceRecipes.smelting().getSmeltingList().remove(Block.oreIron.blockID);


Substituting Mod Items Into Vanilla Recipes (The Ore Dictionary)

To aid the use of mod items as equivalent ingredients to vanilla ones, Forge provides a mechanism called the Ore Dictionary. I have information on the Ore Dictionary here.

Using Your Own IRecipe Classes With The JSON System


Thanks to Choonster for this tip.

You can use your own recipe classes with the JSON system by creating an IRecipeFactory and specifying it in your _factories.json file.

Choonster has an example: IRecipe and IRecipeFactory implementations here and the corresponding _factories.json file here.

Using The JSON Recipe System For Your Own Machines


Thanks to Choonster for this tip.

You can use CraftingHelper.findFiles to iterate through files in a specific path for a mod and process each one in some way.

Forge uses it to load recipes in CraftingHelper.loadRecipes(ModContainer) and advancements in ForgeHooks.loadAdvancements(Map, ModContainer).

***The Information Below is for Minecraft Versions Prior to 1.12 (Before the JSON Recipe Format was Introduced)***

Remove A Recipe From Crafting


It is a bit trickier with Crafting if you want to make sure all recipes for an item are removed, you'll need to iterate through them all like this:


Iterator<IRecipe> iterator = CraftingManager.getInstance().getRecipeList().iterator();

while (iterator.hasNext())
{
    IRecipe recipe = iterator.next();
    if (recipe == null)
     continue;
    ItemStack output = recipe.getRecipeOutput();
    if (output != null && output.itemID == Block.furnaceIdle.blockID)
     iterator.remove();
}

Why Does Some Code Use Object[] In Recipe Registration


Thanks to diesieben07 for this explanation.

You may see that the Minecraft code uses an Object[] in the recipe registrations. Something that looks like this:
GameRegistry.addShapedRecipe(new ItemStack(EFItems.lightBulb), new Object[] {"GGG", "GOG", "III", 'G', Blocks.glass, 'O', Items.gold_ingot, 'I', Items.iron_ingot});

However, you will see that many modders will do the registration like this instead (without the Object[]):
GameRegistry.addShapedRecipe(new ItemStack(EFItems.lightBulb), "GGG", "GOG", "III", 'G', Blocks.glass, 'O', Items.gold_ingot, 'I', Items.iron_ingot);

In Java these are actually equivalent -- you can use either, although most people would choose the latter since it is less typing and easy to read. The method in this case accepts "varargs" meaning that it can take a variable number of parameters; this is literally equivalent to passing an Object[] as the parameter. The vararg approach is an example of "syntactic sugar" which means it is a way of allowing simpler (or easier to read) coding syntax for something.

The reason why the Minecraft vanilla code will use the Object[] syntax is because the decompiler used doesn't take the step of translating to the vararg since the Object[] is already a correct decompilation.

Make Player-Specific Recipes


Check out diesieben07's Player-Specific Recipes Tutorial.

4 comments:

  1. Why do i get this?

    Error:(10, 8) java: net.minecraftforge.registries.IForgeRegistryEntry cannot be inherited with different arguments: and <>

    ReplyDelete
    Replies
    1. It tells you where the error is and if you google "cannot be inherited with different arguments" it seems that it is a problem with your generics. You must have put the wrong thing in the angle brackets <> or something like that.

      Delete
  2. I really appreciate your tutorials. I've relied on them many times while learning to mod.

    I'm commenting because I noticed you have an incomplete sentence under "Replacing (Or Removing) Vanilla Recipes":

    "JSON recipes always have your modid as the domain of their registry name, so they can't directly override a vanilla recipe. But you can implement your own IRecipe directly in code that can be"

    ReplyDelete
  3. However, this will cause errors with the Advancement system because technically all the unlocking of recipes is implemented as advancements.

    How I can fix that?

    ReplyDelete