aboutsummaryrefslogtreecommitdiff
path: root/worldbuilding_guide/recipes.md
blob: 335a6cce7e32666c9f22f620e140c2389b4a5454 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# The House of Icarus - World Building Guide

## Recipes

Recipes define how items are processed on stations to produce new items. They are the foundation for cooking, smithing, crafting, and fletching.

Recipe files live in `data/recipes/<type>/<id>.yaml`. Two formats are supported: **individual** (one recipe per file) and **consolidated** (multiple related recipes per file). Consolidated format is preferred when many recipes share the same type, station, and materials (e.g., all bronze smithing products in one file).

### Recipe vs MadeFrom

- **Recipes** (`data/recipes/`) use a station (fire, range, furnace, anvil, etc.). The player must be near the station object.
- **MadeFrom** (on the item YAML directly) is for item-on-item combinations that need no station. The player combines ingredients from their inventory.

### Recipe YAML Reference

| Field          | Type           | Description                                     |
| -------------- | -------------- | ----------------------------------------------- |
| `id`           | string         | Unique recipe identifier                        |
| `type`         | string         | Recipe type (cooking, smithing, crafting, etc.) |
| `skill`        | string         | Skill checked for success                       |
| `level`        | int            | Required skill level                            |
| `xp`           | int            | XP awarded on success                           |
| `wait`         | float64        | Ticks per craft cycle (default 6)               |
| `station`      | []string       | Station object IDs required                     |
| `consume`      | []ConsumeEntry | Ingredients consumed (see below)                |
| `output`       | string         | ItemID produced on success                      |
| `output_qty`   | int            | Quantity produced (default 1, for stackables)   |
| `fail`         | string         | ItemID produced on failure (optional)           |
| `message`      | string         | Success message                                 |
| `fail_message` | string         | Failure message                                 |
| `success`      | SuccessFormula | Optional skill check formula (see Behaviors)    |

The recipe's name in menus comes from the output item's `name` field, colored using the output item's `color`.

### ConsumeEntry — Multi-Item Ingredient Slots

Each consume entry defines an ingredient slot with multiple valid items. The first matching item found in the player's inventory is consumed.

```yaml
consume:
  - items: [item_id, alternative_id, ...]
    quantity: 1
    byproducts: [byproduct_for_item, byproduct_for_alt, ...]
```

`byproducts` is optional. When present, it matches the `items` list by index — consuming `items[0]` returns `byproducts[0]` to inventory. Use `""` for items with no byproduct:

```yaml
consume:
  - items: [bucket_of_water, vial_of_water]
    quantity: 1
    byproducts: [empty_bucket, ""]   # bucket returns empty, vial is consumed entirely
  - items: [herb]
     quantity: 1                           # no byproducts — herb is consumed entirely
```

### Consolidated Format

For recipe categories where many recipes share the same `type`, `station`, and `wait` (e.g., smithing all bronze items from bronze bars, fletching all arrow shafts from logs), use a **consolidated** file. Shared fields are set once at the top level; each product entry provides only the fields that differ.

**Loader behavior:** At load time, the loader expands each `products` entry into an individual `RecipeDef` with the same shape as the individual format. The production engine sees no difference — all lookup, matching, and execution code works identically.

**Inheritance:** Child fields override parent. `consume` at the top level provides a default ingredient list; any product with different `consume` (e.g., a platebody needing 5 bars instead of 1) specifies its own. `output_qty`, `message`, `fail`, and `level` are per-product.

```yaml
# data/recipes/smithing/bronze.yaml — consolidated: 8 products in one file

type: smithing           # shared by all products
station: [anvil]         # shared by all products
wait: 4                  # shared by all products
consume:                 # default ingredient list — overridden per-product if needed
  - items: [bronze_bar]
    quantity: 1

products:
  - id: smith_bronze_dagger
    output: bronze_dagger
    level: 1
    xp: 12
    # inherits consume: 1 bronze_bar from parent

  - id: smith_bronze_nails
    output: bronze_nails
    level: 4
    xp: 12
    output_qty: 15        # stackable output, 15 per cycle

  - id: smith_bronze_full_helm
    output: bronze_full_helm
    level: 7
    xp: 25
    consume:               # overrides parent — 2 bars instead of 1
      - items: [bronze_bar]
        quantity: 2

  - id: smith_bronze_platebody
    output: bronze_platebody
    level: 18
    xp: 62
    consume:               # overrides parent — 5 bars
      - items: [bronze_bar]
        quantity: 5

  # ... dagger, sword, med_helm, arrowtips, bolts_unf ...
```

