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:
2026-03-22 18:18:47 +01:00
co-authored by Claude Opus 4.6
parent 92209f85e6
commit 145d8fec17
8 changed files with 32 additions and 344 deletions
+7 -7
View File
@@ -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()
-89
View File
@@ -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
View File
@@ -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: