rimlike/scenes/main/main.gd
megaproxy bdd435202d Workbench bill editor — tap a workbench, see/edit bills
Tap-to-select chain extended to workbenches (pawn always wins on shared
tile). Mutually exclusive with pawn selection via EventBus —
selecting one clears the other.

New WorkbenchPanel (scenes/ui/workbench_panel.gd, ~432 LOC, layer 18,
right-anchored 360 px) mirrors PawnDetailPanel shape. Bill rows expose
recipe name, mode (FOREVER / COUNT / UNTIL_N), target count, completed
progress, pause, and remove. Add-bill popup filters RecipeCatalog.all()
by accepted_skill so a Hearth only offers cooking recipes.

Supporting plumbing:
- EventBus.workbench_selected / workbench_deselected signals.
- Workbench.remove_bill() — interrupts mid-craft cleanly via
  on_craft_interrupted() before erasing.
- RecipeCatalog.all() static enumerator + Recipe.display_name() helper.
- World.workbench_at_tile() lookup.
- i18n keys ui.bill.* and ui.workbench.* in strings.gd.

Closes the deferred Phase 17 "Bill UI for workbenches" item. Player-
built workbenches are now functionally configurable; before this
landed, only world.gd-hardcoded bills worked.
2026-05-16 00:29:46 +01:00

158 lines
6.7 KiB
GDScript

