Arcade/kiosk name flow, free cursor in menu and portable Linux export

Every game over unlocks the profile and frees the name, so the menu always asks
for a new player name: the field is empty at every boot, restart or game over and
the game does not start without a name (no anonymous scores). The player identity
is derived from the entered name (deterministic FNV-1a, 16 hex chars valid for the
public API) and the profile keeps the best score per name. Backend untouched.

Mouse: player._ready() captured the pointer before the menu existed, so the start
screen was not clickable. The cursor now always starts visible; the capture happens
only in player.begin_play(), called by start_game(). New `playing` flag gates Esc
and the Web click capture to the actual match.

Linux export preset: single-file x86_64 binary with the PCK embedded, no
libstdc++ dependency, official 4.7.1 template (glibc >= 2.28). Notes and the
export_presets.cfg `#`-is-not-a-comment trap documented in docs/KIOSK-ARCADE.md.

Tests: profile 30/30, start screen 15/15 (cursor free at menu, captured only in
game), boss 50/50, rebalance 14/14, touch 14 PASS, API 42/42, all other suites pass.
This commit is contained in:
enne2
2026-10-24 12:50:03 +02:00
parent 21a1d5bf79
commit cb14fd5400
9 changed files with 397 additions and 106 deletions
+16 -4
View File
@@ -34,6 +34,9 @@ var death_overlay: ColorRect
## True quando la partita e' guidata dai controlli on-screen (touch): il mouse
## emulato dai tocchi non deve muovere la visuale ne' sparare al posto del dito.
var touch_mode := false
## Vero solo dopo l'inizio della partita: al menu il cursore deve restare libero,
## altrimenti la schermata iniziale non e' cliccabile.
var playing := false
const Blood = preload("res://scripts/blood_effects.gd")
const Audio = preload("res://scripts/hl_audio.gd")
@@ -80,12 +83,21 @@ func _ready() -> void:
flash.omni_range = 7
flash.light_energy = 0
camera.add_child(flash)
if DisplayServer.get_name() != "headless" and not OS.has_feature("web"):
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
# Al menu il cursore deve essere visibile e libero: la cattura avviene solo quando
# la partita inizia (begin_play(), chiamata da main.gd::start_game()).
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
var restart_button: Button
var death_note: Label
## Inizio partita: qui, e solo qui, il pointer viene catturato. In modalita' touch il
## cursore serve ai controlli on-screen e il mirino e' emulato dal dito, quindi non
## viene mai catturato.
func begin_play() -> void:
playing = true
if not touch_mode and DisplayServer.get_name() != "headless":
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
## Riga sotto il messaggio di morte: punteggio inviato, posizione in classifica o
## motivo del fallimento. Richiamata da main.gd a conclusione dell'invio.
func set_death_note(text: String) -> void:
@@ -102,11 +114,11 @@ func _input(event: InputEvent) -> void:
get_tree().call_deferred("reload_current_scene")
func _unhandled_input(event: InputEvent) -> void:
if OS.has_feature("web") and not touch_mode and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT and not death_started:
if playing and OS.has_feature("web") and not touch_mode and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT and not death_started:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
if event is InputEventMouseMotion and not touch_mode and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
apply_look(event.relative)
if event.is_action_pressed("pause") and not death_started:
if playing and event.is_action_pressed("pause") and not death_started:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED else Input.MOUSE_MODE_CAPTURED
## Ricarica: tempi invariati, riusata da tastiera (R) e dal pulsante on-screen.