Chapter 11, Recipes

Four little tables, shaped, shapeless, smelting, fuel, and one rule about wildcards that saves you a debugging afternoon.

You've built blocks, items, a mob, a dimension. Now we close the loop: let players make the things you registered. Recipes in RetroAPI are four one-line calls, and the only subtle part is when you call them, so we'll start there.

Register through the callback, not in init()

Vanilla's recipe list doesn't exist yet when your init() runs. If you called RetroRecipes.addShaped(...) directly from init() you'd be appending to a list that the game is about to build and sort around you. So RetroAPI gives you an event that fires after vanilla's recipes exist, and re-sorts the whole list afterwards so your recipes land in the right priority order:

src/main/java/com/example/example_mod/ExampleMod.java
// ----------------------------------------------------------------- recipes --
// Recipes are registered through a callback that fires after vanilla's recipes
// exist (the recipe list is re-sorted afterwards), so modded recipes always
// take priority correctly.
RecipeRegistrationCallback.EVENT.register(ExampleMod::registerRecipes);

That one line goes in init() alongside your block and item registration. The actual recipes live in the method it points at. Here is registerRecipes() in full, all four recipe APIs in eleven lines:

src/main/java/com/example/example_mod/ExampleMod.java
private static void registerRecipes() {
    // Shaped recipe: four cobblestone in a 2x2 square craft one Example Block.
    // Bare Block/Item ingredients match ANY metadata; pass an ItemStack with a
    // damage value (e.g. new ItemStack(Block.WOOL, 1, 14)) for exact-meta matching.
    RetroRecipes.addShaped(new ItemStack(EXAMPLE_BLOCK),
        "CC",
        "CC",
        'C', Block.COBBLESTONE);

    // Shapeless recipe: one Example Block anywhere in the grid gives four Suspicious Substances.
    RetroRecipes.addShapeless(new ItemStack(SUSPICIOUS_SUBSTANCE, 4), EXAMPLE_BLOCK);

    // Smelting: cook an Example Block back into an Suspicious Substance.
    RetroRecipes.addSmelting(EXAMPLE_BLOCK.id, new ItemStack(SUSPICIOUS_SUBSTANCE));

    // Fuel: Suspicious Substances burn in a furnace for 1600 ticks (= 8 smelted items).
    RetroRecipes.addFuel(SUSPICIOUS_SUBSTANCE.id, 1600);
}

The callback is also where you'd register recipes that depend on other mods' items existing, by the time it fires, the whole registry has settled.

The four APIs

addShaped, position matters

addShaped(output, "CC", "CC", 'C', ingredient…) reads like the crafting grid does: each string is a row, each character is a slot, and the trailing pairs map a character to an ingredient. The shape is anchored but movable, a 2×2 pattern crafts anywhere in the 3×3 grid. Spaces in the strings mean "this slot must be empty."

addShapeless, any arrangement

addShapeless(output, ingredients…) ignores position entirely: drop the listed ingredients anywhere and you get the output. Our example takes a single Example Block and returns four Suspicious Substances, handy for "uncrafting" a block back into its parts.

addSmelting, the furnace

addSmelting(inputId, output) takes a numeric item id as input (note EXAMPLE_BLOCK.id, not the block itself) and any ItemStack as output. Put the input on top of a lit furnace and it cooks into the output.

addFuel, burn time in ticks

addFuel(itemId, burnTicks) teaches the furnace that an item is fuel. The unit is ticks, and the conversion is the one number worth memorizing: 200 ticks smelts exactly one item. The showcase uses 1600, so one Suspicious Substance smelts eight things, twice as good as a piece of coal (1600 vs. coal's 1600… actually equal to coal here, which is the joke: your scrap item is as good as coal).

Burn timeItems smeltedVanilla equivalent
2001a stick is 100 (half an item)
16008one piece of coal
20000100one lava bucket

The wildcard rule (read this one)

This is the part that bites people, so RetroAPI standardizes it deliberately, StationAPI, the other big beta library, is inconsistent here and you can't predict its behavior. RetroAPI's rule is simple and total:

A bare Block or Item ingredient matches ANY metadata. Under the hood it's stored with damage -1, the wildcard value. So 'C', Block.WOOL accepts wool of any color. If you need an exact color, say, red wool only, pass a fully-specified ItemStack instead:

exact-metadata matching
// any wool color works:
RetroRecipes.addShaped(output, "CC", "CC", 'C', Block.WOOL);

// ONLY damage 14 (red) wool works:
RetroRecipes.addShaped(output, "CC", "CC", 'C', new ItemStack(Block.WOOL, 1, 14));

The third ItemStack argument is the damage/metadata value. The moment you write the ItemStack form, matching becomes exact.

The full fuel table, for your own machines

The furnace knows what burns. So does anyone who registered fuel with addFuel. But what if you build a machine, a freezer, a smelter, a generator, and want it to "burn whatever a furnace burns"? You don't want to hand-maintain a list of coal, wood, sticks, lava buckets, and every mod's fuel item.

RetroAPI gives you the whole table in one call. As of RetroAPI ≥ 0.1.3, getTotalFuelTime(ItemStack) returns the burn time for a stack across the entire furnace fuel table, vanilla fuels plus every addFuel entry, or 0 if it isn't fuel. The showcase's freezer is exactly one line because of it:

src/main/java/com/example/example_mod/ExampleFreezerBlockEntity.java
/** Burn ticks for a stack: vanilla fuels + RetroRecipes.addFuel fuels, 0 if not fuel. */
public static int fuelTime(ItemStack stack) {
    return RetroRecipes.getTotalFuelTime(stack);
}

That's how the freezer in Chapter 6 accepts coal, wood, lava buckets, and your own Suspicious Substances, the moment you call addFuel on them. One source of truth, no copy-pasting.

Two narrower siblings exist when you want them: getFuelTime(int itemId) returns burn time for modded-only entries (the ones registered via addFuel, ignoring vanilla), and isFuel(int itemId) is a quick boolean check. For most machines you want getTotalFuelTime, it's the "act exactly like a furnace" answer.

Advanced: removing and re-sorting

Two more tools for the cases that need them. removeRecipe(ItemStack output) strips an existing recipe by what it produces, useful for disabling a vanilla recipe you want to replace. And sortCraftingRecipes() re-runs the priority sort by hand; the registration callback already calls it for you, so you only need it if you've been adding recipes outside the callback for some reason.

Try it in game

With the starter kit in your inventory (Chapter 22 covers the dev launch), every one of these is one craft or one smelt away:

Do thisGet thisWhich API
4 cobblestone in a 2×21 Example BlockaddShaped
1 Example Block anywhere in the grid4 Suspicious SubstancesaddShapeless
Smelt an Example Block1 Suspicious SubstanceaddSmelting
Use Suspicious Substances as furnace fuel8 items smelted eachaddFuel

Mine cobble, square it into a block, smelt it back, then feed the leftovers into the furnace as fuel, the whole loop in one playtest.

Crafting is players making things on their own bench. Next we let your code and the server talk directly, across the wire.