class_name TestRun extends RefCounted ## ALT (Karte t_1cdc1a72): dieser Client-Start ist seit dem Lauf-Modus im ## Map-Builder selbst NICHT mehr der Standard-Weg — „▶ Karte testen" startet ## jetzt IN DIESER APP (core/editor/playtest/, kein Client-Ordner, kein ## zweiter Prozess, kein VR). Der Rückbau dieser Datei kommt in einer ## späteren Karte; bis dahin bleibt die Export-/Such-Logik als Fallback ## erhalten und getestet (tests/test_testrun.gd). ## ## Ursprünglicher Zweck (Karte t_b84af40f): Export der AKTUELLEN Map nach Temp ## + Start des scopedvr-client als EIGENES Fenster im Testlauf-Modus ## (--testmap ). Der Editor bleibt offen — der Client ist ein eigener ## Prozess; das Spiel beendet sich selbst sauber (ESC = „Zurück zum Editor"). ## ## Client-Suche (erste Treffer-Zeile gewinnt): ## 1. Konfigurationsdatei user://scopedvr_editor.cfg, Abschnitt [test]: ## client_dir="/pfad/zu/scopedvr-client" ## 2. Klon neben dem Editor: /../scopedvr-client ## (erkennbar an project.godot im Ordner). ## ## Godot-Binary für den Start: das Binary, unter dem der Editor selbst läuft ## (OS.get_executable_path — Sly startet den Editor über den Godot-Editor, ## also kann DAS auch den Client starten). Läuft der Editor irgendwann als ## exportierte App, muss [test] godot_path gesetzt werden. ## ## Alles static + testbar ohne UI (tests/test_testrun.gd). const CFG_PATH := "user://scopedvr_editor.cfg" const TESTRUN_DIR := "user://testrun" static func load_cfg() -> ConfigFile: var cfg := ConfigFile.new() if FileAccess.file_exists(CFG_PATH): cfg.load(CFG_PATH) # Fehler → Default-Logik greift weiter unten. return cfg ## Liefert den Client-Projektordner (mit project.godot) oder "". static func find_client(project_dir: String, cfg: ConfigFile = null) -> String: var candidates: Array = [] if cfg != null: var configured := str(cfg.get_value("test", "client_dir", "")) # Explizit gesetzter Pfad gewinnt, sobald der ORDNER existiert # (bewusst ohne project.godot-Check — wer ihn konfiguriert, weiß es besser). if not configured.is_empty() and DirAccess.dir_exists_absolute(configured): return configured var parent := project_dir.simplify_path().get_base_dir() candidates.append(parent.path_join("scopedvr-client")) for candidate in candidates: var dir := str(candidate).simplify_path() if FileAccess.file_exists(dir.path_join("project.godot")): return dir return "" ## Godot-Binary zum Starten des Clients (Editor-Binary oder Konfiguration). static func find_godot(cfg: ConfigFile = null) -> String: var exe := OS.get_executable_path() if exe.get_file().to_lower().contains("godot"): return exe if cfg != null: return str(cfg.get_value("test", "godot_path", "")) return "" ## Deterministischer Temp-Pfad aus dem Map-Namen (erneuter Testlauf ## überschreibt die alte Export-Datei — kein Müll-Berg). static func temp_map_path(doc: Dictionary) -> String: var raw := str(doc.get("info", {}).get("name", "")).strip_edges() if raw.is_empty(): raw = "karte" var safe := "" for ch in raw: var c := ch.to_lower() if (c >= "a" and c <= "z") or (c >= "0" and c <= "9") or ch == "_" or ch == "-" or ch == " ": safe += ch else: safe += "_" safe = safe.strip_edges().replace(" ", "_").rstrip("_") if safe.is_empty(): safe = "karte" return TESTRUN_DIR.path_join(safe + ".svmap") ## Exportiert das AKTUELLE Dokument (auch ungespeicherte Maps!) nach Temp. ## Rückgabe: {ok, errors, path} — path ist globalisiert (für den Client-Prozess). static func export_temp_map(doc: Dictionary) -> Dictionary: var rel := temp_map_path(doc) DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(TESTRUN_DIR)) var result := SvmapIO.save_to_file(rel, doc) result["path"] = ProjectSettings.globalize_path(rel) return result ## Hand-Haltungs-Tool: liegt im Client-Projekt, weil es dessen Rig und Waffen ## direkt benutzt (kein VR, eigenes Fenster). static func hand_pose_args(client_dir: String) -> PackedStringArray: return PackedStringArray(["--xr-mode", "off", "--path", client_dir, "res://tools/hand_pose_tool/hand_pose_tool.tscn"]) ## Import-Lauf für den Client, nötig nach neuem Stand (neue Modelle/Skripte). static func import_args(client_dir: String) -> PackedStringArray: return PackedStringArray(["--headless", "--path", client_dir, "--import"]) ## Argumentliste für den Client-Start (getestet: exakt diese Form). static func launch_args(client_dir: String, map_path: String) -> PackedStringArray: return PackedStringArray(["--path", client_dir, "--", "--testmap", map_path, "--mock-master"])