Recipes
Author crafting recipes that turn ingredients into products, choose how players trigger them, and gate them on skill levels with XP rewards.
9 min read
What recipes do#
A recipe consumes ingredients from a player's inventory and produces items. It says which items go in, what comes out (with optional secondary and failure outputs), how the player triggers it (using a tool on a log, using ore on a furnace, clicking an option on an item, typing a command), which skill levels are required and how much XP a success grants. The Recipe Editor in the Tools hub builds recipes; the bundled crafting module loads them when the server starts and listens for the matching interactions.
Use recipes for anything shaped like "items in, items out": smelting, cooking, fletching, potion brewing, combining two halves of a key. For gathering from the world (chopping a tree, mining a rock) use a task instead; see Tasks and quests. For random rewards from an item or object, use a loot table.
Opening the tool#
- Click Tools in the top bar.
- Click Recipe Editor on the hub.
The tool is laid out in three panels:
- Recipes (left) — a Categories list (starting with All) with a + button to add a category, then a Search recipes... box, the recipe list and + Add Recipe. Each list entry shows the primary product's icon, an arrow and the ingredient icons. Right-click a recipe for Duplicate or Delete; right-click a category for Rename or Delete.
- Editor (center) — open recipes appear as tabs along the top, titled after their primary product (right-click a tab for Close Tab, Close Other Tabs, Close All Tabs). The header row holds the Category dropdown and Save. Below it, Products and Ingredient Groups sit side by side, and under them the Main fields and the Requirements and Experience Rewards lists.
- Items (right) — every item in the project with a Qty quick-add value (default 1), a Search items... box and the hint "Double-click to add, or drag onto a group".
Recipes save when you click Save, press Ctrl+S, or leave the tool; tabs with unsaved changes carry a *. Deleting a recipe writes the file immediately.
Create a recipe#
- Select a category on the left if you want the new recipe filed under it (otherwise it goes to General), then click + Add Recipe. A recipe opens with one empty ingredient group and an empty primary product group.
- Click the Item Group 1 card under Ingredient Groups so it is highlighted, set Qty in the Items panel, and double-click the ingredient items (or drag them onto the card). Each item appears as a tile with its quantity in the corner; click the quantity to edit it in place, and use the tile's remove button to take it out.
- In the group's Actions list, set how the recipe fires. A new recipe's first group starts with no actions ("No actions — add one below"): click + Action to add one, which defaults to Use On Tool, then pick a type from the dropdown and fill in the field beside it (see the activation table below). + Action again adds an alternative trigger to the same group; + Group adds another ingredient group, created with one Use On Tool action of its own.
- Click the Primary Product card under Products and add the output items the same way.
- Under Requirements, click + Add to add a row: choose the skill from the dropdown (filled from your project's skills) and the minimum Lv (1–120). Under Experience Rewards, click + Add and set the skill and XP (0–99,999, in quarter-point steps; default 10).
- Set Crafting Ticks and Animation ID in Main if you use them (see the runtime section).
- Click Save.
Categories#
Categories are labels used to file recipes in the left panel and in the Category dropdown. + opens the New Category dialog; the category appears in the list even before a recipe uses it. Deleting a category moves its recipes to General. Categories are stored on each recipe, not in a separate list, so a category with no recipes is not saved and is gone after you restart the editor.
Activation types#
Each ingredient group carries one or more actions. An action is the interaction that triggers the recipe; any of a group's actions fires it.
| Action | Field | Fires when |
|---|---|---|
| Use On Tool | Tool item id | The player uses the tool on an item in the group, or the item on the tool. The tool must be in the inventory and is not consumed. |
| Use On Each Other | none | The player uses one item in the group on another item in the same group. |
| Use On Object | Obj object id | The player uses an item in the group on that world object (a furnace, an anvil, a range). |
| Use On NPC | NPC npc id | The player uses an item in the group on that NPC. |
| Item Interaction | option text, e.g. Craft | The player clicks that option on an item in the group. The option is matched case-insensitively against what the client sends. |
| Command | command name, e.g. craft_bronze | The player types ::craft_bronze. The module registers the command at startup; the first recipe to claim a name keeps it. |
| Custom | method name | Ignored by the bundled module. Use it to mark recipes that your own scripts trigger. |
Item and object ids are the numbers shown in the Data Editor. For a station-based craft, put the ingredient in one group with a Use On Object action pointing at the station; for a tool craft, list the ingredients and name the tool in a Use On Tool action.
Products#
Products are grouped, and each group has a Chance (0–100%). Every recipe has a Primary Product group. + Add Secondary Group adds a yellow secondary group (created at 25%), and + Add Failure Group adds a red failure group (created at 1%). Secondary and failure groups can be removed; the primary cannot. When you change one group's chance the editor rebalances the others so the three sum to 100, and a recipe with only a primary group is pinned to 100%.
Files written#
Recipes are saved to server/schemas/Crafting/recipes/recipes_data.json, with a recipes.json schema description created beside it on first save. The file is a JSON array of recipes.
[
{
"recipe_id": "recipe_1776543210123",
"category": "Smithing",
"animation_id": 0,
"crafting_ticks": 4,
"skill_requirements": [ { "skill_id": 10, "level": 5 } ],
"skill_rewards": [ { "skill_id": 10, "experience": 25 } ],
"ingredients": [
{
"items": [ { "item_id": 10, "quantity": 1 } ],
"actions": [ { "activation_type": "use_on_object", "activation_data": "4" } ]
}
],
"products": [
{ "type": "primary", "chance": 100.0, "items": [ { "item_id": 11, "quantity": 1 } ] }
]
}
]
| Field | Type | Meaning |
|---|---|---|
recipe_id | string | Unique id. The editor generates recipe_<timestamp>; duplicating appends _copy. Scripts and the MCP tool address recipes by this id. |
name | string | Optional display name written by the MCP tool. The editor shows recipes by their primary product instead and does not edit this field. |
category | string | Filing label, default General. |
animation_id | int | Loaded by the server but not played by the bundled module. |
crafting_ticks | int | 1–999, default 4. Loaded by the server but not used by the bundled module, which crafts instantly. |
skill_requirements[] | array | skill_id + level. The player must meet every entry. |
skill_rewards[] | array | skill_id + experience. Granted on success. A recipe with no rewards grants no XP. |
ingredients[] | array | Groups of items[] (item_id, quantity) and actions[] (activation_type, activation_data). |
products[] | array | Groups of type (primary, secondary, failure), chance, optional name, and items[]. |
skill_id, required_level, experience_reward, success_rate, tool_item_id | legacy | Written by the MCP tool for older modules, and declared in the recipes.json the editor generates when none exists. The server does not read them; use the arrays and a Use On Tool action instead. |
Older files that stored a flat ingredients list, a flat products list or a single activation_type per group are migrated to the grouped shape when the tool opens them.
How the server crafts#
The Crafting gameplay system owns CraftingModule.cs and the Crafting schema folder. At startup the module loads every recipe with a non-empty recipe_id, registers an item script for every ingredient and tool item that appears in a non-Custom group, and registers a ::command for every Command action.
When a matching interaction arrives the module attempts the craft:
- Every Requirements entry is checked against the player's level ("You need a higher level to craft this.").
- Every Use On Tool tool must be in the inventory ("You don't have the required tool.").
- Every ingredient across all groups must be present in the required quantity ("You don't have the required ingredients.").
- Each product group is rolled. The player needs one free inventory slot per granted product entry, counted before ingredients are removed ("Your inventory is full.").
- Ingredients are removed, products are added, and each Experience Rewards entry grants XP (which can level the player up; see Skills).
A failed attempt still counts as handled, so the player sees the reason instead of "Nothing interesting happens." Item clicks are offered to task triggers first and to item scripts (including crafting) second; see NPC, object and interaction scripts for the full order.
Scripts can drive crafting directly: RecipeRegistry.Instance.AttemptCraft(player, "smelt_bronze") returns a CraftResult (Success, InsufficientLevel, MissingTool, MissingIngredients, NoInventorySpace, RecipeNotFound), and GetById, GetByCategory, GetBySkill and GetAll read the registry. This is how a workbench object script or a crafting interface can offer a menu of recipes rather than relying on use-item interactions.
With AI (MCP)#
define_recipe— upserts one recipe byrecipe_id. The simple form takesingredients,products,activation_type(defaultuse_on_object) andactivation_data; the full form takesingredient_groups(each with its own activation) andproduct_groups(primary,secondary,failurewithchance). Level gates and XP areskill_requirementsandskill_rewards; the flatskill_id,required_levelandexperience_rewardspellings are folded into one-element arrays. Every item must exist and every skill must be inskills_config.json. Fields the tool does not model are preserved on update. PassdryRun: trueto preview.get_schema_datawith the table namerecipesreads the raw rows.
See the tools reference for full argument lists.
