foitin/engine/scenes/select.gd
m3ultra d743216b54 Character select screen + fatality-style finisher system
- Select scene (new main scene): roster cards from characters/* with
  portraits, dual cursors (P1 A/D+J, P2 arrows+O), picks via GameState
- finisher flow: KO puts loser in DIZZY with a 5s flashing FINISH! window;
  winner lands QCF+K in range -> finisher move (Brutal Assassination /
  Hell Slammer B) with darkened stage, victim plays synced death anim;
  window expiry or any stray hit = normal collapse
- new moves rendered for both fighters in all wardrobe states:
  dizzy (Stunned), finisher, death (Standing Death Backward)
- CHAR_TUNE per-character move overrides in tune_fighters.py

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 23:27:51 +10:00

140 lines
4.0 KiB
GDScript

extends Node2D
## Character select: P1 picks with A/D + J, P2 with arrows + O.
## Roster = whatever folders are under characters/ — same scan as the match.
var roster: Array = [] # [{path, id, name, portrait, color}]
var cursor := [0, 0]
var locked := [false, false]
var cards: Array = []
var cursors_ui: Array = []
var vs_timer := -1
const CARD_W := 220
const CARD_H := 300
func _ready() -> void:
var bg := ColorRect.new()
bg.color = Color("#14161c")
bg.size = Vector2(1280, 720)
add_child(bg)
var title := Label.new()
title.text = "F O I T I N"
title.add_theme_font_size_override("font_size", 64)
title.position = Vector2(490, 40)
add_child(title)
var sub := Label.new()
sub.text = "CHOOSE YOUR FIGHTER"
sub.add_theme_font_size_override("font_size", 24)
sub.modulate = Color(1, 1, 1, 0.6)
sub.position = Vector2(520, 120)
add_child(sub)
for path in CharacterData.scan_roster():
var d = JSON.parse_string(FileAccess.get_file_as_string(path + "/manifest.json"))
var entry := {"path": path, "id": d.get("id", ""), "name": d.get("name", "?"),
"color": Color(d.get("color", "#888888")), "portrait": null}
var pp := ProjectSettings.globalize_path(path + "/portrait.png")
if FileAccess.file_exists(pp):
var img := Image.load_from_file(pp)
if img:
entry.portrait = ImageTexture.create_from_image(img)
roster.append(entry)
var total_w: int = roster.size() * (CARD_W + 40) - 40
var x0: float = 640.0 - total_w / 2.0
for i in roster.size():
var card := Node2D.new()
card.position = Vector2(x0 + i * (CARD_W + 40), 220)
add_child(card)
var frame := ColorRect.new()
frame.color = Color("#22252e")
frame.size = Vector2(CARD_W, CARD_H)
card.add_child(frame)
var accent := ColorRect.new()
accent.color = roster[i].color
accent.size = Vector2(CARD_W, 8)
accent.position = Vector2(0, CARD_H - 8)
card.add_child(accent)
if roster[i].portrait:
var spr := Sprite2D.new()
spr.texture = roster[i].portrait
spr.centered = false
spr.scale = Vector2(1.2, 1.2)
spr.position = Vector2((CARD_W - 192) / 2.0, 24)
card.add_child(spr)
var nm := Label.new()
nm.text = roster[i].name
nm.add_theme_font_size_override("font_size", 26)
nm.position = Vector2(20, CARD_H - 52)
card.add_child(nm)
cards.append(card)
for p in 2:
var cur := ColorRect.new()
cur.color = Color("#e8c840") if p == 0 else Color("#40a8e8")
cur.size = Vector2(CARD_W + 12, 6)
add_child(cur)
var tag := Label.new()
tag.text = "P%d" % (p + 1)
tag.add_theme_font_size_override("font_size", 20)
cur.add_child(tag)
tag.position = Vector2(-2, 8 if p == 0 else -34)
cursors_ui.append(cur)
var help := Label.new()
help.text = "P1: A/D move, J lock in P2: arrows move, O lock in"
help.modulate = Color(1, 1, 1, 0.4)
help.position = Vector2(440, 660)
add_child(help)
_place_cursors()
func _place_cursors() -> void:
for p in 2:
var card: Node2D = cards[cursor[p]]
cursors_ui[p].position = card.position + Vector2(-6, CARD_H + 14 if p == 0 else -22)
cursors_ui[p].modulate.a = 0.5 if locked[p] else 1.0
func _input(event: InputEvent) -> void:
if not event is InputEventKey or not event.pressed or event.echo:
return
if vs_timer >= 0:
return
match event.keycode:
KEY_A: _move(0, -1)
KEY_D: _move(0, 1)
KEY_J: _lock(0)
KEY_LEFT: _move(1, -1)
KEY_RIGHT: _move(1, 1)
KEY_O: _lock(1)
_place_cursors()
func _move(p: int, dir: int) -> void:
if locked[p]:
return
cursor[p] = wrapi(cursor[p] + dir, 0, roster.size())
func _lock(p: int) -> void:
locked[p] = true
if locked[0] and locked[1]:
GameState.p1_pick = roster[cursor[0]].id
GameState.p2_pick = roster[cursor[1]].id
var vs := Label.new()
vs.text = "%s VS %s" % [roster[cursor[0]].name, roster[cursor[1]].name]
vs.add_theme_font_size_override("font_size", 48)
vs.position = Vector2(400, 580)
add_child(vs)
vs_timer = 60
func _physics_process(_d: float) -> void:
if vs_timer > 0:
vs_timer -= 1
elif vs_timer == 0:
get_tree().change_scene_to_file("res://engine/scenes/Fight.tscn")