extends SceneTree ## Editor-Test (headless): Spielmodus, Conquest-Zonen, Hills, Modus-Spawns (V21–V24) — Neu-Dialog, ## Katalog, Panel mit Undo/Redo, Speichern/Laden. Ausführen: ## godot --headless --path . --script res://tests/test_modus.gd var failures := 0 var checks := 0 var main: Control func _initialize() -> void: _run_all.call_deferred() func _check(cond: bool, label: String) -> void: checks += 1 if cond: print(" ok: %s" % label) else: failures += 1 print(" FAIL: %s" % label) func _signed(d: Dictionary) -> Dictionary: var c := d.duplicate(true) SvmapIO.sign_map(c) return c func _run_all() -> void: print("Testgruppe: Katalog + Neu-Map") var tdm := SvmapDocument.create_new("T", 512.0, "mittag") _check(not tdm.has("game_mode") and not tdm.has("conquest_zones") and not tdm.has("hills") and not tdm.has("mode_spawns"), "TDM-Map ohne Zusatzfelder (altes Format)") _check(SvmapIO.validate_map(_signed(tdm)).is_empty(), "TDM-Map gültig") var cp := SvmapDocument.create_new("C", 512.0, "mittag", "conquest") _check(cp["game_mode"] == "conquest" and (cp["conquest_zones"] as Array).size() == 3, "Conquest-Map mit 3 Start-Zonen") _check(SvmapIO.validate_map(_signed(cp)).is_empty(), "Conquest-Map gültig: " + str(SvmapIO.validate_map(_signed(cp)))) var kh := SvmapDocument.create_new("K", 512.0, "mittag", "koth") _check(kh["game_mode"] == "koth" and (kh["hills"] as Array).size() == 3 and not kh.has("conquest_zones"), "KotH-Map mit 3 Hills") _check(SvmapIO.validate_map(_signed(kh)).is_empty(), "KotH-Map gültig: " + str(SvmapIO.validate_map(_signed(kh)))) var dmd := SvmapDocument.create_new("D", 512.0, "mittag", "dm") _check(dmd["game_mode"] == "dm" and SvmapIO.validate_map(_signed(dmd)).is_empty(), "Deathmatch-Map gültig") # Alt-Map (vor dem Umbau): "checkpoints" lädt als Conquest, speichert neu var alt := cp.duplicate(true) alt["game_mode"] = "checkpoints" alt["checkpoints"] = alt["conquest_zones"] alt.erase("conquest_zones") var alt_signed := _signed(alt) _check(SvmapIO.validate_map(alt_signed).is_empty(), "Alt-Map (checkpoints) ist gültig") var alt_res := SvmapIO.parse_and_validate(JSON.stringify(alt_signed)) _check(alt_res["ok"] and alt_res["data"]["game_mode"] == "conquest" and alt_res["data"].has("conquest_zones") and not alt_res["data"].has("checkpoints"), "Alt-Map wird beim Laden zu Conquest") var bad := cp.duplicate(true) bad["conquest_zones"] = [] _check(not ModusKatalog.validate(bad, bad["bounds"]).is_empty(), "Modus ohne Zonen ungültig (V21)") bad = kh.duplicate(true) bad["hills"] = [] _check(not ModusKatalog.validate(bad, bad["bounds"]).is_empty(), "KotH ohne Hill ungültig (V21)") bad = cp.duplicate(true) bad["conquest_zones"][0]["pos"] = [9999.0, 0.0, 0.0] _check(not ModusKatalog.validate(bad, bad["bounds"]).is_empty(), "Zone außerhalb ungültig (V22)") bad = cp.duplicate(true) bad["conquest_zones"][1]["id"] = "C1" _check(not ModusKatalog.validate(bad, bad["bounds"]).is_empty(), "doppelte ID ungültig (V22)") bad = cp.duplicate(true) bad["game_mode"] = "ctf" _check(not ModusKatalog.validate(bad, bad["bounds"]).is_empty(), "unbekannter Modus ungültig (V21)") print("Testgruppe: Neu-Dialog") var dlg: ConfirmationDialog = preload("res://core/ui/new_map_dialog.gd").new() root.add_child(dlg) _check(dlg.get_game_mode() == "tdm", "Dialog-Default TDM") dlg.mode_option.select(1) _check(dlg.get_game_mode() == "conquest", "Dialog liefert Conquest") dlg.mode_option.select(2) _check(dlg.get_game_mode() == "koth", "Dialog liefert King of the Hill") dlg.mode_option.select(3) _check(dlg.get_game_mode() == "dm", "Dialog liefert Deathmatch") print("Testgruppe: Panel") var scene: PackedScene = load("res://core/scenes/main.tscn") main = scene.instantiate() root.add_child(main) await process_frame await process_frame main.doc = SvmapDocument.create_new("ModusTest", 512.0, "mittag") main.history.doc = main.doc main.call("_refresh_all") var panel: Node = main.map_mod.sections_host().get_node_or_null("modus") _check(panel != null, "Modus-Panel hängt im Map-Tab") if panel == null: quit(1) return _check(not panel.cp_box.visible, "TDM: Zonen-Bereich verborgen") panel.set_mode("conquest") _check(main.doc["game_mode"] == "conquest" and main.doc["conquest_zones"].size() == 3, "Wechsel auf Conquest setzt 3 Zonen") _check(panel.cp_box.visible and panel.rows_box.get_child_count() == 3, "3 Zeilen im Panel") _check(main.view_builder.zone_root != null and main.view_builder.zone_root.get_child_count() == 3, "3 Zonen im 3D-Bild") var id: String = panel.add_zone(50.0, -30.0) _check(id == "C4" and main.doc["conquest_zones"].size() == 4, "neue Zone C4") panel.edit_zone("C4", {"radius_m": 25.0}) panel.edit_zone("C4", {"x": 80.0, "z": 10.0, "label": "Turm"}) var c4: Dictionary = main.doc["conquest_zones"][3] _check(c4["radius_m"] == 25.0 and c4["pos"][0] == 80.0 and c4["pos"][2] == 10.0, "Radius + Position geändert") panel.edit_zone("C4", {"radius_m": 500.0}) _check(main.doc["conquest_zones"][3]["radius_m"] == ModusKatalog.MAX_RADIUS, "Radius wird begrenzt") _check(SvmapIO.validate_map(_signed(main.doc)).is_empty(), "Map gültig nach Änderungen") panel.remove_zone("C4") _check(main.doc["conquest_zones"].size() == 3, "Zone gelöscht") main.history.undo() main.call("_refresh_all") _check(main.doc["conquest_zones"].size() == 4, "Undo: Zone zurück") while main.history.can_undo(): main.history.undo() main.call("_refresh_all") _check(not main.doc.has("game_mode") and not main.doc.has("conquest_zones") and not panel.cp_box.visible, "Undo bis Start: Felder weg, UI nachgezogen") main.history.redo() main.call("_refresh_all") _check(main.doc.get("game_mode", "") == "conquest", "Redo: Modus wieder da") panel.set_mode("tdm") _check(not main.doc.has("game_mode"), "Zurück auf TDM entfernt game_mode") _check(main.view_builder.zone_root == null, "TDM: keine Zonen im Bild") print("Testgruppe: King of the Hill") panel.set_mode("koth") _check(main.doc["game_mode"] == "koth" and main.doc["hills"].size() == 3, "KotH setzt 3 Hills") _check(panel.rows_box.get_child_count() == 3 and main.view_builder.zone_root.get_child_count() == 3, "3 Hill-Zeilen und 3 Marker") var first_id: String = main.doc["hills"][0]["id"] panel.move_zone(first_id, 1) _check(main.doc["hills"][1]["id"] == first_id, "Hill nach hinten geschoben (Reihenfolge)") panel.move_zone(first_id, 5) _check(main.doc["hills"][1]["id"] == first_id, "Verschieben über den Rand wirkt nicht") var hid: String = panel.add_zone(10.0, 10.0) _check(hid == "H4" and main.doc["hills"].size() == 4, "neuer Hill H4") main.history.undo() main.history.undo() main.call("_refresh_all") _check(main.doc["hills"][0]["id"] == first_id and main.doc["hills"].size() == 3, "Undo stellt Reihenfolge und Liste wieder her") _check(SvmapIO.validate_map(_signed(main.doc)).is_empty(), "KotH-Map gültig") print("Testgruppe: Modus-Spawns") _check(not main.doc.has("mode_spawns"), "ohne Anlegen kein mode_spawns-Block") panel.select_spawn_set("koth") _check(SpawnOps.edit_set == "koth" and SpawnOps.count_points(main.doc, 0) == 0, "KotH-Satz leer, solange nichts angelegt") panel.create_spawns("koth") _check(main.doc.has("mode_spawns") and main.doc["mode_spawns"]["koth"]["0"]["points"].size() == 4, "Anlegen kopiert die 4 Basis-Spawns je Team") _check(SpawnOps.count_points(main.doc, 0) == 4 and SpawnOps.count_points(main.doc, 1) == 4, "Spawn-Werkzeug sieht jetzt den KotH-Satz") var kid := str(main.doc["mode_spawns"]["koth"]["0"]["points"][0]["id"]) _check(kid.begins_with("KA") and SpawnOps.next_id(main.doc, 0).begins_with("KA"), "IDs mit Modus-Präfix (%s)" % kid) var added := SpawnOps.add_zone(main.doc, 0, 30.0, 30.0) _check(main.doc["mode_spawns"]["koth"]["0"]["points"].size() == 5 and main.doc["spawn_zones"]["0"]["points"].size() == 4, "neuer Punkt landet nur im KotH-Satz") _check(SvmapIO.validate_map(_signed(main.doc)).is_empty(), "Map mit eigenen Spawns gültig: " + str(SvmapIO.validate_map(_signed(main.doc)))) main.history.undo() main.call("_refresh_all") _check(not main.doc.has("mode_spawns"), "Undo entfernt den Spawn-Satz wieder") panel.select_spawn_set("dm") panel.create_spawns("dm") _check(main.doc["mode_spawns"]["dm"].has("all") and main.doc["mode_spawns"]["dm"]["all"]["points"].size() == 8, "Deathmatch: beide Teams in einem Topf (8 Punkte)") SpawnOps.add_zone(main.doc, 1, 40.0, 40.0) _check(main.doc["mode_spawns"]["dm"]["all"]["points"].size() == 9 and not main.doc["mode_spawns"]["dm"].has("1"), "Deathmatch: Team-Werkzeug schreibt nach 'all'") _check(SvmapIO.validate_map(_signed(main.doc)).is_empty(), "DM-Spawns gültig") var bad_ms: Dictionary = main.doc.duplicate(true) bad_ms["mode_spawns"]["dm"]["0"] = {"points": [{"id": "X", "pos": [0.0, 0.0, 0.0]}]} _check(not ModusKatalog.validate(bad_ms, bad_ms["bounds"]).is_empty(), "V24: Team-Schlüssel im DM-Satz ungültig") panel.remove_spawns("dm") _check(not main.doc.has("mode_spawns") and SpawnOps.edit_set == "base", "Löschen entfernt den Satz und schaltet auf Basis zurück") SpawnOps.edit_set = "base" panel.set_mode("conquest") var path := "user://modus_test.svmap" var doc2 := _signed(main.doc) var res := SvmapIO.save_to_file(path, doc2) _check(res["ok"], "Speichern ok: " + str(res["errors"])) var back := SvmapIO.load_from_file(path) _check(back["ok"] and back["data"]["game_mode"] == "conquest" and back["data"]["conquest_zones"].size() >= 3, "Laden: Modus + Zonen erhalten") if failures == 0: print("ALL OK: %d checks" % checks) quit(0) else: print("FAILED: %d/%d checks" % [failures, checks]) quit(1)