Files
settled-reach/client/ui/character_select.gd
T
jpmschweitzerandClaude Opus 4.6 61d454228d feat(client): character select, triangle activation consumer, news ticker
Sprint 24 Signal — three client tickets delivering the player-facing
storyteller feedback loop:

- #588: Character archetype select screen between New Game and session
  start. Two-card UI (Smuggler/Detective), keyboard+mouse, ESC cancels.
  GameState.character_archetype persisted and sent in StartupMessage.
  PROTOCOL_VERSION bumped to 19.
- #590: Triangle crisis event consumer. Decodes triangle_crisis_events
  from snapshot, fires sfx_monologue_chime_urgent once per triangle per
  session via AudioManager.CHIME_ACTIVATION.
- #592: News ticker HUD element. Scrolling marquee on UILayer, visible
  only when current_ticker is present in snapshot (Last Shift zone).
  Zero-arg update_from_state reads from GameState.current_snapshot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:44:05 +01:00

90 lines
3.5 KiB
GDScript

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