La classifica globale non dipende più da un argomento: sui build desktop il client usa il servizio pubblico DEFAULT_API_BASE (https://enne2.net/api/zombie-fps). - scripts/leaderboard.gd: resolve_base() statica con priorità --api=<url> (test) → --offline/--no-api (opt-out esplicito per le partite di prova) → origine della pagina sul Web → servizio pubblico. Rimosso l'avviso "Classifica disattivata". - Le suite di test (--script) restano spente a meno di un --api= esplicito, così nessun test può scrivere nella classifica pubblica. - tools/test_start_screen.gd: verifica default attivo, opt-out e override; tools/test_rebalance_branding.gd: suite di test senza invii. - docs/HIGHSCORES.md: nuova regola della base URL e diagnostica aggiornata. Verifiche: test_start_screen 18/18 PASS con display; test_rebalance_branding 14/14 PASS; test_leaderboard.sh invariato (12 PASS, 2 FAIL preesistenti su questa macchina anche prima della modifica).
202 lines
8.3 KiB
GDScript
202 lines
8.3 KiB
GDScript
extends SceneTree
|
|
## Verifica le regole della schermata iniziale in modalita' arcade/kiosk:
|
|
## - la partita parte SOLO dal pulsante GIOCA: click o tocco sullo sfondo, oppure
|
|
## Invio/"Vai" nel campo del nome, non devono avviare nulla;
|
|
## - il campo del nome c'e' sempre, arriva vuoto e senza nome la partita non parte;
|
|
## - al ritorno al menu dopo il game over il campo viene liberato per il nuovo giocatore.
|
|
## Avviare con: --headless --path . --script res://tools/test_start_screen.gd
|
|
## (con display, per gli eventi di puntamento: --rendering-method gl_compatibility)
|
|
const Profile = preload("res://scripts/player_profile.gd")
|
|
const StartScreen = preload("res://scripts/start_screen.gd")
|
|
const Leaderboard = preload("res://scripts/leaderboard.gd")
|
|
|
|
var world: Node3D
|
|
var screen: CanvasLayer
|
|
var failures := 0
|
|
var checks := 0
|
|
var emitted := ""
|
|
var start_events := 0
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
func check(label: String, condition: bool, detail: String = "") -> void:
|
|
checks += 1
|
|
if condition:
|
|
print("PASS: " + label)
|
|
else:
|
|
failures += 1
|
|
print("FAIL: %s %s" % [label, detail])
|
|
|
|
func run_test() -> void:
|
|
await test_world_menu()
|
|
await test_name_always_asked()
|
|
await test_unlocked_name()
|
|
print("%d controlli superati, %d falliti" % [checks - failures, failures])
|
|
quit(1 if failures > 0 else 0)
|
|
|
|
## --- il menu del gioco vero ---------------------------------------------------
|
|
|
|
func test_world_menu() -> void:
|
|
world = load("res://scenes/main.tscn").instantiate()
|
|
root.add_child(world)
|
|
await process_frame
|
|
await process_frame
|
|
screen = world.start_screen
|
|
screen.started.connect(_on_started)
|
|
|
|
var gioca: Button = null
|
|
for button in screen.find_children("*", "Button", true, false):
|
|
if button.text == "GIOCA":
|
|
gioca = button
|
|
var backdrop: ColorRect = screen.find_children("*", "ColorRect", true, false)[0]
|
|
var outside := Vector2(40.0, root.size.y - 80.0)
|
|
var button_center: Vector2 = gioca.get_global_rect().get_center()
|
|
check("il pulsante GIOCA c'e' e lo sfondo usato nel test e' fuori dai controlli",
|
|
gioca != null and not gioca.get_global_rect().has_point(outside),
|
|
"pulsante=%s" % str(gioca))
|
|
|
|
## Bug risoluto: al menu il cursore deve essere visibile e libero, la cattura
|
|
## avviene solo all'inizio della partita (begin_play()).
|
|
check("al menu il giocatore non e' ancora in partita (playing=false)",
|
|
world.player.playing == false, "playing=%s" % str(world.player.playing))
|
|
check("al menu il cursore e' visibile e libero (schermata cliccabile)",
|
|
Input.mouse_mode == Input.MOUSE_MODE_VISIBLE, "mode=%s" % str(Input.mouse_mode))
|
|
var esc := InputEventKey.new()
|
|
esc.physical_keycode = KEY_ESCAPE
|
|
esc.pressed = true
|
|
Input.parse_input_event(esc)
|
|
await process_frame
|
|
check("Esc al menu non cattura il cursore (solo durante la partita)",
|
|
Input.mouse_mode == Input.MOUSE_MODE_VISIBLE, "mode=%s" % str(Input.mouse_mode))
|
|
|
|
_click(outside)
|
|
await process_frame
|
|
await process_frame
|
|
check("il click sullo sfondo non avvia la partita", start_events == 0 and world.start_screen != null,
|
|
"avvii=%d" % start_events)
|
|
|
|
_touch(outside)
|
|
await process_frame
|
|
await process_frame
|
|
check("il tocco sullo sfondo non avvia la partita", start_events == 0 and world.start_screen != null,
|
|
"avvii=%d" % start_events)
|
|
|
|
if screen.name_field != null:
|
|
screen.name_field.text = "Prova"
|
|
screen.name_field.text_submitted.emit("Prova")
|
|
await process_frame
|
|
await process_frame
|
|
check("l'Invio nel campo del nome non avvia la partita",
|
|
start_events == 0 and world.start_screen != null, "avvii=%d" % start_events)
|
|
else:
|
|
check("l'Invio nel campo del nome non avvia la partita", true, "(nome gia' bloccato)")
|
|
|
|
_click(button_center)
|
|
await process_frame
|
|
await process_frame
|
|
check("il pulsante GIOCA avvia la partita", start_events == 1 and world.start_screen == null,
|
|
"avvii=%d schermata=%s" % [start_events, "presente" if world.start_screen != null else "via"])
|
|
check("all'inizio della partita il giocatore e' in partita e il cursore viene catturato",
|
|
world.player.playing == true and (DisplayServer.get_name() == "headless"
|
|
or Input.mouse_mode == Input.MOUSE_MODE_CAPTURED),
|
|
"playing=%s mode=%s display=%s" % [str(world.player.playing), str(Input.mouse_mode),
|
|
DisplayServer.get_name()])
|
|
# La classifica e' attiva di default (nessuna configurazione). Il nodo qui e' spento
|
|
# perche' questa suite gira come --script: le regole si verificano su resolve_base().
|
|
check("senza argomenti la classifica e' attiva sul servizio pubblico",
|
|
Leaderboard.resolve_base(PackedStringArray(), false) == Leaderboard.DEFAULT_API_BASE
|
|
and Leaderboard.DEFAULT_API_BASE == "https://enne2.net/api/zombie-fps",
|
|
"base=%s" % str(Leaderboard.resolve_base(PackedStringArray(), false)))
|
|
check("--offline (e --no-api) spengono la classifica per le partite di prova",
|
|
Leaderboard.resolve_base(PackedStringArray(["--offline"]), false) == ""
|
|
and Leaderboard.resolve_base(PackedStringArray(["--no-api"]), false) == "")
|
|
check("--api= vince su --offline e normalizza la barra finale",
|
|
Leaderboard.resolve_base(PackedStringArray(["--offline", "--api=http://127.0.0.1:8799/"]), false)
|
|
== "http://127.0.0.1:8799")
|
|
check("in una suite di test la classifica resta spenta",
|
|
world.leaderboard.disabled == true and world.leaderboard.base_url == ""
|
|
and Leaderboard.resolve_base(PackedStringArray(), true) == "",
|
|
"base=%s disattivata=%s" % [str(world.leaderboard.base_url), str(world.leaderboard.disabled)])
|
|
world.queue_free()
|
|
await process_frame
|
|
|
|
func _on_started(player_name: String) -> void:
|
|
emitted = player_name
|
|
start_events += 1
|
|
|
|
## --- nome sempre richiesto (macchina condivisa) ------------------------------
|
|
|
|
func menu_with(profile: Dictionary) -> CanvasLayer:
|
|
var layer := CanvasLayer.new()
|
|
layer.set_script(StartScreen)
|
|
layer.set("profile", profile)
|
|
root.add_child(layer)
|
|
layer.started.connect(_on_started)
|
|
return layer
|
|
|
|
func test_name_always_asked() -> void:
|
|
emitted = ""
|
|
start_events = 0
|
|
# Un profilo scritto da una versione precedente arriva con il nome gia' scelto e
|
|
# bloccato: in modalita' arcade il campo deve esserci comunque, vuoto.
|
|
var menu := menu_with({"name": "Mat", "player_id": "abcdef0123456789", "best": 300, "locked": true})
|
|
await process_frame
|
|
var field: LineEdit = menu.get("name_field")
|
|
var field_text := "" if field == null else str(field.text)
|
|
check("regola arcade: campo del nome presente e vuoto anche con profilo precedente",
|
|
field != null and menu.get("name_locked") == false and field_text == "",
|
|
"campo=%s testo=%s bloccato=%s" % [str(field), field_text, str(menu.get("name_locked"))])
|
|
var gioca: Button = null
|
|
for button in menu.find_children("*", "Button", true, false):
|
|
if button.text == "GIOCA":
|
|
gioca = button
|
|
if gioca != null:
|
|
gioca.pressed.emit()
|
|
check("senza nome scritto la partita non parte (nessun punteggio anonimo)",
|
|
start_events == 0 and emitted == "", "avvii=%d nome=%s" % [start_events, emitted])
|
|
if field != null and gioca != null:
|
|
field.text = "Neo"
|
|
gioca.pressed.emit()
|
|
check("con il nome scritto la partita parte con quel nome", emitted == "Neo", "nome=%s" % emitted)
|
|
if field != null:
|
|
menu.reset_for_new_player()
|
|
check("game over: campo liberato per il giocatore successivo", str(field.text) == "",
|
|
"testo=%s" % str(field.text))
|
|
menu.queue_free()
|
|
await process_frame
|
|
|
|
func test_unlocked_name() -> void:
|
|
emitted = ""
|
|
var menu := menu_with({"name": Profile.DEFAULT_NAME, "player_id": "abcdef0123456789", "best": 0})
|
|
await process_frame
|
|
var field: LineEdit = menu.get("name_field")
|
|
check("senza nome scelto il campo e' presente e usabile",
|
|
field != null and field.editable and field.max_length == Profile.NAME_MAX,
|
|
"campo=%s" % str(field))
|
|
menu.queue_free()
|
|
await process_frame
|
|
|
|
## --- eventi sintetici --------------------------------------------------------
|
|
|
|
func _click(point: Vector2) -> void:
|
|
var motion := InputEventMouseMotion.new()
|
|
motion.position = point
|
|
motion.global_position = point
|
|
Input.parse_input_event(motion)
|
|
for pressed in [true, false]:
|
|
var event := InputEventMouseButton.new()
|
|
event.button_index = MOUSE_BUTTON_LEFT
|
|
event.pressed = pressed
|
|
event.position = point
|
|
event.global_position = point
|
|
Input.parse_input_event(event)
|
|
|
|
func _touch(point: Vector2) -> void:
|
|
for pressed in [true, false]:
|
|
var event := InputEventScreenTouch.new()
|
|
event.index = 0
|
|
event.pressed = pressed
|
|
event.position = point
|
|
Input.parse_input_event(event)
|