Phase 19 — onboarding: hint tour + Help modal + tooltip pass
Three-agent fan-out (gdscript-refactor x3) ships the chosen Phase 19 approach: contextual hints during first session + a Help reference, plus a sweep of hover tooltips for desktop discoverability. - HintSystem (autoload) + HintOverlay (layer 22 top-center banner): 7-step tour gated on player events — welcome (boot+2s), pawn select, build drawer open, stockpile painted, work matrix open, day_ended, tour_complete. Per-hint dismissals persist as Array[String] in GameState.settings['dismissed_hints']. Max-3 FIFO queue if hints chain. Reduce-motion path snaps in/out instead of tweening. Reset_tour() public API for the Help modal. - HelpModal (layer 20): 5-tab static reference (Controls / Verbs / Priorities / Storyteller / Tips). Opens via EventBus.help_requested, dimmed backdrop, X/Esc/backdrop-tap dismiss. SettingsMenu gains an 'Onboarding' section: Show-hints checkbox, Help button (emits help_requested), Reset hints button (calls HintSystem.reset_tour with has_method guard). Pre-existing 'W' keybind reference fixed to 'P'. - Tooltip pass: tooltip_text via Strings.t on every TopBar button (10 buttons incl. speed shortcuts), BuildDrawer FAB, and every tool button in BuildDrawer (21 tools). _add_tool_btn extended with optional tooltip param. ~34 new tooltip.* string keys. Contracts pre-written (Opus): EventBus.help_requested, hint_dismissed, ui_panel_opened signals; GameState show_hints + dismissed_hints defaults; BuildDrawer.open + WorkPriorityMatrix.open emit ui_panel_opened so HintSystem can subscribe via one signal. Also recorded [MED] known bug in memory.md: drag-paint with active paint tool is eaten by camera drag-pan. MCP runtime verified: welcome banner fires 2s after boot, dismiss queues build_drawer hint on next ui_panel_opened, dismissed_hints persisted as ['welcome'], HelpModal opens via help_requested with tab switching working (Controls → Tips verified visually). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bba1ce4334
commit
59ca6ba9c5
16 changed files with 844 additions and 22 deletions
|
|
@ -65,6 +65,11 @@ signal request_wolf_spawn(count: int) ## Phase 15 EventCatalog →
|
|||
signal wolf_spawned(wolves: Array) ## Emitted by WolfSpawner AFTER a raid wave has been instantiated; carries the spawned Wolf nodes. Audio uses this for raid-warning sting.
|
||||
signal day_ended(summary: Dictionary) ## Emitted by Clock at dusk→night boundary; carries the end-of-day recap dict.
|
||||
|
||||
# Phase 19 — Onboarding (hint system + help section).
|
||||
signal help_requested ## Settings "Help" button → opens HelpModal.
|
||||
signal hint_dismissed(hint_id: StringName) ## Emitted by HintOverlay when player closes a hint; HintSystem persists the dismissal.
|
||||
signal ui_panel_opened(panel_id: StringName) ## Emitted by UI panels (build_drawer, work_matrix, ...) when they become visible. HintSystem subscribes for tour gating.
|
||||
|
||||
# Phase 18 — Alert wiring (dangling signals surfaced to AlertsLog).
|
||||
signal no_stockpile_accepts(item_type: StringName, tile: Vector2i) ## Emitted by HaulingProvider when an item needs haul but no stockpile accepts it (rate-limited per item_type).
|
||||
signal bill_blocked(recipe_label: String, reason: StringName, focus_tile: Vector2i) ## Emitted by CraftingProvider when a bill cannot proceed. reason: missing_ingredient | skill_too_low | no_workbench.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ var settings: Dictionary = {
|
|||
"pause_on_pawn_down": true,
|
||||
"pause_on_modal": true,
|
||||
"show_day_summary": true,
|
||||
# Phase 19 — onboarding hint system.
|
||||
# show_hints: master toggle; dismissed_hints: per-hint dismissal log (Array[String]).
|
||||
"show_hints": true,
|
||||
"dismissed_hints": [] as Array,
|
||||
"audio_master": 1.0,
|
||||
"audio_music": 1.0,
|
||||
"audio_sfx": 1.0,
|
||||
|
|
|
|||
200
autoload/hint_system.gd
Normal file
200
autoload/hint_system.gd
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
extends Node
|
||||
## Phase 19 — Onboarding hint tour orchestrator.
|
||||
##
|
||||
## Reads GameState.settings["show_hints"] and ["dismissed_hints"] at _ready.
|
||||
## Connects signal-based triggers for the 7-step tour; queues hints when the
|
||||
## overlay is busy (max depth 3, oldest dropped first on overflow).
|
||||
##
|
||||
## The autoload name is HintSystem. HintOverlay registers itself here in its
|
||||
## own _ready so this singleton never calls find_child() on the tree.
|
||||
##
|
||||
## Tour steps in order:
|
||||
## welcome → pawn_select → build_drawer → stockpile_painted →
|
||||
## work_matrix → day_ended → tour_complete (auto-fired after day_ended)
|
||||
|
||||
# ── tour definition ──────────────────────────────────────────────────────────
|
||||
|
||||
## Each entry: { id, message_key } — trigger wiring is in _connect_triggers().
|
||||
const TOUR: Array = [
|
||||
{ "id": &"welcome", "msg": &"hint.welcome" },
|
||||
{ "id": &"pawn_select", "msg": &"hint.pawn_select" },
|
||||
{ "id": &"build_drawer", "msg": &"hint.build_drawer" },
|
||||
{ "id": &"stockpile_painted", "msg": &"hint.stockpile_painted" },
|
||||
{ "id": &"work_matrix", "msg": &"hint.work_matrix" },
|
||||
{ "id": &"day_ended", "msg": &"hint.day_ended" },
|
||||
{ "id": &"tour_complete", "msg": &"hint.tour_complete" },
|
||||
]
|
||||
|
||||
const MAX_QUEUE: int = 3
|
||||
|
||||
# ── state ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
var _overlay: Node = null # HintOverlay — registered via register_overlay()
|
||||
var _queue: Array = [] # Array[StringName] pending hint ids
|
||||
var _overlay_busy: bool = false # true while overlay is sliding in or showing
|
||||
var _enabled: bool = false # false if show_hints == false or all dismissed
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
var show: bool = bool(GameState.settings.get("show_hints", true))
|
||||
if not show:
|
||||
Audit.log("hint_system", "HintSystem disabled (show_hints=false)")
|
||||
return
|
||||
_enabled = true
|
||||
_connect_triggers()
|
||||
Audit.log("hint_system", "HintSystem ready")
|
||||
|
||||
|
||||
# ── public API ───────────────────────────────────────────────────────────────
|
||||
|
||||
## Called by HintOverlay._ready() to register itself with this singleton.
|
||||
func register_overlay(overlay: Node) -> void:
|
||||
_overlay = overlay
|
||||
Audit.log("hint_system", "HintOverlay registered")
|
||||
|
||||
|
||||
## Reset the full tour: clear dismissed list, re-enable, refire welcome.
|
||||
## Called by HelpModal's "Reset hints" button.
|
||||
func reset_tour() -> void:
|
||||
GameState.settings["dismissed_hints"] = [] as Array
|
||||
_queue.clear()
|
||||
_overlay_busy = false
|
||||
_enabled = true
|
||||
if not _are_triggers_connected():
|
||||
_connect_triggers()
|
||||
_try_show(&"welcome")
|
||||
Audit.log("hint_system", "tour reset")
|
||||
|
||||
|
||||
# ── trigger wiring ───────────────────────────────────────────────────────────
|
||||
|
||||
func _are_triggers_connected() -> bool:
|
||||
return EventBus.pawn_selected.is_connected(_on_pawn_selected)
|
||||
|
||||
|
||||
func _connect_triggers() -> void:
|
||||
# welcome — deferred 2s timer, only if not already dismissed.
|
||||
if not _is_dismissed(&"welcome"):
|
||||
get_tree().create_timer(2.0).timeout.connect(_on_welcome_timer)
|
||||
|
||||
# pawn_select
|
||||
if not EventBus.pawn_selected.is_connected(_on_pawn_selected):
|
||||
EventBus.pawn_selected.connect(_on_pawn_selected)
|
||||
|
||||
# build_drawer + work_matrix share ui_panel_opened
|
||||
if not EventBus.ui_panel_opened.is_connected(_on_ui_panel_opened):
|
||||
EventBus.ui_panel_opened.connect(_on_ui_panel_opened)
|
||||
|
||||
# stockpile_painted
|
||||
if not EventBus.designation_added.is_connected(_on_designation_added):
|
||||
EventBus.designation_added.connect(_on_designation_added)
|
||||
|
||||
# day_ended
|
||||
if not EventBus.day_ended.is_connected(_on_day_ended):
|
||||
EventBus.day_ended.connect(_on_day_ended)
|
||||
|
||||
# hint_dismissed — HintOverlay emits this; we persist and dequeue.
|
||||
if not EventBus.hint_dismissed.is_connected(_on_hint_dismissed):
|
||||
EventBus.hint_dismissed.connect(_on_hint_dismissed)
|
||||
|
||||
|
||||
# ── trigger handlers ─────────────────────────────────────────────────────────
|
||||
|
||||
func _on_welcome_timer() -> void:
|
||||
_try_show(&"welcome")
|
||||
|
||||
|
||||
func _on_pawn_selected(_pawn) -> void:
|
||||
_try_show(&"pawn_select")
|
||||
|
||||
|
||||
func _on_ui_panel_opened(panel_id: StringName) -> void:
|
||||
match panel_id:
|
||||
&"build_drawer":
|
||||
_try_show(&"build_drawer")
|
||||
&"work_matrix":
|
||||
_try_show(&"work_matrix")
|
||||
|
||||
|
||||
func _on_designation_added(_cell: Vector2i, tool: StringName) -> void:
|
||||
if tool == &"paint_stockpile":
|
||||
_try_show(&"stockpile_painted")
|
||||
|
||||
|
||||
func _on_day_ended(_summary: Dictionary) -> void:
|
||||
_try_show(&"day_ended")
|
||||
|
||||
|
||||
func _on_hint_dismissed(hint_id: StringName) -> void:
|
||||
# Persist dismissal (String, not StringName, for JSON-safe save).
|
||||
var dismissed: Array = GameState.settings["dismissed_hints"]
|
||||
var id_str: String = String(hint_id)
|
||||
if not dismissed.has(id_str):
|
||||
dismissed.append(id_str)
|
||||
GameState.settings["dismissed_hints"] = dismissed
|
||||
|
||||
_overlay_busy = false
|
||||
Audit.log("hint_system", "dismissed: %s total=%d" % [id_str, dismissed.size()])
|
||||
|
||||
# Auto-fire tour_complete after day_ended is dismissed.
|
||||
if hint_id == &"day_ended":
|
||||
_try_show(&"tour_complete")
|
||||
|
||||
# Drain the queue.
|
||||
_drain_queue()
|
||||
|
||||
|
||||
# ── show / queue logic ───────────────────────────────────────────────────────
|
||||
|
||||
func _try_show(hint_id: StringName) -> void:
|
||||
if not _enabled:
|
||||
return
|
||||
if _is_dismissed(hint_id):
|
||||
return
|
||||
if _overlay_busy:
|
||||
_enqueue(hint_id)
|
||||
return
|
||||
_show_now(hint_id)
|
||||
|
||||
|
||||
func _show_now(hint_id: StringName) -> void:
|
||||
if _overlay == null:
|
||||
# Overlay not yet mounted — queue for later.
|
||||
_enqueue(hint_id)
|
||||
return
|
||||
var msg_key: StringName = _msg_key_for(hint_id)
|
||||
_overlay_busy = true
|
||||
_overlay.show_hint(hint_id, Strings.t(msg_key))
|
||||
|
||||
|
||||
func _enqueue(hint_id: StringName) -> void:
|
||||
if _queue.has(hint_id):
|
||||
return # already pending
|
||||
if _queue.size() >= MAX_QUEUE:
|
||||
_queue.pop_front() # drop oldest to cap backlog
|
||||
_queue.append(hint_id)
|
||||
|
||||
|
||||
func _drain_queue() -> void:
|
||||
while _queue.size() > 0:
|
||||
var next: StringName = _queue[0]
|
||||
_queue.pop_front()
|
||||
if _is_dismissed(next):
|
||||
continue # skip already-dismissed entries
|
||||
_show_now(next)
|
||||
return # show one at a time; wait for next hint_dismissed
|
||||
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
func _is_dismissed(hint_id: StringName) -> bool:
|
||||
var dismissed: Array = GameState.settings.get("dismissed_hints", [])
|
||||
return dismissed.has(String(hint_id))
|
||||
|
||||
|
||||
func _msg_key_for(hint_id: StringName) -> StringName:
|
||||
for entry in TOUR:
|
||||
if entry["id"] == hint_id:
|
||||
return entry["msg"]
|
||||
return &"hint.welcome" # fallback
|
||||
1
autoload/hint_system.gd.uid
Normal file
1
autoload/hint_system.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://g7bkgx0vfiq1
|
||||
|
|
@ -261,6 +261,72 @@ const TABLE: Dictionary = {
|
|||
&"item.armor": "Armor",
|
||||
&"item.corpse": "Corpse",
|
||||
&"item.ash": "Ash",
|
||||
# Phase 19 — Onboarding hint tour.
|
||||
&"hint.welcome": "Welcome to your settlement. Drag to pan the camera, scroll or pinch to zoom.",
|
||||
&"hint.pawn_select": "Tap a pawn to open their detail panel — HP, hunger, mood, skills, and work priorities.",
|
||||
&"hint.build_drawer": "The Build drawer has four tabs: Designate orders, Build structures, paint Stockpiles, or Cancel a tool.",
|
||||
&"hint.stockpile_painted": "Tap your new stockpile to set its accepted items and priority. Pawns will haul matching items here.",
|
||||
&"hint.work_matrix": "Work priorities go 0 (off) to 4 (urgent). Pawns pick the highest-priority work they're capable of.",
|
||||
&"hint.day_ended": "Day ends at dusk. Sleep, mood, and weather all carry into the next day. Plan ahead.",
|
||||
&"hint.tour_complete": "Tour complete. Find more in Settings → Help anytime.",
|
||||
&"hint.got_it": "Got it",
|
||||
# Phase 19 — Help modal content.
|
||||
&"ui.help.title": "Help",
|
||||
&"ui.help.close": "X",
|
||||
&"ui.help.tab.controls": "Controls",
|
||||
&"ui.help.tab.verbs": "Verbs",
|
||||
&"ui.help.tab.priorities": "Priorities",
|
||||
&"ui.help.tab.storyteller": "Storyteller",
|
||||
&"ui.help.tab.tips": "Tips",
|
||||
&"help.controls.heading": "Camera & Selection",
|
||||
&"help.controls.body": "Drag with mouse or finger to pan the camera.\nScroll wheel or pinch to zoom.\n\nWASD or arrow keys also pan; + / - zoom; Home or C re-centers.\n\nTap a pawn, workbench, or stockpile to open its detail panel.\nRight-click or Escape deselects.\n\nB toggles the Build drawer.\nP opens the Work priority matrix.\nL opens the Alerts log.\n, opens Settings.",
|
||||
&"help.verbs.heading": "Player Verbs",
|
||||
&"help.verbs.body": "Designate - orders pawns to chop trees, mine rocks, dig graves, prevent roofing, or plant new trees.\n\nBuild - queues construction: walls, floors, doors, beds, torches, crates, workbenches, and quarries.\n\nStockpile - paints a zone where items get hauled. Each zone has a filter (which item types) and a priority.\n\nCancel - clears a tool selection or pending designation.",
|
||||
&"help.priorities.heading": "Work Priorities",
|
||||
&"help.priorities.body": "Each pawn has priorities (0-4) per work category. The Work matrix grid lets you tune them.\n\n0 = Off. Pawn won't do this work.\n1 = Background. Only if nothing else fits.\n2 = Low.\n3 = Normal (default).\n4 = Urgent. Drops other work to take this.\n\nNeeds (rest, eat, sleep) always run regardless - you can't starve a pawn by setting priorities.",
|
||||
&"help.storyteller.heading": "Storyteller Events",
|
||||
&"help.storyteller.body": "Each in-game day at 6 AM the Storyteller may roll an event. Categories include nudges (gentle hints), threats (wolves, raids), wanderers (recruit offers), seasonal beats, disease, resource booms, lore, and milestones.\n\nThreat events auto-pause the sim and show a modal with a choice. Other events show a banner that fades in a few seconds.\n\nThe Storyteller's tension rises and falls based on what happens to your colony - calmer when nothing has happened recently, escalating after kills, damage, or near-misses.",
|
||||
&"help.tips.heading": "Early-game Tips",
|
||||
&"help.tips.body": "Build at least one bed and one wall before nightfall - pawns sleep on the ground and grow tired, and wolves come at night.\n\nKeep beds indoors (under a roof). Rain and cold ruin sleep quality.\n\nPaint a stockpile early - items left on the ground decay (corpses, food) or just clutter the map.\n\nWatch tension. If it climbs, slow down and reinforce. The Storyteller is reactive - give it nothing to react to and it stays quiet.\n\nFood priority order: Meal > Bread > Vegetable / Grain / Strawberry > raw Wheat. Cook before eating raw.",
|
||||
# Phase 19 — Onboarding section in SettingsMenu.
|
||||
&"ui.settings.section.onboarding": "Onboarding",
|
||||
&"ui.settings.show_hints": "Show hints (first session)",
|
||||
&"ui.settings.help": "Help",
|
||||
&"ui.settings.reset_hints": "Reset hints",
|
||||
&"ui.settings.hints_reset": "Hint tour reset. Welcome banner will reappear shortly.",
|
||||
# Phase 19 — Hover tooltips for desktop discoverability.
|
||||
&"tooltip.pause": "Pause sim (Space)",
|
||||
&"tooltip.speed_normal": "Normal speed (1)",
|
||||
&"tooltip.speed_fast": "Fast speed (2)",
|
||||
&"tooltip.speed_ultra": "Ultra speed (3)",
|
||||
&"tooltip.save": "Save game",
|
||||
&"tooltip.load": "Load game",
|
||||
&"tooltip.settings": "Open Settings (,)",
|
||||
&"tooltip.build": "Open Build drawer (B)",
|
||||
&"tooltip.work": "Open Work priorities (P)",
|
||||
&"tooltip.log": "Open Alerts log (L)",
|
||||
&"tooltip.fab_build": "Toggle Build drawer (B)",
|
||||
&"tooltip.tool.chop": "Order pawns to chop selected trees for wood",
|
||||
&"tooltip.tool.mine": "Order pawns to mine selected rocks for stone / ore",
|
||||
&"tooltip.tool.dig_grave": "Mark a tile for grave digging",
|
||||
&"tooltip.tool.no_roof": "Prevent auto-roofing on selected tiles",
|
||||
&"tooltip.tool.plant_tree": "Plant a sapling (1 wood)",
|
||||
&"tooltip.tool.build_wall_stone": "Build stone wall",
|
||||
&"tooltip.tool.build_wall_wood": "Build wood wall",
|
||||
&"tooltip.tool.build_floor_wood": "Build wood floor",
|
||||
&"tooltip.tool.build_floor_stone": "Build stone floor",
|
||||
&"tooltip.tool.build_door": "Build a door",
|
||||
&"tooltip.tool.build_crate": "Build a storage crate",
|
||||
&"tooltip.tool.build_bed": "Build a bed",
|
||||
&"tooltip.tool.build_torch": "Build a torch",
|
||||
&"tooltip.tool.build_workbench_carpenter": "Build a carpenter's bench",
|
||||
&"tooltip.tool.build_workbench_smelter": "Build a smelter",
|
||||
&"tooltip.tool.build_workbench_millstone": "Build a millstone",
|
||||
&"tooltip.tool.build_workbench_hearth": "Build a hearth",
|
||||
&"tooltip.tool.build_workbench_cremation_pyre": "Build a cremation pyre",
|
||||
&"tooltip.tool.paint_quarry": "Paint a quarry on a stone outcrop",
|
||||
&"tooltip.tool.paint_stockpile": "Paint a stockpile zone",
|
||||
&"tooltip.tool.graveyard": "Paint a graveyard zone",
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue