feat(@projects/@magic-civilization): ✨ add tutorial reset and diplomacy hotkeys
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
986cf87f5f
commit
032602805b
9 changed files with 166 additions and 13 deletions
|
|
@ -347,5 +347,10 @@
|
|||
"hotkey_ingame_menu": "In-game menu / Close",
|
||||
"hotkey_help": "This cheat sheet",
|
||||
"hotkey_end_turn": "End turn",
|
||||
"hotkey_select_move": "Select / move unit"
|
||||
"hotkey_select_move": "Select / move unit",
|
||||
"options_reset_tutorial": "Reset Tutorial",
|
||||
"options_tutorial_reset_label": "Replay on next start",
|
||||
"options_tutorial_reset_done": "Tutorial will replay",
|
||||
"hotkey_encyclopedia_key": "F2",
|
||||
"hotkey_diplomacy": "Diplomacy"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ const BINDINGS: Array[Dictionary] = [
|
|||
{"group": "hotkey_group_overlays", "keys": "R", "label": "hotkey_overlay_water"},
|
||||
{"group": "hotkey_group_overlays", "keys": "E", "label": "hotkey_overlay_elevation"},
|
||||
{"group": "hotkey_group_overlays", "keys": "L", "label": "hotkey_overlay_ley_line"},
|
||||
{"group": "hotkey_group_menus", "keys": "F1", "label": "hotkey_encyclopedia"},
|
||||
{"group": "hotkey_group_menus", "keys": "F1", "label": "hotkey_help"},
|
||||
{"group": "hotkey_group_menus", "keys": "F2", "label": "hotkey_encyclopedia"},
|
||||
{"group": "hotkey_group_menus", "keys": "F8", "label": "hotkey_diplomacy"},
|
||||
{"group": "hotkey_group_menus", "keys": "F9", "label": "hotkey_stats"},
|
||||
{"group": "hotkey_group_menus", "keys": "?", "label": "hotkey_help"},
|
||||
{"group": "hotkey_group_menus", "keys": "Esc", "label": "hotkey_ingame_menu"},
|
||||
|
|
|
|||
|
|
@ -293,6 +293,14 @@ func _on_happiness_hover_exit() -> void:
|
|||
_breakdown_popup = null
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
## Encyclopedia now routes through the `ui_encyclopedia` InputMap action
|
||||
## (default: F2) so F1 stays free for `ui_help` / hotkey_sheet.
|
||||
if event.is_action_pressed("ui_encyclopedia"):
|
||||
get_viewport().set_input_as_handled()
|
||||
_on_encyclopedia_pressed()
|
||||
|
||||
|
||||
func _unhandled_key_input(event: InputEvent) -> void:
|
||||
if not event is InputEventKey:
|
||||
return
|
||||
|
|
@ -300,9 +308,6 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
|||
if key.pressed and not key.echo and key.keycode == KEY_F9:
|
||||
get_viewport().set_input_as_handled()
|
||||
_on_stats_pressed()
|
||||
elif key.pressed and not key.echo and key.keycode == KEY_F1:
|
||||
get_viewport().set_input_as_handled()
|
||||
_on_encyclopedia_pressed()
|
||||
elif key.pressed and not key.echo and key.keycode == KEY_F8:
|
||||
get_viewport().set_input_as_handled()
|
||||
_on_diplomacy_pressed()
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ var _defaults: RefCounted
|
|||
@onready var _autosave_next: Button = %AutosaveIntervalNextButton
|
||||
@onready var _autosave_label: Label = %AutosaveIntervalValueLabel
|
||||
@onready var _tooltips_check: CheckBox = %TooltipsCheck
|
||||
@onready var _reset_tutorial_button: Button = %ResetTutorialButton
|
||||
|
||||
# -- Game Defaults --
|
||||
@onready var _map_size_prev: Button = %DefaultMapSizePrevButton
|
||||
|
|
@ -110,6 +111,7 @@ func _connect_signals() -> void:
|
|||
_autosave_prev.pressed.connect(_on_autosave_interval_prev)
|
||||
_autosave_next.pressed.connect(_on_autosave_interval_next)
|
||||
_tooltips_check.toggled.connect(_on_tooltips_toggled)
|
||||
_reset_tutorial_button.pressed.connect(_on_reset_tutorial_pressed)
|
||||
|
||||
_map_size_prev.pressed.connect(_defaults.on_map_size_prev)
|
||||
_map_size_next.pressed.connect(_defaults.on_map_size_next)
|
||||
|
|
@ -422,6 +424,13 @@ func _on_tooltips_toggled(pressed: bool) -> void:
|
|||
SettingsManager.set_setting("gameplay", "show_tooltips", pressed)
|
||||
|
||||
|
||||
## Clears the `tutorial_completed` flag so the first-run overlay shows again
|
||||
## on the next world_map boot. Label flips to the "done" string as confirmation.
|
||||
func _on_reset_tutorial_pressed() -> void:
|
||||
SettingsManager.set_setting("gameplay", "tutorial_completed", false)
|
||||
_reset_tutorial_button.text = ThemeVocabulary.lookup("options_tutorial_reset_done")
|
||||
|
||||
|
||||
# -- Privacy --
|
||||
|
||||
func _refresh_privacy() -> void:
|
||||
|
|
|
|||
|
|
@ -748,6 +748,28 @@ size_flags_vertical = 4
|
|||
button_pressed = true
|
||||
text = "Enabled"
|
||||
|
||||
[node name="ResetTutorialRow" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ContentVBox"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(0, 40)
|
||||
|
||||
[node name="ResetTutorialLabel" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ContentVBox/ResetTutorialRow"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(220, 0)
|
||||
theme_override_font_sizes/font_size = 15
|
||||
text = "Reset Tutorial"
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Spacer" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ContentVBox/ResetTutorialRow"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ResetTutorialButton" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ContentVBox/ResetTutorialRow"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
custom_minimum_size = Vector2(160, 32)
|
||||
text = "Replay on next start"
|
||||
|
||||
; ===================== GAME DEFAULTS =====================
|
||||
|
||||
[node name="DefaultsSectionRow" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ContentVBox"]
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ const OverlayRendererScript: GDScript = preload(
|
|||
const FogRendererScript: GDScript = preload("res://engine/src/rendering/fog_renderer.gd")
|
||||
const CityScreenScene: PackedScene = preload("res://engine/scenes/city/city_screen.tscn")
|
||||
const ChroniclePanelScene: PackedScene = preload("res://engine/scenes/hud/chronicle_panel.tscn")
|
||||
const TutorialOverlayScene: PackedScene = preload("res://engine/scenes/hud/tutorial_overlay.tscn")
|
||||
const HotkeySheetScene: PackedScene = preload("res://engine/scenes/hud/hotkey_sheet.tscn")
|
||||
const TutorialOverlayScript: GDScript = preload("res://engine/scenes/hud/tutorial_overlay.gd")
|
||||
const WorldMapCombatScript: GDScript = preload("res://engine/scenes/world_map/world_map_combat.gd")
|
||||
const WorldMapCityActionsScript: GDScript = preload(
|
||||
"res://engine/scenes/world_map/world_map_city_actions.gd"
|
||||
|
|
@ -209,6 +212,7 @@ func _start_game() -> void:
|
|||
_sync_units()
|
||||
if not _arena_mode:
|
||||
_update_hud()
|
||||
_mount_hud_overlays()
|
||||
|
||||
var local_player_index: int = 0
|
||||
if player != null:
|
||||
|
|
@ -220,6 +224,15 @@ func _start_game() -> void:
|
|||
TurnManager.start_turn()
|
||||
|
||||
|
||||
## Persistent HUD overlays mounted once per world-map boot. The hotkey sheet
|
||||
## is always present so `ui_help` (F1 / ?) works in-game; the first-run
|
||||
## tutorial is gated on `TutorialOverlay.should_show_on_first_run()`.
|
||||
func _mount_hud_overlays() -> void:
|
||||
add_child(HotkeySheetScene.instantiate())
|
||||
if TutorialOverlayScript.should_show_on_first_run():
|
||||
add_child(TutorialOverlayScene.instantiate())
|
||||
|
||||
|
||||
func _update_fog(player: RefCounted, game_map: RefCounted) -> void:
|
||||
var arrays: Array = WorldMapVisionScript.build_fog_arrays(player, game_map)
|
||||
_hex_renderer.update_fog(arrays[0], arrays[1])
|
||||
|
|
|
|||
|
|
@ -32,26 +32,29 @@ func after_each() -> void:
|
|||
|
||||
|
||||
func _make_player(idx: int) -> RefCounted:
|
||||
var p: RefCounted = PlayerScript.new()
|
||||
p.player_index = idx
|
||||
# PlayerScript._init signature: (p_index, p_player_name, p_race_id).
|
||||
# `index` is the real field name (not player_index).
|
||||
var p: RefCounted = PlayerScript.new(idx, "Player%d" % idx, "dwarf")
|
||||
p.units = []
|
||||
p.cities = []
|
||||
return p
|
||||
|
||||
|
||||
func _make_warrior(owner_idx: int, pos: Vector2i) -> RefCounted:
|
||||
var u: RefCounted = UnitScript.new()
|
||||
u.type_id = "dwarf_warrior"
|
||||
u.owner_index = owner_idx
|
||||
u.position = pos
|
||||
# UnitScript._init signature: (p_unit_id, p_owner, p_position). `owner`
|
||||
# is the real field name (not owner_index). `is_alive` is a method on
|
||||
# the class, not a per-instance callable we can override.
|
||||
var u: RefCounted = UnitScript.new("warrior", owner_idx, pos)
|
||||
u.hp = 10
|
||||
u.is_alive = func() -> bool: return u.hp > 0
|
||||
u.max_hp = 10
|
||||
return u
|
||||
|
||||
|
||||
func _make_city(owner_idx: int, pos: Vector2i) -> RefCounted:
|
||||
# CityScript exposes both `owner` (write-through setter) and
|
||||
# `owner_index`. `owner` is the one the AI reads.
|
||||
var c: RefCounted = CityScript.new()
|
||||
c.owner_index = owner_idx
|
||||
c.owner = owner_idx
|
||||
c.position = pos
|
||||
return c
|
||||
|
||||
|
|
|
|||
89
src/game/engine/tests/unit/test_tutorial_hotkey_wiring.gd
Normal file
89
src/game/engine/tests/unit/test_tutorial_hotkey_wiring.gd
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
extends GutTest
|
||||
## p1-03 tutorial wiring + p2-03 hotkey_sheet wiring + F1 collision migration.
|
||||
|
||||
const TutorialOverlayScript: GDScript = preload("res://engine/scenes/hud/tutorial_overlay.gd")
|
||||
const HotkeySheetScene: PackedScene = preload("res://engine/scenes/hud/hotkey_sheet.tscn")
|
||||
|
||||
|
||||
func before_all() -> void:
|
||||
DataLoader.load_theme("age-of-dwarves")
|
||||
ThemeVocabulary.load_vocabulary("age-of-dwarves")
|
||||
|
||||
|
||||
# -- p1-03: tutorial reset + first-run path --
|
||||
|
||||
|
||||
func test_tutorial_reset_flag_flips_should_show() -> void:
|
||||
SettingsManager.set_setting("gameplay", "tutorial_completed", true)
|
||||
assert_false(TutorialOverlayScript.should_show_on_first_run(),
|
||||
"seen tutorial must NOT reshow")
|
||||
SettingsManager.set_setting("gameplay", "tutorial_completed", false)
|
||||
assert_true(TutorialOverlayScript.should_show_on_first_run(),
|
||||
"after reset, tutorial should show again")
|
||||
|
||||
|
||||
func test_world_map_mount_hotkey_sheet_const_exists() -> void:
|
||||
## world_map.gd preloads HotkeySheetScene + TutorialOverlayScene and calls
|
||||
## `_mount_hud_overlays()` in _start_game — verify the scene file exists
|
||||
## at the path the preload resolves.
|
||||
assert_true(ResourceLoader.exists("res://engine/scenes/hud/hotkey_sheet.tscn"),
|
||||
"hotkey_sheet.tscn must exist at the path world_map.gd preloads")
|
||||
assert_true(ResourceLoader.exists("res://engine/scenes/hud/tutorial_overlay.tscn"),
|
||||
"tutorial_overlay.tscn must exist at the path world_map.gd preloads")
|
||||
|
||||
|
||||
# -- p2-03: ui_encyclopedia InputMap migration --
|
||||
|
||||
|
||||
func test_ui_encyclopedia_action_exists() -> void:
|
||||
assert_true(InputMap.has_action("ui_encyclopedia"),
|
||||
"ui_encyclopedia InputMap action must exist (F1 migration)")
|
||||
|
||||
|
||||
func test_ui_encyclopedia_bound_to_f2() -> void:
|
||||
var events: Array = InputMap.action_get_events("ui_encyclopedia")
|
||||
var found_f2: bool = false
|
||||
for event in events:
|
||||
if event is InputEventKey:
|
||||
var key: InputEventKey = event as InputEventKey
|
||||
if key.keycode == KEY_F2:
|
||||
found_f2 = true
|
||||
break
|
||||
assert_true(found_f2, "ui_encyclopedia must be bound to F2")
|
||||
|
||||
|
||||
func test_ui_help_still_bound() -> void:
|
||||
assert_true(InputMap.has_action("ui_help"),
|
||||
"ui_help must still exist (F1 / ?)")
|
||||
|
||||
|
||||
# -- p2-03: hotkey_sheet lists new bindings --
|
||||
|
||||
|
||||
func test_hotkey_sheet_lists_help_and_encyclopedia_split() -> void:
|
||||
var sheet: CanvasLayer = HotkeySheetScene.instantiate()
|
||||
add_child_autofree(sheet)
|
||||
await wait_frames(1)
|
||||
var bindings: Array = sheet.BINDINGS
|
||||
var has_help_f1: bool = false
|
||||
var has_encyclopedia_f2: bool = false
|
||||
for entry: Dictionary in bindings:
|
||||
if entry.get("keys") == "F1" and entry.get("label") == "hotkey_help":
|
||||
has_help_f1 = true
|
||||
if entry.get("keys") == "F2" and entry.get("label") == "hotkey_encyclopedia":
|
||||
has_encyclopedia_f2 = true
|
||||
assert_true(has_help_f1, "BINDINGS must include F1 → hotkey_help")
|
||||
assert_true(has_encyclopedia_f2,
|
||||
"BINDINGS must include F2 → hotkey_encyclopedia (migrated)")
|
||||
|
||||
|
||||
# -- options reset checkbox --
|
||||
|
||||
|
||||
func test_options_reset_tutorial_vocab_exists() -> void:
|
||||
assert_ne(ThemeVocabulary.lookup("options_reset_tutorial"),
|
||||
"options_reset_tutorial",
|
||||
"options_reset_tutorial vocab key must resolve")
|
||||
assert_ne(ThemeVocabulary.lookup("options_tutorial_reset_done"),
|
||||
"options_tutorial_reset_done",
|
||||
"options_tutorial_reset_done vocab key must resolve")
|
||||
|
|
@ -43,6 +43,11 @@ ui_help={
|
|||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":47,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
ui_encyclopedia={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194333,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[rendering]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue