rimlike/scenes/ai/recipe.gd
megaproxy d98d2c2425 Renewable resources: tree growth + WildGrowth + Quarry on BigRockNode
Trees: 4 growth stages (Sapling→Young→Growing→Mature), only Mature
yields wood. WildGrowth ticker fires every in-game hour; rejection-
samples grass tiles and plants a sapling with ~30% probability (capped
at MAP_TREE_LIMIT=60). New `paint_plant_tree` designation lets the
player manually plant — ghost sapling registered as a build_site that
ConstructionProvider fulfils. Stage round-trips through save/load.
Initial seed mixes 4 saplings + 6 mature so growth is visible day 1.

Quarry: new BigRockNode entity (2×2 permanent stone outcrop, never
depletes). 3 nodes seeded far from cabin. New QuarryWorkbench
(extends Workbench, auto-FOREVER `quarry_stone` bill, recipe drops
1 stone per 300 work-ticks). New `paint_quarry` designation only
accepts BigRockNode tiles. CraftingProvider now supports recipes
with `ingredient_count == 0` — skips ingredient-fetch and goes
straight to walk+craft toils. Recipe gains `ingredient_count` field
(defaults 0). Save/load layering: big_rock_node spawns at priority 0
(same as rock/tree), quarry_workbench at priority 2 (after the node).

UI: Plant tree + Build quarry buttons added to Build drawer.
build_drawer_thumb gains `plant_tree` (sapling sprout in dirt) and
`paint_quarry` (stone block + chisel + cut-stone pile) shapes.
inspect_tooltip recognises BigRockNode + shows tree growth stage on
hover.

Delegation: gdscript-refactor (Sonnet ×2) for trees full impl +
quarry skeleton; quick-edit (Haiku) for CraftingProvider no-ingredient
plumbing + TopBar polish; integration handled on Opus.
2026-05-16 16:36:16 +01:00

88 lines
3.4 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 = ""
# ── 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,
}
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", ""))
return r