Three-agent fan-out (gdscript-refactor x3) closing deferred polish: - Stockpile chip filter UI: new StockpilePanel (layer 18, right-anchored, mirrors WorkbenchPanel). 5-priority segmented control + 21-chip 4-col filter grid using Item.ALL_TYPES; wildcard (empty accepted_types) shows all chips checked with 'All' hint, first explicit pick switches to explicit-list mode. Selection chain extended to pawn → workbench → stockpile with mutual exclusion. 12 ui.stockpile.* + 13 item.* keys. - DaySummaryCard: layer-19 modal auto-opens at dusk→night via day_ended, auto-pauses sim, shows day+season header, weather row, stats grid with green/yellow/red tension bar, Continue dismiss + backdrop tap. Settings 'Show end-of-day summary' toggle persists via GameState. - Atmospheric audio: rain ambient loop (Cozy Melodies Pack 6) on weather_changed rain/storm with 0.5s fade-out on clear; thunder sting (Magic and Spells 6) on rain→storm transition; raid warning sting (Sword Pack 1, 'blades drawn') on EventBus.wolf_spawned. All on SFX bus — inherits existing slider + suspend mute. Contracts pre-written before fan-out: EventBus.stockpile_selected / stockpile_deselected / wolf_spawned signals; WolfSpawner._trigger_raid + _on_request_wolf_spawn now emit wolf_spawned with the spawned array. MCP runtime verified: StockpilePanel opens with 21 chips, DaySummaryCard renders weather row + tension bar + auto-pause, rain_player.playing=true on weather_changed(rain), all three new SFX keys in Audio.SFX_FILES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
218 lines
9.3 KiB
GDScript
218 lines
9.3 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 STOCKPILE_PANEL_SCRIPT: Script = preload("res://scenes/ui/stockpile_panel.gd")
|
|
const MEDIEVAL_THEME_SCRIPT: Script = preload("res://scenes/ui/medieval_theme.gd")
|
|
|
|
# Built once in _ready and re-applied to any CanvasLayer-rooted Control because
|
|
# CanvasLayer doesn't propagate the root-Window theme cascade.
|
|
var _app_theme: Theme = null
|
|
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")
|
|
# Phase 17 — DaySummaryCard end-of-day recap modal (layer 19).
|
|
const DAY_SUMMARY_CARD_SCRIPT: Script = preload("res://scenes/ui/day_summary_card.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")
|
|
|
|
# Medieval-warm Theme — assigned on the root Window first. Cascade alone
|
|
# doesn't reach Controls inside CanvasLayers (CanvasLayer has no theme
|
|
# property), so we also walk the tree post-mount and apply to every Control
|
|
# encountered. (2026-05-16 polish pass.)
|
|
_app_theme = MEDIEVAL_THEME_SCRIPT.build()
|
|
get_tree().root.theme = _app_theme
|
|
|
|
# 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)
|
|
|
|
# Stockpile filter + priority editor. Right-anchored 360 px, layer 18;
|
|
# mutually exclusive with PawnDetailPanel and WorkbenchPanel via Selection.
|
|
var stockpile_panel := CanvasLayer.new()
|
|
stockpile_panel.set_script(STOCKPILE_PANEL_SCRIPT)
|
|
stockpile_panel.name = "StockpilePanel"
|
|
add_child(stockpile_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.")
|
|
|
|
# Phase 17 — DaySummaryCard (layer 19) — auto-opens on day_ended signal.
|
|
var day_summary_card := CanvasLayer.new()
|
|
day_summary_card.set_script(DAY_SUMMARY_CARD_SCRIPT)
|
|
day_summary_card.name = "DaySummaryCard"
|
|
add_child(day_summary_card)
|
|
|
|
Audit.log("main", "Phase 17 — DaySummaryCard mounted.")
|
|
|
|
# Apply the medieval theme to every Control under each CanvasLayer.
|
|
# CanvasLayers interrupt the root-Window theme cascade so we have to seed
|
|
# each one explicitly. Defer one frame so panels that build their UI in
|
|
# _ready (PawnDetailPanel, WorkbenchPanel, BuildDrawer) finish first.
|
|
call_deferred("_apply_theme_to_canvas_layers")
|
|
|
|
|
|
## Walks the scene tree and assigns _app_theme to every Control directly under
|
|
## a CanvasLayer (the topmost Control in each layer's branch). From there the
|
|
## standard Control-to-Control cascade carries the theme to all descendants.
|
|
## Also catches popups and modals that mount later via child_entered_tree.
|
|
func _apply_theme_to_canvas_layers() -> void:
|
|
for c in get_children():
|
|
if c is CanvasLayer:
|
|
_seed_layer_theme(c)
|
|
# Watch for late additions (popup menus, modals).
|
|
if not c.child_entered_tree.is_connected(_on_layer_child_added):
|
|
c.child_entered_tree.connect(_on_layer_child_added)
|
|
|
|
|
|
func _seed_layer_theme(layer: CanvasLayer) -> void:
|
|
for c in layer.get_children():
|
|
if c is Control and c.theme == null:
|
|
c.theme = _app_theme
|
|
|
|
|
|
func _on_layer_child_added(node: Node) -> void:
|
|
if node is Control and node.theme == null:
|
|
node.theme = _app_theme
|