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>
This commit is contained in:
parent
57e1f0f389
commit
6789ca739f
5 changed files with 77 additions and 5 deletions
|
|
@ -39,6 +39,9 @@ const TABLE: Dictionary = {
|
||||||
# Phase 6 — new item types (carpenter bench + smelter outputs)
|
# Phase 6 — new item types (carpenter bench + smelter outputs)
|
||||||
&"item.plank": "Plank",
|
&"item.plank": "Plank",
|
||||||
&"item.stone_block": "Stone block",
|
&"item.stone_block": "Stone block",
|
||||||
|
# Phase 19 — smelted ingots
|
||||||
|
&"item.iron_ingot": "Iron ingot",
|
||||||
|
&"item.gold_ingot": "Gold ingot",
|
||||||
# Phase 7 — food loop and cooking chain item types
|
# Phase 7 — food loop and cooking chain item types
|
||||||
&"item.flour": "Flour",
|
&"item.flour": "Flour",
|
||||||
&"item.bread": "Bread",
|
&"item.bread": "Bread",
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,13 @@ var skill_threshold: int = 0
|
||||||
## call Strings.t("item." + id) for player-visible text.
|
## call Strings.t("item." + id) for player-visible text.
|
||||||
var label: String = ""
|
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 ───────────────────────────────────────────────────────────────
|
# ── save / load ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -70,6 +77,7 @@ func to_dict() -> Dictionary:
|
||||||
"required_skill": String(required_skill),
|
"required_skill": String(required_skill),
|
||||||
"skill_threshold": skill_threshold,
|
"skill_threshold": skill_threshold,
|
||||||
"label": label,
|
"label": label,
|
||||||
|
"target_workbench": String(target_workbench),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -85,4 +93,7 @@ static func from_dict(d: Dictionary) -> Recipe:
|
||||||
r.required_skill = StringName(d.get("required_skill", str(SKILL_CRAFTING)))
|
r.required_skill = StringName(d.get("required_skill", str(SKILL_CRAFTING)))
|
||||||
r.skill_threshold = int(d.get("skill_threshold", 0))
|
r.skill_threshold = int(d.get("skill_threshold", 0))
|
||||||
r.label = str(d.get("label", ""))
|
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
|
return r
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ static func plank() -> Recipe:
|
||||||
r.work_ticks = 60 # ~3 sim seconds at 1× (20 Hz × 3 s = 60 ticks)
|
r.work_ticks = 60 # ~3 sim seconds at 1× (20 Hz × 3 s = 60 ticks)
|
||||||
r.required_skill = Recipe.SKILL_CRAFTING
|
r.required_skill = Recipe.SKILL_CRAFTING
|
||||||
r.skill_threshold = 0
|
r.skill_threshold = 0
|
||||||
|
r.target_workbench = &"carpenter"
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -35,6 +36,7 @@ static func stone_block() -> Recipe:
|
||||||
r.work_ticks = 80 # ~4 sim seconds at 1×
|
r.work_ticks = 80 # ~4 sim seconds at 1×
|
||||||
r.required_skill = Recipe.SKILL_CRAFTING
|
r.required_skill = Recipe.SKILL_CRAFTING
|
||||||
r.skill_threshold = 0
|
r.skill_threshold = 0
|
||||||
|
r.target_workbench = &"smelter"
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,6 +54,7 @@ static func flour() -> Recipe:
|
||||||
r.work_ticks = 50 # ~2.5 sim seconds at 1× — quick grinding pass
|
r.work_ticks = 50 # ~2.5 sim seconds at 1× — quick grinding pass
|
||||||
r.required_skill = Recipe.SKILL_CRAFTING
|
r.required_skill = Recipe.SKILL_CRAFTING
|
||||||
r.skill_threshold = 0
|
r.skill_threshold = 0
|
||||||
|
r.target_workbench = &"millstone"
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -125,9 +128,46 @@ static func quarry_stone() -> Recipe:
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
# ── Phase 19 — iron/gold smelting ────────────────────────────────────────────
|
||||||
|
|
||||||
|
static func iron_smelt() -> Recipe:
|
||||||
|
## Iron ore → iron ingot. Worked at the Smelter (accepted_skill = crafting).
|
||||||
|
## Secondary fuel ingredient (wood) is recorded but not yet enforced at
|
||||||
|
## runtime — CraftingProvider only checks the primary ingredient today.
|
||||||
|
var r := Recipe.new()
|
||||||
|
r.id = &"iron_smelt"
|
||||||
|
r.label = "Smelt iron ingot"
|
||||||
|
r.ingredient_type = Item.TYPE_IRON_ORE
|
||||||
|
r.ingredient2_type = Item.TYPE_WOOD
|
||||||
|
r.ingredient2_count = 1
|
||||||
|
r.output_type = Item.TYPE_IRON_INGOT
|
||||||
|
r.work_ticks = 200 # ~10 sim seconds at 1× — slower than stone-block
|
||||||
|
r.required_skill = Recipe.SKILL_CRAFTING
|
||||||
|
r.skill_threshold = 0
|
||||||
|
r.target_workbench = &"smelter"
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
static func gold_smelt() -> Recipe:
|
||||||
|
## Gold ore → gold ingot. Same smelting chain as iron.
|
||||||
|
var r := Recipe.new()
|
||||||
|
r.id = &"gold_smelt"
|
||||||
|
r.label = "Smelt gold ingot"
|
||||||
|
r.ingredient_type = Item.TYPE_GOLD
|
||||||
|
r.ingredient2_type = Item.TYPE_WOOD
|
||||||
|
r.ingredient2_count = 1
|
||||||
|
r.output_type = Item.TYPE_GOLD_INGOT
|
||||||
|
r.work_ticks = 200 # ~10 sim seconds at 1×
|
||||||
|
r.required_skill = Recipe.SKILL_CRAFTING
|
||||||
|
r.skill_threshold = 0
|
||||||
|
r.target_workbench = &"smelter"
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
## Returns one fresh instance of every recipe in the catalog. Used by UI
|
## Returns one fresh instance of every recipe in the catalog. Used by UI
|
||||||
## recipe-pickers to enumerate available bills; callers filter by
|
## recipe-pickers to enumerate available bills; callers filter by
|
||||||
## `recipe.required_skill` against the workbench's `accepted_skill`.
|
## `recipe.required_skill` and `recipe.target_workbench` against the
|
||||||
|
## workbench's `accepted_skill` and `label_text`.
|
||||||
static func all() -> Array[Recipe]:
|
static func all() -> Array[Recipe]:
|
||||||
return [
|
return [
|
||||||
plank(),
|
plank(),
|
||||||
|
|
@ -137,4 +177,6 @@ static func all() -> Array[Recipe]:
|
||||||
meal_from_vegetables(),
|
meal_from_vegetables(),
|
||||||
cremate_corpse(),
|
cremate_corpse(),
|
||||||
quarry_stone(),
|
quarry_stone(),
|
||||||
|
iron_smelt(),
|
||||||
|
gold_smelt(),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,12 @@ const TYPE_BREAD: StringName = &"bread"
|
||||||
# Phase 14 — cremation output. One ash item drops per cremated corpse.
|
# Phase 14 — cremation output. One ash item drops per cremated corpse.
|
||||||
const TYPE_ASH: StringName = &"ash"
|
const TYPE_ASH: StringName = &"ash"
|
||||||
|
|
||||||
|
# Phase 19 — smelted ingots (iron_ore → iron_ingot, gold → gold_ingot).
|
||||||
|
# No dedicated atlas sprite; on-floor visual uses the procedural hue-hashed
|
||||||
|
# square fallback in _draw() and the matching shape is in draw_item_shape().
|
||||||
|
const TYPE_IRON_INGOT: StringName = &"iron_ingot"
|
||||||
|
const TYPE_GOLD_INGOT: StringName = &"gold_ingot"
|
||||||
|
|
||||||
const ALL_TYPES: Array[StringName] = [
|
const ALL_TYPES: Array[StringName] = [
|
||||||
TYPE_WOOD, TYPE_STONE, TYPE_IRON_ORE, TYPE_COPPER_ORE,
|
TYPE_WOOD, TYPE_STONE, TYPE_IRON_ORE, TYPE_COPPER_ORE,
|
||||||
TYPE_SILVER, TYPE_GOLD, TYPE_CLOTH, TYPE_VEGETABLE,
|
TYPE_SILVER, TYPE_GOLD, TYPE_CLOTH, TYPE_VEGETABLE,
|
||||||
|
|
@ -72,6 +78,7 @@ const ALL_TYPES: Array[StringName] = [
|
||||||
TYPE_PLANK, TYPE_STONE_BLOCK,
|
TYPE_PLANK, TYPE_STONE_BLOCK,
|
||||||
TYPE_FLOUR, TYPE_BREAD,
|
TYPE_FLOUR, TYPE_BREAD,
|
||||||
TYPE_ASH,
|
TYPE_ASH,
|
||||||
|
TYPE_IRON_INGOT, TYPE_GOLD_INGOT,
|
||||||
]
|
]
|
||||||
|
|
||||||
# ── quality system (docs/architecture.md "Quality system") ───────────────────
|
# ── quality system (docs/architecture.md "Quality system") ───────────────────
|
||||||
|
|
|
||||||
|
|
@ -415,14 +415,23 @@ func _on_recipe_chosen(id: int) -> void:
|
||||||
_populate_bills()
|
_populate_bills()
|
||||||
|
|
||||||
|
|
||||||
## Returns all catalog recipes whose required_skill matches the workbench's
|
## Returns all catalog recipes visible at the current workbench.
|
||||||
## accepted_skill. Returns an empty array when no workbench is set.
|
## Two-pass filter:
|
||||||
|
## 1. required_skill must match workbench.accepted_skill.
|
||||||
|
## 2. If the recipe has a target_workbench set, it must match
|
||||||
|
## workbench.label_text.to_lower() — e.g. "Carpenter" → "carpenter".
|
||||||
|
## Recipes with an empty target_workbench pass to any matching-skill bench.
|
||||||
|
## Returns an empty array when no workbench is set.
|
||||||
func _filtered_recipes() -> Array[Recipe]:
|
func _filtered_recipes() -> Array[Recipe]:
|
||||||
if current_workbench == null:
|
if current_workbench == null:
|
||||||
return []
|
return []
|
||||||
|
var workbench_key: StringName = StringName(current_workbench.label_text.to_lower())
|
||||||
var result: Array[Recipe] = []
|
var result: Array[Recipe] = []
|
||||||
for r in RecipeCatalog.all():
|
for r in RecipeCatalog.all():
|
||||||
if r.required_skill == current_workbench.accepted_skill:
|
if r.required_skill != current_workbench.accepted_skill:
|
||||||
|
continue
|
||||||
|
if r.target_workbench != &"" and r.target_workbench != workbench_key:
|
||||||
|
continue
|
||||||
result.append(r)
|
result.append(r)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue