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)