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 = &"" ## 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 = "" # ── 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), "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, } 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.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", "")) return r