rimlike/scenes/ai/recipe.gd
megaproxy 6789ca739f add iron/gold smelting + disambiguate workbench-vs-recipe
Q: iron_smelt (iron_ore + wood → iron_ingot) and gold_smelt
(gold + wood → gold_ingot) recipes added at Smelter, using the
existing ingredient2 buffer mechanism. New TYPE_IRON_INGOT and
TYPE_GOLD_INGOT item types (procedural hue-hash draw for now).

R: new Recipe.target_workbench field (StringName, empty = any matching
skill) round-trips through to_dict/from_dict. Workbench bill picker
filters by both required_skill AND target_workbench vs lower-cased
label_text. plank → carpenter, stone_block/iron_smelt/gold_smelt →
smelter, flour → millstone. Cooking-only recipes (bread, meal) stay
unrestricted since Hearth is the only cooking workbench.

9 recipes total now, 4 distinct workbench routes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:45:11 +01:00

99 lines
4.1 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class_name Recipe extends RefCounted
## A recipe describes one production step: what raw material goes in, what
## product comes out, how long it takes, and what skill is required.
##
## Phase 6 ships single-ingredient / single-output recipes. Multi-input is
## a Phase 7+ expansion — the dict seam in to_dict/from_dict already uses
## the same key names as the full architecture.md spec so no migration is needed.
##
## Save/load contract:
## var r2 := Recipe.from_dict(r.to_dict())
## assert(r2.id == r.id and r2.work_ticks == r.work_ticks)
const SKILL_CRAFTING: StringName = &"crafting"
const SKILL_COOKING: StringName = &"cooking"
## Unique identifier — e.g. &"plank", &"stone_block", &"basic_meal".
var id: StringName = &""
## Item type consumed by this recipe (single-ingredient for Phase 6).
var ingredient_type: StringName = &""
## Count of ingredient_type required by this recipe. 0 = no ingredient (work only).
var ingredient_count: int = 0
## Phase 14 — optional secondary ingredient. Empty string = no secondary.
## CraftingProvider Phase 14 follow-up: enforce pickup of ingredient2 before
## assigning a pawn to this bill (currently stub — only ingredient_type enforced).
var ingredient2_type: StringName = &""
var ingredient2_count: int = 0
## Item type produced by this recipe.
var output_type: StringName = &""
## Sim ticks needed at 1× skill (no pawn modifier applied here).
var work_ticks: int = 100
## Which skill governs this recipe's quality roll and speed.
var required_skill: StringName = SKILL_CRAFTING
## Pawn's skill level must be >= this to be assigned this bill.
## Per design.md: skills modify duration and quality, never permission — so
## this threshold defaults to 0 (no gate); bill UI lets the player raise it.
var skill_threshold: int = 0
## Human-readable label for Audit logs and (later) bill UI. Not i18n'd here;
## call Strings.t("item." + id) for player-visible text.
var label: String = ""
## Optional workbench affinity. When non-empty, the bill picker only shows this
## recipe at workbenches whose label_text.to_lower() matches this value.
## Empty string (default) means "any workbench whose accepted_skill matches" —
## i.e. the pre-Phase-19 behaviour and the correct fallback for older saves.
## Values should be lowercase: &"carpenter", &"smelter", &"millstone".
var target_workbench: StringName = &""
# ── save / load ───────────────────────────────────────────────────────────────
## Player-visible display name for this recipe. Used by the workbench bill
## editor's recipe-picker and the bill list. Falls back to `id` if `label`
## is empty (shouldn't happen for catalog recipes, but defensive).
func display_name() -> String:
if label.is_empty():
return str(id)
return label
func to_dict() -> Dictionary:
return {
"id": String(id),
"ingredient_type": String(ingredient_type),
"ingredient_count": ingredient_count,
"ingredient2_type": String(ingredient2_type),
"ingredient2_count": ingredient2_count,
"output_type": String(output_type),
"work_ticks": work_ticks,
"required_skill": String(required_skill),
"skill_threshold": skill_threshold,
"label": label,
"target_workbench": String(target_workbench),
}
static func from_dict(d: Dictionary) -> Recipe:
var r := Recipe.new()
r.id = StringName(d.get("id", ""))
r.ingredient_type = StringName(d.get("ingredient_type", ""))
r.ingredient_count = int(d.get("ingredient_count", 0))
r.ingredient2_type = StringName(d.get("ingredient2_type", ""))
r.ingredient2_count = int(d.get("ingredient2_count", 0))
r.output_type = StringName(d.get("output_type", ""))
r.work_ticks = int(d.get("work_ticks", 100))
r.required_skill = StringName(d.get("required_skill", str(SKILL_CRAFTING)))
r.skill_threshold = int(d.get("skill_threshold", 0))
r.label = str(d.get("label", ""))
# Default to empty — old saves without this key get the pre-Phase-19
# "any matching skill" behaviour, which is correct.
r.target_workbench = StringName(d.get("target_workbench", ""))
return r