Recipes & fuel
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.
When to register: the door decides
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:
// ----------------------------------------------------------------- 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:
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.
New in 0.3.0. Behind the retroapi entrypoint (Entrypoints & sides) you can also just call the four methods below inline in initRetro(). That door opens after vanilla's recipe list exists and before RetroAPI sorts it, so recipes added there land in the right order without the callback. The callback still works, and is still the right tool when you must react to other mods' registrations.
The "my recipes randomly don't exist" bug, and why it happened. A recipe is an ItemStack, and an ItemStack stores a numeric id. Mods register during init, when ids are still provisional; the real ones arrive later, when a world's saved id map (or a server's) is applied. Every recipe built beforehand kept pointing at the old number, so it stayed in the list, count and all, and simply stopped matching, unless this session's provisional ids happened to match the world's, which is exactly why relaunching "fixed" it sometimes. RetroAPI now sweeps its own tables after any id change: crafting recipes, smelting inputs and outputs, fuels, and achievement icons are all repointed. If your mod caches an ItemStack or a raw id in a field, fix it in the same pass:
IdRemapCallback.EVENT.register(remap -> {
remap.fix(ExampleMod.STARTER_KIT); // an ItemStack field
ExampleMod.magicItemId = remap.map(ExampleMod.magicItemId); // a raw id
});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 time | Items smelted | Vanilla equivalent |
|---|---|---|
| 200 | 1 | a stick is 100 (half an item) |
| 1600 | 8 | one piece of coal |
| 20000 | 100 | one 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:
// 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:
/** 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 Block entities & GUIs 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 (Testing covers the dev launch), every one of these is one craft or one smelt away:
| Do this | Get this | Which API |
|---|---|---|
| 4 cobblestone in a 2×2 | 1 Example Block | addShaped |
| 1 Example Block anywhere in the grid | 4 Suspicious Substances | addShapeless |
| Smelt an Example Block | 1 Suspicious Substance | addSmelting |
| Use Suspicious Substances as furnace fuel | 8 items smelted each | addFuel |
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.