# Crafting recipes (craft.recipes) ## What it is for `craft.recipes` turns items into other items. A recipe lists inputs, outputs and a time, and is made by hand or at a station of the world (a workbench, a forge, a kitchen, a printer). Recipes can start locked and be unlocked by a script, a mission or a find. A craft screen lists what can be made with what the player holds. Inputs are taken from `inventory.items` when the craft starts; the outputs are given when the time is over, and what does not fit in the inventory is owed and given as soon as there is room. One craft runs at a time. ## Turn it on Install the kit in a project and set its settings with the tools below; `gripforge kit api craft.recipes` lists every setting, and `gripforge docs craft-recipes` prints this guide offline. ```text gripforge_gamekit_install { project_id: "gkp_…", id: "craft.recipes" } gripforge_gamekit_configure { project_id: "gkp_…", id: "craft.recipes", config: { "handCrafting": true, "stationReach": 5 } } ``` ```json { "id": "gem", "name": "Gem", "station": "forge", "inputs": [{ "item": "plank" }], "outputs": [{ "item": "gem", "count": 7 }], "seconds": 0.5 } ``` ## First example With two wood, `craft` makes two planks at once. A recipe with a `station` is refused (`station`) until its station is open; the timed craft then blocks others (`busy`), survives a save, and gives its outputs at the end, owing what the inventory cannot hold. ```text craft.recipes.craft("plank") // { ok: false, reason: "inputs" }: no wood yet inventory.items.give("wood", 2) craft.recipes.craft("plank") // { ok: true }: two planks at once craft.recipes.craft("gem") // { ok: false, reason: "station" }: it is made at a forge craft.recipes.open("forge", station) // true: the station (an instance with craft_station) is open craft.recipes.craft("gem") // { ok: true }: craft.started, the gems come after 0.5 s craft.recipes.craft("plank") // { ok: false, reason: "busy" } ``` ## Settings and defaults | Setting | Default | Meaning | | --- | --- | --- | | `handCrafting` | `true` | {{s_handCrafting}} | | `stationReach` | 4 | {{s_stationReach}} | | `stationLabel` | `Use {name}` | {{s_stationLabel}} | ## Services - `craft.recipes` (service): `recipes(station?)`, `canCraft(id)`, `craft(id)` answers `{ ok, reason? }`, `unlock(id)`, `unlocked(id)`, `open(station?, entity?)`, `close()`, `current()` and `job()` (the running craft). - The `recipes` collection: `id`, `name`, `description`, `category` (a heading on the craft screen), `station` (the kind of station, absent: by hand), `inputs` (1 to 12 stacks `{ item, count }`), `outputs` (1 to 6 stacks), `seconds` (0 to 3600, default 0: at once), `unlocked` (`false` hides it until unlocked, default `true`). A recipe `{ "id": "iron_sword", "extends": "sword" }` of a game or a mod inherits an entry. An instance with the component `craft_station { station, label? }` is a station of that kind; using it opens the craft screen with the recipes made there. ## Events - `craft.started`: the inputs were taken; the outputs come after `seconds`. - `craft.completed`: the outputs were given (`recipe`, `outputs`). - `craft.failed`: a craft was refused, with a `reason` of `unknown`, `locked`, `station`, `inputs`, `busy`, or the reason of the tap that cancelled `craft.make` (`refused` when it gave none). - `craft.unlocked`: a locked recipe was revealed (`recipe`). - `craft.opened` (the craft screen opened; `station` is empty for the hand menu) and `craft.closed`. ## Hooks and modding - `craft.make`: the player is about to make a recipe (it is known, makeable here, nothing else runs, the inputs are held, nothing taken yet); `seconds` is mutable (a craft from the `craft_item` verb stays instant), and cancelling answers `{ ok: false, reason }` and emits `craft.failed`. - `craft.unlock`: a locked recipe is about to be revealed (`unlock`, the `unlock_recipe` verb); cancelled, it stays locked (`unlock` answers `false`, no event). ```json { "id": "slow_forge", "hook": "craft.make", "match": { "station": "forge" }, "set": { "seconds": 5 } } ``` ## Using it from content Script verbs: the actions `craft_item` (`recipe`: make it for the player at once, no station needed), `unlock_recipe` and `open_crafting` (`station`, empty for the hand menu), and the condition `recipe_unlocked`. The input `craft_menu` (key `KeyG`) opens the hand menu. Variables: `craft.open`, `craft.station`, `craft.crafted`. ```json { "craft_item": "bandage" } { "unlock_recipe": "steel_pickaxe" } { "open_crafting": "workbench" } ``` ## When it does not do what you expect | Symptom | Cause and fix | | --- | --- | | `craft` answers `ok: false` with a reason | `unknown` (no such recipe), `locked` (call `unlock`), `station` (open the right station first), `inputs` (the player lacks items) or `busy` (another craft runs). | | The outputs never arrive | The inventory is full: what does not fit is owed and given as soon as there is room. A timed craft also needs the game to be playing for its `seconds` to elapse. | | A recipe does not show on the craft screen | It is locked (`unlocked: false`), its `station` is not the one open, or `handCrafting` is `false` for a hand recipe. | ## Limits - It needs `inventory.items`: there is no crafting without an inventory. - One timed craft at a time, for the player only; no queue and no skill or quality system. - The craft screen is a web display module; on other targets the service, the verbs and the events work and your game draws its own screen. ## Versions Kit `craft.recipes` 1.2.1. It needs `inventory.items`, `input.actions` and `game.engine` ^1. 1.2.0 made `recipes` an inheritable registry (`extends`); 1.1.0 added the hooks; 1.0.0 (2026-10-04) was the first release.