**Use consolidated files when:**
- Many recipes share the same `type`, `station`, and `material`
- Adding a new tier means copy-pasting identical consume/station/wait fields
- The relationship between recipes is easier to understand as a group

**Use individual files when:**
- The recipe is unique and doesn't share structure with others (e.g., compound smelting recipes with different ore ratios)
- You want fine-grained version control on a single recipe
- The recipe is complex enough that a consolidated file would be harder to read

Individual and consolidated files coexist — the loader handles both transparently.

### Station-based recipe (cooking on a fire)

```yaml
id: cook_trout
type: cooking
skill: cooking
level: 15
xp: 70
wait: 6
station: [fire, cooking_range]
consume:
  - items: [raw_trout]
    quantity: 1
output: trout
fail: burnt_fish
message: "Cooked to perfection. It looks great!"
fail_message: "You accidentally burn the trout."
```

### Station-specific recipe (range only, not fire)

```yaml
id: cook_bread
type: cooking
skill: cooking
level: 1
xp: 40
wait: 6
station: [cooking_range]
consume:
  - items: [bread_dough]
    quantity: 1
output: bread
fail: burnt_meat
message: "You bake the dough into a fresh loaf of bread."
fail_message: "You burn the bread to a crisp."
```

### Smelting recipe (ore to bar at a furnace)

Smelting recipes use `type: smelting` and `station: [furnace]`. The `smelt` command or `use <ore> on furnace` triggers them. Multi-ore recipes use `qty` on consume entries.

```yaml
id: smelt_steel
type: smelting
skill: smithing
level: 40
xp: 17
wait: 4
station: [furnace]
consume:
  - items: [iron_ore]
    quantity: 1
  - items: [coal]
    quantity: 2
output: steel_bar
message: "You remove a white hot steel bar!"
fail_message: "You fail to smelt a usable bar."
```

Iron smelting uses a `success` formula for its 50% failure rate:

```yaml
success:
  base: 0.5
  per_level: 0
  cap: 0.5
```

### Smithing recipe (bar to item at an anvil)

Smithing recipes use `type: smithing` and `station: [anvil]`. The `smith` command or `use <bar> on anvil` triggers them. Requires a hammer (`tool_type: hammer`) in inventory.

**Recommended:** Use consolidated format (one file per metal type) since all products for a given metal share the same bar, station, and wait. See [Consolidated Format](#consolidated-format) above.

The individual format is also supported:

```yaml
id: smith_steel_platebody
type: smithing
skill: smithing
level: 48
xp: 187
wait: 4
station: [anvil]
consume:
  - items: [steel_bar]
    quantity: 5
output: steel_platebody
message: "You hammer out a steel platebody."
```

For stackable outputs, use `output_qty`:

```yaml
id: smith_iron_nails
type: smithing
skill: smithing
level: 24
xp: 25
wait: 4
station: [anvil]
consume:
  - items: [iron_bar]
    quantity: 1
output: iron_nails
output_qty: 15
message: "You hammer out some iron nails."
```

### MadeFrom — Item-on-item combinations

Item-on-item combinations are defined directly on the output item via the `made_from` field. No recipe file needed.

```yaml
# data/items/bread_dough.yaml
id: bread_dough
name: bread dough
color: "222"
made_from:
  - items: [pot_of_flour]
    quantity: 1
  - items: [bucket_of_water, pitcher_of_water, vial_of_water]
    quantity: 1
```

The player uses `use flour on water` to combine them. The system finds that `bread_dough` can be made from these ingredients and produces it.

### Food/Healing Items

Items with `heal_value` and `eat_message` can be consumed via the `eat` command.

```yaml
id: bread
name: bread
color: "222"
description: "A fresh loaf of bread, still warm from the oven."
value: 5
heal_value: 5
eat_message: "You eat the bread. Warm and satisfying."
```