Minecraft Modding: The Ore Dictionary And Recipes

Introduction



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.

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.

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.

Adding Conditions To When 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.

Registering Items/Blocks To Alias To Minecraft


To get around this allow for a sort of "aliasing" of custom blocks to be treated like the vanilla ones, Forge provides an "ore dictionary".  The OreDictionary class implements a system/registry which associates items/blocks with a string name (which can be same as vanilla types).

By default, Forge has changed out many of these default vanilla recipes to accept ore dictionary items. To see the available ore dictionary names that you can associate your block or item to, see here.

For example, wood planks are one of the items with an ore dictionary alias. So to register a new block or item that Minecraft will accept as a wood plank, simply put OreDictionary.registerOre("plankWood", yourBlockInstance); in your mods' method for handling the init life cycle event in your common proxy.


Registering Items/Blocks To Alias To Other Mods



Key Point: In addition to aliasing to the vanilla items, the ore dictionary also allows you to alias between other people's mods. For example, if you know a mod that has a "copperIngot" registered in the ore dictionary, you could make your own version that would be treated the same in that mod's recipes provided you also register yours as "copperIngot".

Using Ore Dictionary As Recipe Ingredients


So what if you want to use something from the ore dictionary as input to a recipe? The recipe registration methods accept the ore dictionary name preceeded by a ":". For example:

mod.addRecipe("woodenBoots 1", 3, 2, ":woodPlank", "", ":woodPlank", ":woodPlank", "", ":woodPlank");


Using Ore Dictionary Elsewhere


Outside of recipes, wherever you have code that looks for a specific item/block you should consider comparing with the Ore Dictionary because that would maximize compatibility with other mods.

The OreDictionary class provides the getOres() method (which returns an ArrayList of ItemStacks). You then need to loop through that list and compare with whatever ItemStack you're interested in.

Note: You should use the version of the getOres() method that takes a String parameter.

To loop through the list and compare, OreDictionary has a method called containsMatch(). Unfortunately that method is private so you can't access it, but it is a simple method that you can just copy. Then you just pass in the ArrayList you got from the getOres() method, plus the ItemStack you're interested in.

Warning: The loop required for containsMatch() can be slow so try not to invoke it every tick if you can avoid it. For example, if you're interested in checking something the player is holding it is better to check it whenever something new is equipped rather than every tick.

Further Reading


These other tutorials can give you better idea of actual implementations with the ore dictionary:


Remove A 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);

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.

No comments:

Post a Comment