Wire the first-shift tutorial into gameplay
Add the non-blocking prompt card, safe compliant visitor, action-driven advancement through the real wheel/inspection/warning/radio panels, persisted completion/skip, a Settings replay action and localized tutorial strings. Scripted tests/captures bypass the tutorial unless --tutorial is passed. Add a windowed smoke covering the full flow. Headless 237/237, Python 94/94, tutorial smoke passes.
This commit is contained in:
1 parent
76eb5e3a79
commit
86332cd401
12 files changed
+392
-15
No files matched your search
@@ -10,6 +10,7 @@ const InputBindings := preload("res://src/core/input_bindings.gd")
|
||||
const UiArt := preload("res://src/game/ui/art.gd")
|
||||
|
||||
signal applied(settings: Dictionary)
|
||||
signal replay_tutorial_requested()
|
||||
signal closed()
|
||||
|
||||
var _root: Control = null
|
||||
@@ -86,6 +87,15 @@ func _build() -> void:
|
||||
_head_bob.toggled.connect(_on_head_bob_toggled)
|
||||
column.add_child(_head_bob)
|
||||
|
||||
var replay_row := HBoxContainer.new()
|
||||
replay_row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
column.add_child(replay_row)
|
||||
var replay := Button.new()
|
||||
replay.name = "ReplayTutorialButton"
|
||||
replay.text = "Replay tutorial"
|
||||
replay.pressed.connect(func(): replay_tutorial_requested.emit())
|
||||
replay_row.add_child(replay)
|
||||
|
||||
var controls_title := Label.new()
|
||||
controls_title.text = "Controls (click a binding, then press a key or pad button)"
|
||||
controls_title.add_theme_font_size_override("font_size", 13)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
class_name TutorialOverlay
|
||||
extends CanvasLayer
|
||||
|
||||
const Strings := preload("res://src/core/strings.gd")
|
||||
## Non-blocking first-shift tutorial card. It never takes mouse input; F1 is
|
||||
## handled by GameManager so the prompt can stay visible while the player moves.
|
||||
|
||||
var _panel: PanelContainer = null
|
||||
var _prompt: Label = null
|
||||
var _progress: Label = null
|
||||
|
||||
func _init() -> void:
|
||||
layer = 14
|
||||
_build()
|
||||
hide()
|
||||
|
||||
func _build() -> void:
|
||||
var root := Control.new()
|
||||
root.name = "TutorialRoot"
|
||||
root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
root.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(root)
|
||||
|
||||
_panel = PanelContainer.new()
|
||||
_panel.name = "TutorialCard"
|
||||
_panel.set_anchor(SIDE_LEFT, 0.5)
|
||||
_panel.set_anchor(SIDE_RIGHT, 0.5)
|
||||
_panel.set_anchor(SIDE_TOP, 0.08)
|
||||
_panel.set_anchor(SIDE_BOTTOM, 0.08)
|
||||
_panel.offset_left = -330
|
||||
_panel.offset_right = 330
|
||||
_panel.offset_top = 0
|
||||
_panel.offset_bottom = 112
|
||||
_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.035, 0.045, 0.06, 0.92)
|
||||
style.border_color = Color(0.62, 0.69, 0.8, 0.55)
|
||||
style.border_width_left = 1
|
||||
style.border_width_top = 1
|
||||
style.border_width_right = 1
|
||||
style.border_width_bottom = 1
|
||||
style.corner_radius_top_left = 10
|
||||
style.corner_radius_top_right = 10
|
||||
style.corner_radius_bottom_left = 10
|
||||
style.corner_radius_bottom_right = 10
|
||||
style.content_margin_left = 18
|
||||
style.content_margin_right = 18
|
||||
style.content_margin_top = 10
|
||||
style.content_margin_bottom = 10
|
||||
_panel.add_theme_stylebox_override("panel", style)
|
||||
root.add_child(_panel)
|
||||
|
||||
var column := VBoxContainer.new()
|
||||
column.add_theme_constant_override("separation", 3)
|
||||
_panel.add_child(column)
|
||||
|
||||
var header := HBoxContainer.new()
|
||||
column.add_child(header)
|
||||
var title := Label.new()
|
||||
title.text = Strings.lookup("tutorial.header")
|
||||
title.add_theme_font_size_override("font_size", 12)
|
||||
title.modulate = Color(0.6, 0.8, 1.0)
|
||||
title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
header.add_child(title)
|
||||
_progress = Label.new()
|
||||
_progress.add_theme_font_size_override("font_size", 12)
|
||||
_progress.modulate = Color(0.65, 0.7, 0.78)
|
||||
header.add_child(_progress)
|
||||
|
||||
_prompt = Label.new()
|
||||
_prompt.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_prompt.add_theme_font_size_override("font_size", 15)
|
||||
column.add_child(_prompt)
|
||||
|
||||
var hint := Label.new()
|
||||
hint.text = Strings.lookup("tutorial.skip_hint")
|
||||
hint.add_theme_font_size_override("font_size", 11)
|
||||
hint.modulate = Color(0.55, 0.6, 0.68)
|
||||
column.add_child(hint)
|
||||
|
||||
func show_beat(text: String, progress: Vector2i) -> void:
|
||||
_prompt.text = text
|
||||
_progress.text = "%d/%d" % [progress.x, progress.y]
|
||||
visible = true
|
||||
|
||||
func close() -> void:
|
||||
visible = false
|
||||
Reference in new issue
Block a user