refactor(client): remove archetype selection, fix Randomise→Randomize
- Remove character_select.tscn and character_select.gd entirely (Smuggler/Detective archetypes removed per v0.2 pivot) - New Game goes straight to character creation - Rename Randomise→Randomize throughout (US English convention) - Add *.import and *.uid to client gitignore (import artifacts) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+3
-1
@@ -9,10 +9,12 @@ tooling/line-previewer/target/
|
||||
tooling/test-client/target/
|
||||
content-ron/
|
||||
|
||||
# Godot client (further ignores managed by client team)
|
||||
# Godot client
|
||||
client/.godot/
|
||||
client/export/
|
||||
client/reports/
|
||||
client/**/*.import
|
||||
client/**/*.uid
|
||||
|
||||
# Renderer (separate Godot project for sprite pipeline)
|
||||
renderer/.godot/
|
||||
|
||||
@@ -221,7 +221,7 @@ character_select:
|
||||
|
||||
character_creation:
|
||||
btn_back: "[ESC] Back"
|
||||
btn_randomise: "[R] Randomise"
|
||||
btn_randomize: "[R] Randomize"
|
||||
btn_start: "[ENTER] Start"
|
||||
btn_rotate_left: "[Q] ◄"
|
||||
btn_rotate_right: "► [E]"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
; #705: Character creation screen — 3D preview + 5-tab customisation panel.
|
||||
; SubViewport renders CharacterVisual live. Tab panel: Body/Head/Hair/Clothing/Accessories.
|
||||
; Keyboard: Q/E rotate, Tab/Shift+Tab cycle tabs, R randomise, Enter start, Esc back.
|
||||
; Keyboard: Q/E rotate, Tab/Shift+Tab cycle tabs, R randomize, Enter start, Esc back.
|
||||
; D-146 (tile-scale preview), D-158 (frontal -5° camera default), D-155 (cardinal rotation).
|
||||
|
||||
[sub_resource type="Environment" id="Environment_1"]
|
||||
@@ -198,10 +198,10 @@ theme_override_colors/font_color = Color(0.53, 0.56, 0.63, 1.0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="RandomiseBtn" type="Button" parent="Footer"]
|
||||
[node name="RandomizeBtn" type="Button" parent="Footer"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(140, 36)
|
||||
text = "[R] Randomise"
|
||||
text = "[R] Randomize"
|
||||
theme_override_font_sizes/font_size = 13
|
||||
theme_override_colors/font_color = Color(0.784, 0.816, 0.878, 1.0)
|
||||
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://char_select_scene_sr"]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/character_select.gd" id="1_charselect"]
|
||||
|
||||
; #588: Character archetype select — two-card overlay between New Game and main.tscn.
|
||||
; Keyboard: left/right to pick, Enter to confirm, ESC to cancel (no save dir created).
|
||||
|
||||
[node name="CharacterSelect" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1_charselect")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.04, 0.04, 0.07, 0.97)
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="."]
|
||||
layout_mode = 1
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -200.0
|
||||
offset_top = 100.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 126.0
|
||||
grow_horizontal = 2
|
||||
text = "Choose your perspective."
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 16
|
||||
theme_override_colors/font_color = Color(0.784, 0.816, 0.878, 1.0)
|
||||
|
||||
[node name="Cards" type="HBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -316.0
|
||||
offset_top = -110.0
|
||||
offset_right = 316.0
|
||||
offset_bottom = 140.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 24
|
||||
alignment = 1
|
||||
|
||||
; --- Smuggler card ---
|
||||
|
||||
[node name="CardSmugglerWrapper" type="Control" parent="Cards"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(280, 240)
|
||||
mouse_filter = 0
|
||||
|
||||
[node name="CardBorder" type="ColorRect" parent="Cards/CardSmugglerWrapper"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.18, 0.22, 0.28, 1.0)
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="CardInner" type="ColorRect" parent="Cards/CardSmugglerWrapper"]
|
||||
layout_mode = 1
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 2.0
|
||||
offset_top = 2.0
|
||||
offset_right = -2.0
|
||||
offset_bottom = -2.0
|
||||
color = Color(0.07, 0.07, 0.10, 1.0)
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Cards/CardSmugglerWrapper/CardInner"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 20.0
|
||||
offset_top = 20.0
|
||||
offset_right = -20.0
|
||||
offset_bottom = -20.0
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="NameLabel" type="Label" parent="Cards/CardSmugglerWrapper/CardInner/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Smuggler"
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_colors/font_color = Color(0.906, 0.773, 0.278, 1.0)
|
||||
|
||||
[node name="RoleLabel" type="Label" parent="Cards/CardSmugglerWrapper/CardInner/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Freight logistics worker — Sova Transit"
|
||||
autowrap_mode = 2
|
||||
theme_override_font_sizes/font_size = 13
|
||||
theme_override_colors/font_color = Color(0.533, 0.565, 0.627, 1.0)
|
||||
|
||||
[node name="Divider" type="Control" parent="Cards/CardSmugglerWrapper/CardInner/VBox"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(0, 12)
|
||||
|
||||
[node name="ToneLabel" type="Label" parent="Cards/CardSmugglerWrapper/CardInner/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Insider access. Social camouflage. The ring is your daily life."
|
||||
autowrap_mode = 2
|
||||
theme_override_font_sizes/font_size = 12
|
||||
theme_override_colors/font_color = Color(0.416, 0.447, 0.510, 1.0)
|
||||
|
||||
; --- Detective card ---
|
||||
|
||||
[node name="CardDetectiveWrapper" type="Control" parent="Cards"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(280, 240)
|
||||
mouse_filter = 0
|
||||
|
||||
[node name="CardBorder" type="ColorRect" parent="Cards/CardDetectiveWrapper"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
color = Color(0.18, 0.22, 0.28, 1.0)
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="CardInner" type="ColorRect" parent="Cards/CardDetectiveWrapper"]
|
||||
layout_mode = 1
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 2.0
|
||||
offset_top = 2.0
|
||||
offset_right = -2.0
|
||||
offset_bottom = -2.0
|
||||
color = Color(0.07, 0.07, 0.10, 1.0)
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Cards/CardDetectiveWrapper/CardInner"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 20.0
|
||||
offset_top = 20.0
|
||||
offset_right = -20.0
|
||||
offset_bottom = -20.0
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="NameLabel" type="Label" parent="Cards/CardDetectiveWrapper/CardInner/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Detective"
|
||||
theme_override_font_sizes/font_size = 26
|
||||
theme_override_colors/font_color = Color(0.906, 0.773, 0.278, 1.0)
|
||||
|
||||
[node name="RoleLabel" type="Label" parent="Cards/CardDetectiveWrapper/CardInner/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Commission investigator — External assignment"
|
||||
autowrap_mode = 2
|
||||
theme_override_font_sizes/font_size = 13
|
||||
theme_override_colors/font_color = Color(0.533, 0.565, 0.627, 1.0)
|
||||
|
||||
[node name="Divider" type="Control" parent="Cards/CardDetectiveWrapper/CardInner/VBox"]
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(0, 12)
|
||||
|
||||
[node name="ToneLabel" type="Label" parent="Cards/CardDetectiveWrapper/CardInner/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Institutional authority. Analytical lattice. You were sent here."
|
||||
autowrap_mode = 2
|
||||
theme_override_font_sizes/font_size = 12
|
||||
theme_override_colors/font_color = Color(0.416, 0.447, 0.510, 1.0)
|
||||
|
||||
[node name="ConfirmBtn" type="Button" parent="."]
|
||||
layout_mode = 1
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -60.0
|
||||
offset_top = -80.0
|
||||
offset_right = 60.0
|
||||
offset_bottom = -50.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
text = "Begin"
|
||||
theme_override_font_sizes/font_size = 15
|
||||
theme_override_colors/font_color = Color(0.906, 0.773, 0.278, 1.0)
|
||||
@@ -2,7 +2,7 @@
|
||||
##
|
||||
## Validates the CharacterCreation UI: scene instantiation, tab structure,
|
||||
## signal emission (creation_confirmed / creation_cancelled), keyboard nav
|
||||
## callbacks, randomise, color derivation helpers, and game flow wiring.
|
||||
## callbacks, randomize, color derivation helpers, and game flow wiring.
|
||||
##
|
||||
## These are UI-only tests (no compositor/server required).
|
||||
## CharacterVisual asset paths fall back gracefully when GLBs are absent.
|
||||
@@ -115,10 +115,10 @@ func test_footer_buttons_exist() -> void:
|
||||
if _scene == null:
|
||||
return
|
||||
var back: Button = _scene.get_node_or_null("Footer/BackBtn")
|
||||
var rand_btn: Button = _scene.get_node_or_null("Footer/RandomiseBtn")
|
||||
var rand_btn: Button = _scene.get_node_or_null("Footer/RandomizeBtn")
|
||||
var start: Button = _scene.get_node_or_null("Footer/StartBtn")
|
||||
assert_bool(back != null).override_failure_message("BackBtn must exist in Footer").is_true()
|
||||
assert_bool(rand_btn != null).override_failure_message("RandomiseBtn must exist in Footer").is_true()
|
||||
assert_bool(rand_btn != null).override_failure_message("RandomizeBtn must exist in Footer").is_true()
|
||||
assert_bool(start != null).override_failure_message("StartBtn must exist in Footer").is_true()
|
||||
|
||||
|
||||
@@ -367,33 +367,33 @@ func test_derive_accent_shifts_hue() -> void:
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Randomise (Task #8)
|
||||
# Randomize (Task #8)
|
||||
# =============================================================================
|
||||
|
||||
func test_randomise_changes_body_type() -> void:
|
||||
func test_randomize_changes_body_type() -> void:
|
||||
if _scene == null:
|
||||
return
|
||||
# Run randomise multiple times to ensure it doesn't always pick the default
|
||||
# Run randomize multiple times to ensure it doesn't always pick the default
|
||||
var changed := false
|
||||
var original := _scene._descriptor.body_type
|
||||
for _i in 20:
|
||||
_scene._on_randomise()
|
||||
_scene._on_randomize()
|
||||
if _scene._descriptor.body_type != original:
|
||||
changed = true
|
||||
break
|
||||
assert_bool(changed).override_failure_message(
|
||||
"Randomise must be able to change body type from default"
|
||||
"Randomize must be able to change body type from default"
|
||||
).is_true()
|
||||
|
||||
|
||||
func test_randomise_produces_valid_skin_tone() -> void:
|
||||
func test_randomize_produces_valid_skin_tone() -> void:
|
||||
if _scene == null:
|
||||
return
|
||||
for _i in 10:
|
||||
_scene._on_randomise()
|
||||
_scene._on_randomize()
|
||||
var tone := _scene._descriptor.skin_tone
|
||||
assert_bool(tone >= 0 and tone <= 8).override_failure_message(
|
||||
"Randomise must produce skin_tone in range 0–8"
|
||||
"Randomize must produce skin_tone in range 0–8"
|
||||
).is_true()
|
||||
|
||||
|
||||
@@ -471,7 +471,7 @@ func test_q_key_rotates_left() -> void:
|
||||
).is_not_equal(initial)
|
||||
|
||||
|
||||
func test_r_key_triggers_randomise() -> void:
|
||||
func test_r_key_triggers_randomize() -> void:
|
||||
if _scene == null:
|
||||
return
|
||||
# Seed to a known state so change is detectable
|
||||
@@ -490,7 +490,7 @@ func test_r_key_triggers_randomise() -> void:
|
||||
changed = true
|
||||
break
|
||||
assert_bool(changed).override_failure_message(
|
||||
"R key must trigger randomise and change body_type or skin_tone"
|
||||
"R key must trigger randomize and change body_type or skin_tone"
|
||||
).is_true()
|
||||
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ const RECENT_SLOTS := 9
|
||||
@onready var _cam_angle_btn: Button = $Layout/PreviewPanel/PreviewOverlay/CamAngleBtn
|
||||
@onready var _tab_container: TabContainer = $Layout/TabPanel/TabContainer
|
||||
@onready var _footer_back: Button = $Footer/BackBtn
|
||||
@onready var _footer_randomise: Button = $Footer/RandomiseBtn
|
||||
@onready var _footer_randomize: Button = $Footer/RandomizeBtn
|
||||
@onready var _footer_start: Button = $Footer/StartBtn
|
||||
@onready var _modal_root: Control = $ColorPickerModal
|
||||
|
||||
@@ -179,7 +179,7 @@ var _modal_recent_btns: Array[Button] = []
|
||||
var _clothing_dock_container: Control = null
|
||||
var _accessory_dock_container: Control = null
|
||||
|
||||
# --- Cached asset scan results (populated once during tab build, reused by randomise) ---
|
||||
# --- Cached asset scan results (populated once during tab build, reused by randomize) ---
|
||||
var _cached_hair_ids: Array = []
|
||||
var _cached_head_ids: Array = []
|
||||
|
||||
@@ -212,11 +212,11 @@ func _ready() -> void:
|
||||
_rotate_right_btn.pressed.connect(_on_rotate_right)
|
||||
_cam_angle_btn.pressed.connect(_on_cam_angle_toggle)
|
||||
_footer_back.pressed.connect(_on_back)
|
||||
_footer_randomise.pressed.connect(_on_randomise)
|
||||
_footer_randomize.pressed.connect(_on_randomize)
|
||||
_footer_start.pressed.connect(_on_start)
|
||||
|
||||
_footer_back.text = UIStrings.get_text("character_creation.btn_back")
|
||||
_footer_randomise.text = UIStrings.get_text("character_creation.btn_randomise")
|
||||
_footer_randomize.text = UIStrings.get_text("character_creation.btn_randomize")
|
||||
_footer_start.text = UIStrings.get_text("character_creation.btn_start")
|
||||
_rotate_left_btn.text = UIStrings.get_text("character_creation.btn_rotate_left")
|
||||
_rotate_right_btn.text = UIStrings.get_text("character_creation.btn_rotate_right")
|
||||
@@ -1117,7 +1117,7 @@ func _input(event: InputEvent) -> void:
|
||||
_on_rotate_right()
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_R:
|
||||
_on_randomise()
|
||||
_on_randomize()
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_ENTER, KEY_KP_ENTER:
|
||||
_on_start()
|
||||
@@ -1147,10 +1147,10 @@ func _on_start() -> void:
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Randomise
|
||||
# Randomize
|
||||
# =============================================================================
|
||||
|
||||
func _on_randomise() -> void:
|
||||
func _on_randomize() -> void:
|
||||
var all_types := CharacterVisualDescriptor.BodyType.values()
|
||||
_descriptor.body_type = all_types[randi() % all_types.size()]
|
||||
_descriptor.skin_tone = randi() % CharacterVisual.SKIN_TONES.size()
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
extends Control
|
||||
## #588: Character archetype select panel — shown after "New Game", before loading main.tscn.
|
||||
## Two cards (Smuggler / Detective). Keyboard (left/right/enter/esc) and mouse.
|
||||
## Emits archetype_confirmed(archetype: String) or archetype_cancelled on ESC.
|
||||
##
|
||||
## ESC cancels without creating a save directory — new_game() fires AFTER confirmation.
|
||||
|
||||
signal archetype_confirmed(archetype: String)
|
||||
signal archetype_cancelled
|
||||
|
||||
const CARD_BG_NORMAL := Color(0.07, 0.07, 0.10, 1.0)
|
||||
const CARD_BG_SELECTED := Color(0.10, 0.12, 0.18, 1.0)
|
||||
const CARD_BORDER_NORMAL := Color(0.18, 0.22, 0.28, 1.0)
|
||||
const CARD_BORDER_SELECTED := Color(0.906, 0.773, 0.278, 1.0) # INSERT_COLOR_HOVER
|
||||
|
||||
# Archetypes in display order — index 0=smuggler (left card), 1=detective (right card)
|
||||
const ARCHETYPES := ["smuggler", "detective"]
|
||||
|
||||
@onready var _smuggler_wrapper: Control = $Cards/CardSmugglerWrapper
|
||||
@onready var _detective_wrapper: Control = $Cards/CardDetectiveWrapper
|
||||
@onready var _confirm_btn: Button = $ConfirmBtn
|
||||
@onready var _title_label: Label = $TitleLabel
|
||||
|
||||
var _selected_index: int = 0 # 0=smuggler, 1=detective
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_title_label.text = UIStrings.get_text("character_select.title")
|
||||
_confirm_btn.text = UIStrings.get_text("character_select.confirm")
|
||||
|
||||
# Smuggler card labels
|
||||
$Cards/CardSmugglerWrapper/CardInner/VBox/NameLabel.text = UIStrings.get_text("character_select.smuggler_card_name")
|
||||
$Cards/CardSmugglerWrapper/CardInner/VBox/RoleLabel.text = UIStrings.get_text("character_select.smuggler_card_role")
|
||||
$Cards/CardSmugglerWrapper/CardInner/VBox/ToneLabel.text = UIStrings.get_text("character_select.smuggler_card_tone")
|
||||
|
||||
# Detective card labels
|
||||
$Cards/CardDetectiveWrapper/CardInner/VBox/NameLabel.text = UIStrings.get_text("character_select.detective_card_name")
|
||||
$Cards/CardDetectiveWrapper/CardInner/VBox/RoleLabel.text = UIStrings.get_text("character_select.detective_card_role")
|
||||
$Cards/CardDetectiveWrapper/CardInner/VBox/ToneLabel.text = UIStrings.get_text("character_select.detective_card_tone")
|
||||
|
||||
_confirm_btn.pressed.connect(_on_confirm)
|
||||
_smuggler_wrapper.gui_input.connect(_on_card_input.bind(0))
|
||||
_detective_wrapper.gui_input.connect(_on_card_input.bind(1))
|
||||
|
||||
_update_card_visuals()
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if not visible:
|
||||
return
|
||||
if event is InputEventKey and event.pressed and not event.is_echo():
|
||||
match event.keycode:
|
||||
KEY_LEFT:
|
||||
_selected_index = 0
|
||||
_update_card_visuals()
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_RIGHT:
|
||||
_selected_index = 1
|
||||
_update_card_visuals()
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_ENTER, KEY_KP_ENTER:
|
||||
_on_confirm()
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_ESCAPE:
|
||||
archetype_cancelled.emit()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _on_card_input(event: InputEvent, card_index: int) -> void:
|
||||
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
_selected_index = card_index
|
||||
_update_card_visuals()
|
||||
|
||||
|
||||
func _on_confirm() -> void:
|
||||
archetype_confirmed.emit(ARCHETYPES[_selected_index])
|
||||
|
||||
|
||||
func _update_card_visuals() -> void:
|
||||
_set_card_selected(_smuggler_wrapper, _selected_index == 0)
|
||||
_set_card_selected(_detective_wrapper, _selected_index == 1)
|
||||
_confirm_btn.grab_focus()
|
||||
|
||||
|
||||
func _set_card_selected(wrapper: Control, selected: bool) -> void:
|
||||
var border: ColorRect = wrapper.get_node("CardBorder")
|
||||
var inner: ColorRect = wrapper.get_node("CardInner")
|
||||
border.color = CARD_BORDER_SELECTED if selected else CARD_BORDER_NORMAL
|
||||
inner.color = CARD_BG_SELECTED if selected else CARD_BG_NORMAL
|
||||
+5
-41
@@ -1,12 +1,10 @@
|
||||
extends Control
|
||||
## #258: Main menu — New Game / Continue / Load Game / Quit.
|
||||
## New Game: shows character select panel (D-085 save dir created after archetype chosen).
|
||||
## New Game: opens character creation screen, then starts game.
|
||||
## Continue: loads most recent save directory.
|
||||
## Load Game: shows sorted save list for manual selection (#257).
|
||||
## #588: Character archetype selection — panel shown between New Game click and game load.
|
||||
|
||||
const GAME_SCENE := "res://scenes/main.tscn"
|
||||
const CHARACTER_SELECT_SCENE := "res://scenes/character_select.tscn"
|
||||
const CHARACTER_CREATION_SCENE := "res://scenes/character_creation.tscn"
|
||||
|
||||
const BG_COLOR := Color(0.05, 0.05, 0.08, 1.0)
|
||||
@@ -26,8 +24,7 @@ const FONT_SIZE_BTN := 15
|
||||
@onready var _saves_list: VBoxContainer = $LoadGamePanel/VBox/SavesScroll/SavesList
|
||||
@onready var _load_back_btn: Button = $LoadGamePanel/VBox/BackBtn
|
||||
|
||||
var _char_select: Control = null # Instantiated on demand
|
||||
var _char_creation: Control = null # Instantiated after archetype confirmed
|
||||
var _char_creation: Control = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -47,40 +44,10 @@ func _refresh_continue_state() -> void:
|
||||
|
||||
|
||||
func _on_new_game() -> void:
|
||||
# #588: Show character select before creating the save directory.
|
||||
# ESC on character select cancels with no directory created.
|
||||
GameState.pending_load_path = ""
|
||||
_show_character_select()
|
||||
|
||||
|
||||
func _show_character_select() -> void:
|
||||
if _char_select != null and is_instance_valid(_char_select):
|
||||
_char_select.queue_free()
|
||||
var scene := load(CHARACTER_SELECT_SCENE) as PackedScene
|
||||
if scene == null:
|
||||
push_error("MainMenu: failed to load character_select.tscn")
|
||||
return
|
||||
_char_select = scene.instantiate()
|
||||
add_child(_char_select)
|
||||
_char_select.archetype_confirmed.connect(_on_archetype_confirmed)
|
||||
_char_select.archetype_cancelled.connect(_on_archetype_cancelled)
|
||||
|
||||
|
||||
func _on_archetype_confirmed(archetype: String) -> void:
|
||||
if _char_select != null and is_instance_valid(_char_select):
|
||||
_char_select.queue_free()
|
||||
_char_select = null
|
||||
# Set archetype first; save dir created after character creation confirms.
|
||||
GameState.character_archetype = archetype
|
||||
_show_character_creation()
|
||||
|
||||
|
||||
func _on_archetype_cancelled() -> void:
|
||||
if _char_select != null and is_instance_valid(_char_select):
|
||||
_char_select.queue_free()
|
||||
_char_select = null
|
||||
|
||||
|
||||
func _show_character_creation() -> void:
|
||||
if _char_creation != null and is_instance_valid(_char_creation):
|
||||
_char_creation.queue_free()
|
||||
@@ -107,8 +74,6 @@ func _on_creation_cancelled() -> void:
|
||||
if _char_creation != null and is_instance_valid(_char_creation):
|
||||
_char_creation.queue_free()
|
||||
_char_creation = null
|
||||
# Return to archetype select
|
||||
_show_character_select()
|
||||
|
||||
|
||||
func _start_game_with_defaults() -> void:
|
||||
@@ -116,12 +81,11 @@ func _start_game_with_defaults() -> void:
|
||||
if game_id.is_empty():
|
||||
push_error("MainMenu: new_game() failed to create save directory — cannot start")
|
||||
return
|
||||
SessionManager.save_character_archetype(game_id, GameState.character_archetype)
|
||||
get_tree().change_scene_to_file(GAME_SCENE)
|
||||
|
||||
|
||||
func _on_continue() -> void:
|
||||
GameState.pending_load_path = "" # clear stale load path from previous Load selection
|
||||
GameState.pending_load_path = ""
|
||||
var saves := SessionManager.list_game_dirs()
|
||||
if saves.is_empty():
|
||||
return
|
||||
@@ -129,7 +93,7 @@ func _on_continue() -> void:
|
||||
get_tree().change_scene_to_file(GAME_SCENE)
|
||||
|
||||
|
||||
var _list_built: bool = false # guard against queue_free() race on rapid reopen
|
||||
var _list_built: bool = false
|
||||
|
||||
|
||||
func _on_load_game_browse() -> void:
|
||||
@@ -141,7 +105,7 @@ func _on_load_game_browse() -> void:
|
||||
|
||||
func _on_load_back() -> void:
|
||||
_load_panel.visible = false
|
||||
_list_built = false # allow rebuild on next open
|
||||
_list_built = false
|
||||
|
||||
|
||||
func _on_quit() -> void:
|
||||
|
||||
Reference in New Issue
Block a user