extends Node2D
## Bootstrap. Mounts the world view + UI overlay.
##
## Once we add menus / continue-game / new-game flows this will branch
## on game state. For Phase 1 it just instances the World and TopBar,
## which are children placed in main.tscn.
##
## Phase 15 — StorytellerBanner and StorytellerModal are runtime-instantiated
## here (same pattern as world.gd's BeautySystem / DirtinessSystem). Both are
## CanvasLayer nodes so they draw above the world regardless of scene-tree order.
##
## Phase 16 — LoadMenu (layer 25) and ResumeToast (layer 22) are runtime-
## instantiated here. LoadMenu ref is injected into TopBar so the Load button
## can call open() without a get_node("/root/…") call.
const STORYTELLER_BANNER_SCRIPT: Script = preload("res://scenes/ui/storyteller_banner.gd")
const STORYTELLER_MODAL_SCRIPT: Script = preload("res://scenes/ui/storyteller_modal.gd")
const LOAD_MENU_SCRIPT: Script = preload("res://scenes/ui/load_menu.gd")
const RESUME_TOAST_SCRIPT: Script = preload("res://scenes/ui/resume_toast.gd")
# Phase 17 — PawnDetailPanel (layer 18) and SettingsMenu (layer 26).
const PAWN_DETAIL_PANEL_SCRIPT: Script = preload("res://scenes/ui/pawn_detail_panel.gd")
const WORKBENCH_PANEL_SCRIPT: Script = preload("res://scenes/ui/workbench_panel.gd")
const SETTINGS_MENU_SCRIPT: Script = preload("res://scenes/ui/settings_menu.gd")
# Phase 17 (Agent B) — BuildDrawer bottom-sheet (layer 16).
const BUILD_DRAWER_SCRIPT: Script = preload("res://scenes/ui/build_drawer.gd")
# Phase 17 (Agent C) — WorkPriorityMatrix (layer 17) and AlertsLog (layer 19).
const WORK_PRIORITY_MATRIX_SCRIPT: Script = preload("res://scenes/ui/work_priority_matrix.gd")
const ALERTS_LOG_SCRIPT: Script = preload("res://scenes/ui/alerts_log.gd")
func _ready() -> void:
Audit.log("main", "Phase 3 — world + AI (Decision + JobRunner + RestProvider) online.")
# Autoloads — keep these asserts; cheap and catch a renamed-autoload
# regression instantly.
assert(World != null, "World autoload missing")
assert(Sim != null, "Sim autoload missing")
assert(GameState != null, "GameState autoload missing")
assert(EventBus != null, "EventBus autoload missing")
assert(Strings != null, "Strings autoload missing")
assert(SaveSystem != null, "SaveSystem autoload missing")
assert(Autosave != null, "Autosave autoload missing")
# Phase 15 — Storyteller UI layers. Runtime-instantiated so no .tscn edit is
# needed. CanvasLayer ensures correct draw order above World/TopBar regardless
# of parent-node position.
var banner := CanvasLayer.new()
banner.set_script(STORYTELLER_BANNER_SCRIPT)
banner.name = "StorytellerBanner"
add_child(banner)
var modal := CanvasLayer.new()
modal.set_script(STORYTELLER_MODAL_SCRIPT)
modal.name = "StorytellerModal"
add_child(modal)
Audit.log("main", "Phase 15 — StorytellerBanner + StorytellerModal mounted.")
# Phase 16 — Save/Load UI layers.
var resume_toast := CanvasLayer.new()
resume_toast.set_script(RESUME_TOAST_SCRIPT)
resume_toast.name = "ResumeToast"
add_child(resume_toast)
var load_menu := CanvasLayer.new()
load_menu.set_script(LOAD_MENU_SCRIPT)
load_menu.name = "LoadMenu"
add_child(load_menu)
# Inject LoadMenu ref into TopBar so the Load button can call open()
# without reaching into the scene tree by path.
var top_bar = get_node_or_null("TopBar")
if top_bar != null:
top_bar.load_menu = load_menu
Audit.log("main", "Phase 16 — LoadMenu + ResumeToast mounted.")
# Phase 17 — PawnDetailPanel (layer 18) and SettingsMenu (layer 26).
var pawn_detail_panel := CanvasLayer.new()
pawn_detail_panel.set_script(PAWN_DETAIL_PANEL_SCRIPT)
pawn_detail_panel.name = "PawnDetailPanel"
add_child(pawn_detail_panel)
# Bill-editor bottom-sheet for workbenches. Same shape as PawnDetailPanel
# (right-anchored 360 px, layer 18); mutually exclusive with it via Selection.
var workbench_panel := CanvasLayer.new()
workbench_panel.set_script(WORKBENCH_PANEL_SCRIPT)
workbench_panel.name = "WorkbenchPanel"
add_child(workbench_panel)
var settings_menu := CanvasLayer.new()
settings_menu.set_script(SETTINGS_MENU_SCRIPT)
settings_menu.name = "SettingsMenu"
add_child(settings_menu)
# Inject SettingsMenu ref into TopBar so the Settings button can call open()
# without reaching into the scene tree by path.
if top_bar != null:
top_bar.settings_menu = settings_menu
if top_bar.has_method("_add_settings_btn"):
top_bar._add_settings_btn()
Audit.log("main", "Phase 17 — PawnDetailPanel + WorkbenchPanel + SettingsMenu mounted.")
# Phase 17 (Agent B) — BuildDrawer bottom-sheet (layer 16).
# Must mount AFTER the World node is ready (World._ready seeds designation_ctl).
# Resolve the Designation controller from the World child so BuildDrawer can
# call set_active_tool() without a get_node("/root/…") call.
var build_drawer := CanvasLayer.new()
build_drawer.set_script(BUILD_DRAWER_SCRIPT)
build_drawer.name = "BuildDrawer"
add_child(build_drawer)
# Inject Designation ref (World child node) into the drawer.
var world_node = get_node_or_null("World")
if world_node != null:
var desig = world_node.get_node_or_null("DesignationCtl")
if desig != null:
build_drawer.designation = desig
else:
Audit.log("main", "BuildDrawer: DesignationCtl not found on World — tool paint disabled")
else:
Audit.log("main", "BuildDrawer: World node not found — tool paint disabled")
# Inject BuildDrawer ref into TopBar and add the Build button.
if top_bar != null:
top_bar.build_drawer = build_drawer
if top_bar.has_method("_add_build_btn"):
top_bar._add_build_btn()
Audit.log("main", "Phase 17 (Agent B) — BuildDrawer mounted.")
# Phase 17 (Agent C) — WorkPriorityMatrix (layer 17) and AlertsLog (layer 19).
var work_matrix := CanvasLayer.new()
work_matrix.set_script(WORK_PRIORITY_MATRIX_SCRIPT)
work_matrix.name = "WorkPriorityMatrix"
add_child(work_matrix)
var alerts_log := CanvasLayer.new()
alerts_log.set_script(ALERTS_LOG_SCRIPT)
alerts_log.name = "AlertsLog"
add_child(alerts_log)
# Inspect tooltip — hover any tile to see what's there (pawn / wall / bed
# / crate contents / tree progress / item stack / stockpile filter).
# Layer 50: above world, below modals (which sit at 100+).
var inspect := CanvasLayer.new()
inspect.set_script(preload("res://scenes/ui/inspect_tooltip.gd"))
inspect.name = "InspectTooltip"
add_child(inspect)
# Inject refs into TopBar and add Work + Log buttons to ButtonRow.
if top_bar != null:
top_bar.work_priority_matrix = work_matrix
top_bar.alerts_log_panel = alerts_log
if top_bar.has_method("_add_work_log_btns"):
top_bar._add_work_log_btns()
Audit.log("main", "Phase 17 (Agent C) — WorkPriorityMatrix + AlertsLog mounted.")