Compare commits
1 Commits
acb96933a7
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| cd466c73b5 |
@@ -1,68 +1,115 @@
|
|||||||
# RecipeManager
|
# Recipes
|
||||||
|
|
||||||
## Overview
|
## FoodItem
|
||||||
|
|
||||||
`RecipeManager` is a static Godot class that centralizes recipe data for food item combinations and allows runtime lookup of result scenes.
|
Every ingredient, meal, and side in the game is a `FoodItem` node (`prefabs/food_item.gd`), a sibling component on the pickable item's scene. It's just three exported fields:
|
||||||
|
|
||||||
The manager loads a single YAML file at startup: `res://recipes.yaml`, using the installed `addons/yaml` addon.
|
- `id: String` — the recipe key this item is looked up by in `recipes.yaml` (e.g. `"raw_burger"`).
|
||||||
|
- `type: Type` — `MEAL`, `SIDE`, `INGREDIENT`, or `NONE`. Used for things like which plate slot an item lands in.
|
||||||
|
- `sell_value: int` — money paid out when the item is sold/consumed.
|
||||||
|
|
||||||
## Responsibilities
|
`id` is the link between a spawned scene and its entry in `recipes.yaml` — every process below keys off it.
|
||||||
|
|
||||||
- Load recipe definitions from `recipes.yaml`.
|
## `recipes.yaml`
|
||||||
- Provide symmetric lookups for two-item combining recipes so `A + B` and `B + A` map to the same result.
|
|
||||||
- Resolve item IDs to their corresponding scene resources.
|
|
||||||
- Print all loaded combining recipes when the application starts.
|
|
||||||
|
|
||||||
## Data structure in `recipes.yaml`
|
`RecipeManager` loads a single file, `res://recipes.yaml`, with one top-level section per process plus an `items` section mapping each `id` to its scene:
|
||||||
|
|
||||||
The recipe YAML file contains two top-level sections:
|
|
||||||
|
|
||||||
- `items`: maps item IDs to their scene path and optional metadata.
|
|
||||||
- `combining`: maps result item IDs to one or more recipe pairs.
|
|
||||||
|
|
||||||
Example structure:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
items:
|
items:
|
||||||
hamburger:
|
raw_burger:
|
||||||
scene: res://Items/hamburger.tscn
|
scene: res://items/burger.tscn
|
||||||
type: meal
|
cooked_burger:
|
||||||
burger_buns:
|
scene: res://items/cooked_burger.tscn
|
||||||
scene: res://Items/BurgerBuns.tscn
|
|
||||||
type: ingredient
|
|
||||||
|
|
||||||
combining:
|
combining: # instant, two items -> one
|
||||||
hamburger:
|
hamburger:
|
||||||
- [cooked_burger, burger_buns]
|
- [cooked_burger, burger_buns]
|
||||||
- [charcoal, charcoal]
|
|
||||||
charcoal:
|
cooking: # timed, one item -> one (a station's work meter)
|
||||||
- [cube, cube]
|
cooked_burger:
|
||||||
|
ingredient: raw_burger
|
||||||
|
time: 4
|
||||||
|
|
||||||
|
chopping: # timed, one item -> one
|
||||||
|
chopped_potato:
|
||||||
|
ingredient: potato
|
||||||
|
work: 30
|
||||||
|
|
||||||
|
rolling: # parsed, no station consumes it yet
|
||||||
|
pie_base:
|
||||||
|
ingredient: dough
|
||||||
|
work: 80
|
||||||
|
|
||||||
|
augmenting: # parsed, no station consumes it yet
|
||||||
|
cooked_burger:
|
||||||
|
has_tomato: sliced_tomato
|
||||||
```
|
```
|
||||||
|
|
||||||
### `items`
|
`cooking` entries may also be a list, so multiple ingredients can produce the same result (see `charcoal` in the real file, which can be made by overcooking three different things).
|
||||||
|
|
||||||
Each entry under `items` uses the item ID as the key.
|
## RecipeManager
|
||||||
The manager reads the `scene` field for each item and uses it to resolve the packed scene for recipe results.
|
|
||||||
|
|
||||||
### `combining`
|
`RecipeManager` (`global/RecipeManager.gd`) is a static class — no instance needed. On first use it lazily parses `recipes.yaml` (via the `YAML` addon) into five internal maps (`_combining_map`, `_cooking_map`, `_chopping_map`, `_rolling_map`, `_augmenting_map`) plus `_scene_paths`, and never re-parses after (`_loaded` guard).
|
||||||
|
|
||||||
Each entry under `combining` represents a result item ID, and its value is a list of recipe pairs.
|
It doesn't hand out those raw maps. Instead it exposes narrow getters that the rest of the game calls:
|
||||||
Each recipe pair is a two-item array of ingredient IDs.
|
|
||||||
This allows a single result item to have multiple valid recipes.
|
|
||||||
|
|
||||||
The manager normalizes each ingredient pair using a sorted key string internally, so lookups are symmetric.
|
| Function | Returns |
|
||||||
|
|---|---|
|
||||||
|
| `get_combination_result(a, b)` | `PackedScene` for combining two ids (order-independent) |
|
||||||
|
| `get_cooking_result(ingredient)` | result id, or `""` if not cookable |
|
||||||
|
| `get_cooking_time(ingredient)` | seconds of work needed |
|
||||||
|
| `get_chopping_result(ingredient)` | result id, or `""` if not choppable |
|
||||||
|
| `get_chopping_work(ingredient)` | work needed |
|
||||||
|
| `get_item_scene(id)` | `PackedScene` for any item id |
|
||||||
|
| `print_all_recipes()` | debug dump of every loaded recipe, called once from `main.gd` at startup |
|
||||||
|
|
||||||
## Runtime behavior
|
`rolling` and `augmenting` are parsed and included in `print_all_recipes()`, but currently have no matching getters or station — they're not wired into a conversion process yet.
|
||||||
|
|
||||||
- `FoodItem.id` is used when combining items to look up the recipe result.
|
## Conversion processes
|
||||||
- `RecipeManager.get_combination(first_id, second_id)` computes a canonical key for the pair and returns the resulting item's scene.
|
|
||||||
- On startup, `main.gd` calls `RecipeManager.print_all_recipes()`, which logs each combining recipe in the form:
|
|
||||||
|
|
||||||
`ingredient_a + ingredient_b -> result_id`
|
Two different mechanisms turn one `FoodItem` into another today:
|
||||||
|
|
||||||
## Notes
|
**Combining** — instant, no station involved. `CombinableItem` (`prefabs/combinable_item.gd`) sits on every pickable item; while the item is snapped into a zone, its trigger area watches for another `FoodItem` entering. On contact it asks `RecipeManager.get_combination_result(a, b)` — if a recipe exists, it despawns both ingredients and spawns the result in their place.
|
||||||
|
|
||||||
- `RecipeManager` prefers the installed YAML addon to parse `recipes.yaml` when it is available.
|
**Cooking / chopping** — timed, driven by `WorkStation`'s shared work meter (`current_work` / `max_work` / `result_id`, see [Stations.md](Stations.md)). When a `Hob` or `Counter` picks up a `FoodItem`, it asks `RecipeManager` whether that ingredient has a recipe for its process (`get_cooking_result`/`get_chopping_result`) and, if so, how much work it needs (`get_cooking_time`/`get_chopping_work`), then starts accumulating work — a `Hob` ticks it every frame, a `Counter` adds it per knife swipe. Once `current_work` reaches `max_work`, `WorkStation.convert_item()` calls `RecipeManager.get_item_scene(result_id)`, despawns the original item, and spawns the result in the same snap zone.
|
||||||
- If the addon is unavailable in the current runtime, the manager falls back to a lightweight built-in parser for the simple `items`/`combining` file format.
|
|
||||||
- Scene paths now live in the YAML data instead of being hardcoded in the manager.
|
Separately, `ItemContainer` (`Containers/container.gd`) calls `get_item_scene(id)` too, but only to instantiate the correct visual mesh for each id in a plate's synced contents list — that's a display lookup, not a conversion.
|
||||||
- Because `RecipeManager` is static, it can be used from any script without creating an instance.
|
|
||||||
|
## Diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
YAML["recipes.yaml"] -->|"YAML.load_file()"| RM["RecipeManager<br/>(global/RecipeManager.gd)"]
|
||||||
|
RM --> Maps["_combining_map / _cooking_map / _chopping_map / _scene_paths<br/>(_rolling_map / _augmenting_map parsed, not yet consumed)"]
|
||||||
|
|
||||||
|
subgraph Instant["Instant conversion"]
|
||||||
|
direction TB
|
||||||
|
CI["CombinableItem<br/>(prefabs/combinable_item.gd)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Timed["Timed conversion"]
|
||||||
|
direction TB
|
||||||
|
Hob["Hob<br/>(stations/hob.gd)"]
|
||||||
|
Counter["Counter<br/>(stations/counter.gd)"]
|
||||||
|
WS["WorkStation.convert_item()<br/>(abstract/work_station.gd)"]
|
||||||
|
Hob -.->|"result_id, max_work"| WS
|
||||||
|
Counter -.->|"result_id, max_work"| WS
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Display["Display only, not a conversion"]
|
||||||
|
direction TB
|
||||||
|
Container["ItemContainer<br/>(Containers/container.gd)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
CI -->|"get_combination_result(a, b)"| RM
|
||||||
|
Hob -->|"get_cooking_result / get_cooking_time"| RM
|
||||||
|
Counter -->|"get_chopping_result / get_chopping_work"| RM
|
||||||
|
WS -->|"get_item_scene(result_id)"| RM
|
||||||
|
Container -->|"get_item_scene(id)"| RM
|
||||||
|
|
||||||
|
Spawn(("NetworkManager.spawn_item"))
|
||||||
|
CI ==>|"instantiates result"| Spawn
|
||||||
|
WS ==>|"instantiates result"| Spawn
|
||||||
|
```
|
||||||
|
|
||||||
|
Solid arrows are calls into `RecipeManager`; dotted arrows are `Hob`/`Counter` handing their recipe off to `WorkStation`'s shared work meter; thick arrows are the actual item conversion. `CombinableItem` and `WorkStation` are grouped in adjacent lanes so both paths to `NetworkManager.spawn_item` stay short; `ItemContainer` sits in its own lane since it never spawns anything.
|
||||||
|
|||||||
Reference in New Issue
Block a user