- 423 Objekte in 19 deutschen Kategorien (Panzer, Radfahrzeuge, Flugzeuge, Gebaeude, Zelte, Tuerme, Mauern, Sperren, Kisten, Technik, Felsen, Boden, Decals ...); Gruppen "Bauwerke" und "Kisten & Barrieren" samt Test-Asset-Namenslisten entfernt - LibraryBrowser: Gruppen/Namen aus dem Manifest (kategorie, name_de), unbekannte Kategorien bekommen automatisch eine eigene Gruppe - Thumbs neu, library/ synchronisiert (inkl. neuer Kollision) - Neue Identitaetsprobe: Kollision ALLER Objekte Editor = Client = Server - Tests auf Pack-Objekte umgestellt Co-Authored-By: Claude <noreply@anthropic.com>
402 lines
17 KiB
GDScript
402 lines
17 KiB
GDScript
extends SceneTree
|
||
## Editor-Test (headless): Karte t_96e3c4ee — Test-Startpunkt per Klick
|
||
## („hinklicken, wo man beim Testen spawnt", statt nur Team-Spawnpunkte).
|
||
## Ausführen: godot4 --headless --path . --script res://tests/test_playtest_spawn.gd
|
||
## Exit 0 = alles grün.
|
||
##
|
||
## Deckt die Karten-Pflichten ab:
|
||
## 1. Setzen/Löschen ändert die .svmap NICHT (Byte-Vergleich) + kein Undo.
|
||
## 2. Höhe = Terrainhöhe an der Stelle (Toleranz 1 cm).
|
||
## 3. Fallback-Reihenfolge je einmal belegt (Test-Start → Team-Spawn → Mitte).
|
||
## 4. Nach „Neustart" wiederhergestellt (Sidecar laden/leeren).
|
||
## 5. T im Testlauf versetzt genau dorthin, Kapsel-Physik (0.65 über Mitte), Kollision
|
||
## beachtet (blocked = ok=false, Position unverändert; Kisten-Dach = oben).
|
||
## 6. Marker existiert nur bei gesetztem Punkt.
|
||
## 7. Test-Start taucht in KEINER Spawn-Zone/Analyse auf.
|
||
|
||
var failures := 0
|
||
var checks := 0
|
||
var main: Control
|
||
var subvp: SubViewport
|
||
|
||
|
||
func _initialize() -> void:
|
||
_run_all.call_deferred()
|
||
|
||
|
||
func _run_all() -> void:
|
||
# Sauberer Sidecar-Start (Suite darf von früheren Läufen nichts erben).
|
||
TestStartStore.clear_point("")
|
||
var scene: PackedScene = load("res://scenes/main.tscn")
|
||
_check(scene != null, "scenes/main.tscn laedt")
|
||
if scene == null:
|
||
_finish()
|
||
return
|
||
main = scene.instantiate()
|
||
root.add_child(main)
|
||
await process_frame
|
||
await process_frame
|
||
main.call("_test_start_reload") # frischer Zustand nach dem Key-Clear
|
||
|
||
var container := _find_viewport_container(main)
|
||
if container != null:
|
||
subvp = container.get_child(0) as SubViewport
|
||
subvp.size = Vector2i(800, 600) # deterministisches Fadenkreuz
|
||
await process_frame
|
||
|
||
# Test-Map: hügeliges Terrain (Höhe steigt mit x), Team-0-Spawn, Objekt.
|
||
var doc: Dictionary = SvmapDocument.create_new("Test-Start-Karte", 100.0, "mittag")
|
||
var terrain: Dictionary = doc.get("terrain", {})
|
||
var res := int(terrain.get("resolution", 0))
|
||
var heights := PackedFloat32Array()
|
||
heights.resize(res * res)
|
||
for zi in res:
|
||
for xi in res:
|
||
heights[zi * res + xi] = 2.0 + float(xi) * 0.1 # 2.0 … +Ramp
|
||
terrain["min_height_m"] = 2.0
|
||
terrain["max_height_m"] = 2.0 + float(res - 1) * 0.1
|
||
terrain["heightmap_r16_b64"] = TerrainField.encode(heights, 2.0, 2.0 + float(res - 1) * 0.1)
|
||
doc["objects"] = [
|
||
{"id": "box_001", "pos": [10.0, 0.0, 0.0], "scale": 1.0, "rot_y_deg": 0.0},
|
||
]
|
||
# V11-konform: 4 Punkte pro Team (sonst lehnt der Validator das Speichern ab).
|
||
doc["spawn_zones"] = {
|
||
"0": {"points": [
|
||
{"id": "A1", "pos": [-20.0, 0.0, 0.0]}, {"id": "A2", "pos": [-24.0, 0.0, 2.0]},
|
||
{"id": "A3", "pos": [-22.0, 0.0, -3.0]}, {"id": "A4", "pos": [-26.0, 0.0, -2.0]},
|
||
]},
|
||
"1": {"points": [
|
||
{"id": "B1", "pos": [20.0, 0.0, 0.0]}, {"id": "B2", "pos": [24.0, 0.0, 2.0]},
|
||
{"id": "B3", "pos": [22.0, 0.0, -3.0]}, {"id": "B4", "pos": [26.0, 0.0, -2.0]},
|
||
]},
|
||
}
|
||
main.set("doc", doc)
|
||
main.set("current_path", "")
|
||
main.set("dirty", false)
|
||
main.get("history").begin(doc)
|
||
main.call("_clear_edit_state")
|
||
main.call("_refresh_all")
|
||
main.call("_test_start_reload")
|
||
await process_frame
|
||
await process_frame
|
||
|
||
_test_svmap_unchanged()
|
||
_test_height_snaps_to_terrain()
|
||
_test_fallback_order()
|
||
_test_sidecar_roundtrip()
|
||
_test_marker_visibility()
|
||
_test_t_key_toggles_tool()
|
||
_test_not_a_game_spawn()
|
||
await _test_playtest_teleport()
|
||
|
||
_finish()
|
||
|
||
|
||
func _finish() -> void:
|
||
TestStartStore.clear_point("") # Suite hinterlässt keinen Müll
|
||
if failures == 0:
|
||
print("ALL OK: %d checks" % checks)
|
||
quit(0)
|
||
else:
|
||
print("FAILED: %d/%d checks" % [failures, checks])
|
||
quit(1)
|
||
|
||
|
||
func _check(cond: bool, label: String) -> void:
|
||
checks += 1
|
||
if cond:
|
||
print(" ok: %s" % label)
|
||
else:
|
||
failures += 1
|
||
print(" FAIL: %s" % label)
|
||
|
||
|
||
func _find_viewport_container(node: Node) -> SubViewportContainer:
|
||
if node is SubViewportContainer:
|
||
return node as SubViewportContainer
|
||
for child in node.get_children():
|
||
var found := _find_viewport_container(child)
|
||
if found != null:
|
||
return found
|
||
return null
|
||
|
||
|
||
func _tool() -> Node:
|
||
return main.get("test_start_tool")
|
||
|
||
|
||
func _marker() -> Node3D:
|
||
var t := _tool()
|
||
if t == null:
|
||
return null
|
||
return t.get("_marker")
|
||
|
||
|
||
## --- 1: Setzen/Löschen ändert die .svmap nicht ------------------------------
|
||
func _test_svmap_unchanged() -> void:
|
||
print("Testgruppe: Setzen/Löschen ändert die .svmap NICHT (Byte-Vergleich)")
|
||
var path := "user://test_playtest_spawn_a.svmap"
|
||
var r1 := SvmapIO.save_to_file(path, main.get("doc"))
|
||
_check(bool(r1.get("ok")), "Map vor dem Setzen speicherbar (Validator ok)")
|
||
var bytes_before := FileAccess.get_file_as_bytes(path)
|
||
var doc_before := str(main.get("doc"))
|
||
var undo_before: bool = main.get("history").call("can_undo")
|
||
|
||
_tool().call("demo_set", 20.0, -30.0)
|
||
_check(main.get("test_start_point") != null, "Test-Start gesetzt (main.test_start_point)")
|
||
var r2 := SvmapIO.save_to_file(path, main.get("doc"))
|
||
_check(bool(r2.get("ok")), "Map nach dem Setzen speicherbar")
|
||
var bytes_after_set := FileAccess.get_file_as_bytes(path)
|
||
_check(bytes_after_set == bytes_before, "Setzen: .svmap byte-identisch")
|
||
_check(str(main.get("doc")) == doc_before, "Setzen: Dokument unverändert")
|
||
_check(not main.get("history").call("can_undo"), "Setzen: KEIN Undo-Eintrag")
|
||
|
||
main.call("_test_start_delete")
|
||
_check(main.get("test_start_point") == null, "Test-Start gelöscht")
|
||
var bytes_after_del := FileAccess.get_file_as_bytes(path)
|
||
_check(bytes_after_del == bytes_before, "Löschen: .svmap byte-identisch")
|
||
_check(str(main.get("doc")) == doc_before, "Löschen: Dokument unverändert")
|
||
DirAccess.remove_absolute(ProjectSettings.globalize_path(path))
|
||
|
||
|
||
## --- 2: Höhe = Terrainhöhe an der Stelle (Toleranz 1 cm) --------------------
|
||
func _test_height_snaps_to_terrain() -> void:
|
||
print("Testgruppe: Höhe = Terrainhöhe an der Stelle (Toleranz 1 cm)")
|
||
var doc: Dictionary = main.get("doc")
|
||
_tool().call("demo_set", 30.0, 5.0)
|
||
var expected := SvmapDocument.terrain_height_at(doc, 30.0, 5.0)
|
||
var marker := _marker()
|
||
_check(marker != null and marker.visible, "Marker sichtbar nach dem Setzen")
|
||
_check(absf(marker.position.y - expected) <= 0.01,
|
||
"Marker-Höhe == Terrainhöhe (%.3f == %.3f, Δ %.4f m)" % [
|
||
marker.position.y, expected, absf(marker.position.y - expected)])
|
||
_check(absf(marker.position.x - 30.0) < 0.001 and absf(marker.position.z - 5.0) < 0.001,
|
||
"Marker x/z exakt am Klickpunkt")
|
||
# Klick auf ein Objekt: Punkt auf dem Boden DARUNTER (gleiche Höhe-Regel).
|
||
_tool().call("demo_set", 10.0, 0.0, "auf dem Boden unter dem Objekt")
|
||
var expected_obj := SvmapDocument.terrain_height_at(doc, 10.0, 0.0)
|
||
_check(absf(marker.position.y - expected_obj) <= 0.01,
|
||
"Objekt-Treffer: Marker auf dem Boden DARUNTER (%.3f == %.3f)" % [
|
||
marker.position.y, expected_obj])
|
||
var status: String = main.status_label.text
|
||
_check(status.contains("Boden darunter") or status.contains("unter dem Objekt"),
|
||
"Statuszeile meldet Klartext „unter dem Objekt“: '%s'" % status)
|
||
main.call("_test_start_delete")
|
||
|
||
|
||
## --- 3: Fallback-Reihenfolge -------------------------------------------------
|
||
func _test_fallback_order() -> void:
|
||
print("Testgruppe: Fallback-Reihenfolge (Test-Start → Team-Spawn → Mitte)")
|
||
var doc: Dictionary = main.get("doc")
|
||
# a) Test-Start gewinnt.
|
||
var r: Dictionary = TestStart.resolve_start(doc, Vector3(33.0, 0.0, -44.0))
|
||
_check(str(r.get("quelle")) == "test", "Fallback 1: gesetzter Test-Start gewinnt")
|
||
_check(absf((r.get("pos") as Vector3).x - 33.0) < 0.001
|
||
and absf((r.get("pos") as Vector3).z + 44.0) < 0.001,
|
||
"Fallback 1: Position == Test-Start (x/z)")
|
||
_check(absf((r.get("pos") as Vector3).y - SvmapDocument.terrain_height_at(doc, 33.0, -44.0)) <= 0.01,
|
||
"Fallback 1: Höhe automatisch vom Terrain")
|
||
# b) Kein Test-Start → erster Team-Spawnpunkt (Team Blau = „0“).
|
||
r = TestStart.resolve_start(doc, null)
|
||
_check(str(r.get("quelle")) == "team", "Fallback 2: erster Team-Spawnpunkt")
|
||
_check(absf((r.get("pos") as Vector3).x + 20.0) < 0.001,
|
||
"Fallback 2: Position == Team-Blau-Spawn A1 (-20, 0)")
|
||
# c) Weder noch → Kartenmitte.
|
||
var ohne: Dictionary = doc.duplicate(true)
|
||
ohne["spawn_zones"] = {}
|
||
r = TestStart.resolve_start(ohne, null)
|
||
_check(str(r.get("quelle")) == "mitte", "Fallback 3: Kartenmitte")
|
||
var bmin: Array = doc.get("bounds", {}).get("min", [])
|
||
var bmax: Array = doc.get("bounds", {}).get("max", [])
|
||
_check(absf((r.get("pos") as Vector3).x - (float(bmin[0]) + float(bmax[0])) * 0.5) < 0.001,
|
||
"Fallback 3: Mitte == Bounds-Mitte")
|
||
# Klartext-Hinweise je Stufe.
|
||
_check(str(r.get("status")).length() > 0 and str(TestStart.resolve_start(doc, Vector3.ZERO)["status"]).length() > 0,
|
||
"Jede Fallback-Stufe liefert einen Klartext-Hinweis")
|
||
|
||
|
||
## --- 4: Nach Neustart wiederhergestellt (Sidecar) ----------------------------
|
||
func _test_sidecar_roundtrip() -> void:
|
||
print("Testgruppe: Sidecar — nach Neustart wiederhergestellt")
|
||
var pfad := "/tmp/scopedvr_test_karte.svmap"
|
||
TestStartStore.save_point(pfad, Vector3(12.5, 0.0, -7.25))
|
||
var loaded: Variant = TestStartStore.load_point(pfad)
|
||
_check(loaded != null, "Sidecar: Punkt gespeichert + lesbar")
|
||
if loaded != null:
|
||
var v := loaded as Vector3
|
||
_check(absf(v.x - 12.5) < 0.001 and absf(v.z + 7.25) < 0.001,
|
||
"Sidecar: x/z exakt (%.2f, %.2f)" % [v.x, v.z])
|
||
# editor-Flow: setzen → main verliert den Zustand („Neustart") → reload holt ihn zurück.
|
||
_tool().call("demo_set", 3.0, 4.0)
|
||
main.set("test_start_point", null)
|
||
main.call("_test_start_reload")
|
||
var p: Variant = main.get("test_start_point")
|
||
_check(p != null, "Neustart-Simulation: Punkt aus dem Sidecar wieder da")
|
||
if p != null:
|
||
var pv := p as Vector3
|
||
_check(absf(pv.x - 3.0) < 0.001 and absf(pv.z - 4.0) < 0.001,
|
||
"Neustart-Simulation: gleiche Position (%.1f, %.1f)" % [pv.x, pv.z])
|
||
main.call("_test_start_delete")
|
||
_check(TestStartStore.load_point("") == null, "Löschen entfernt den Sidecar-Eintrag")
|
||
|
||
|
||
## --- 6: Marker existiert nur bei gesetztem Punkt ------------------------------
|
||
func _test_marker_visibility() -> void:
|
||
print("Testgruppe: Marker existiert nur bei gesetztem Punkt")
|
||
var marker := _marker()
|
||
_check(marker != null and not marker.visible, "Ohne Punkt: Marker unsichtbar")
|
||
_tool().call("demo_set", -5.0, 8.0)
|
||
_check(marker.visible, "Mit Punkt: Marker sichtbar")
|
||
main.call("_test_start_delete")
|
||
_check(not marker.visible, "Nach Löschen: Marker wieder unsichtbar")
|
||
|
||
|
||
## --- 6b: Taste T schaltet das Werkzeug im Editor um ---------------------------
|
||
func _test_t_key_toggles_tool() -> void:
|
||
print("Testgruppe: Taste T schaltet das Test-Start-Werkzeug um")
|
||
_check(str(main.get("tool_mode")) == "objects", "Start im Objekt-Werkzeug (Vorbedingung)")
|
||
main._unhandled_key_input(_press_key(KEY_T))
|
||
_check(str(main.get("tool_mode")) == "teststart", "T: Test-Start-Werkzeug an")
|
||
var btn: Button = main.get("test_start_btn")
|
||
_check(btn != null and btn.button_pressed, "Toolbar-Knopf leuchtet mit")
|
||
var panel: Control = main.get("test_start_panel")
|
||
_check(panel != null and panel.visible, "Panel (Info + Löschen) sichtbar")
|
||
main._unhandled_key_input(_press_key(KEY_T))
|
||
_check(str(main.get("tool_mode")) == "objects", "T nochmal: zurück zum Objekt-Werkzeug")
|
||
_check(not panel.visible, "Panel wieder zu")
|
||
# ESC im Werkzeug beendet es, der Punkt bleibt.
|
||
main._unhandled_key_input(_press_key(KEY_T))
|
||
_tool().call("demo_set", 1.0, 2.0)
|
||
main._unhandled_key_input(_press_key(KEY_ESCAPE))
|
||
_check(str(main.get("tool_mode")) == "objects", "ESC: Werkzeug beendet")
|
||
_check(main.get("test_start_point") != null, "ESC: gesetzter Test-Start bleibt")
|
||
main.call("_test_start_delete")
|
||
|
||
|
||
## --- 7: Test-Start taucht in keiner Spawn-Zone/Analyse auf --------------------
|
||
func _test_not_a_game_spawn() -> void:
|
||
print("Testgruppe: Kein Spiel-Spawnpunkt (Spawn-Zonen/Analyse unberührt)")
|
||
var doc: Dictionary = main.get("doc")
|
||
var zones_before := str(doc.get("spawn_zones", {}))
|
||
var check_before := SpawnOps.ready_report(doc, main.get("manifest"))
|
||
_tool().call("demo_set", 44.0, 44.0)
|
||
_check(str(doc.get("spawn_zones", {})) == zones_before,
|
||
"Setzen: spawn_zones im Dokument byte-identisch")
|
||
var check_after := SpawnOps.ready_report(doc, main.get("manifest"))
|
||
_check(str(check_after) == str(check_before), "Setzen: Map-Check-Ergebnis unverändert")
|
||
var live_before := SpawnOps.live_status(doc, main.get("manifest"))
|
||
_tool().call("demo_set", -44.0, 33.0)
|
||
_check(SpawnOps.live_status(doc, main.get("manifest")) == live_before,
|
||
"Weiteres Setzen: Live-Spawn-Status unverändert")
|
||
main.call("_test_start_delete")
|
||
_check(str(doc.get("spawn_zones", {})) == zones_before,
|
||
"Löschen: spawn_zones im Dokument byte-identisch")
|
||
|
||
|
||
## --- 5: T im Testlauf versetzt genau dorthin (Augenhöhe, Kollision) ----------
|
||
func _test_playtest_teleport() -> void:
|
||
print("Testgruppe: T im Testlauf — versetzt genau dorthin, Augenhöhe, Kollision")
|
||
main.set("test_start_point", null)
|
||
main.call("_test_start_reload")
|
||
main.call("_on_test_run")
|
||
await process_frame
|
||
var pt: Node = main.get("playtest")
|
||
_check(pt != null and bool(pt.get("active")), "Testlauf läuft (Vorbedingung)")
|
||
if pt == null:
|
||
return
|
||
var body: Node3D = pt.get("body")
|
||
var doc: Dictionary = main.get("doc")
|
||
|
||
# a) Fadenkreuz-Treffer: Blick 45° nach vorn-unten. Das Ramp-Terrain hängt
|
||
# nur von x ab — der Strahl (yaw 0) bleibt auf konstantem x, also ist
|
||
# die Ziel-Höhe analytisch exakt.
|
||
body.set("yaw_deg", 0.0) # Blick nach -Z
|
||
body.set("pitch_deg", -45.0)
|
||
body.call("_apply_look") # yaw/pitch erst durch _apply_look auf die Kamera
|
||
var cam: Camera3D = body.get("look_cam")
|
||
var eye: Vector3 = cam.global_position # Kapsel-Mitte + EYE_HEIGHT (0.65)
|
||
var dir: Vector3 = cam.global_transform.basis * Vector3(0.0, 0.0, -1.0)
|
||
_check(absf(dir.y + 0.7071) < 0.01, "Kamera-Blick zeigt 45° nach unten (dir.y ≈ -0.707)")
|
||
var h0: float = body.call("terrain_height", eye.x, eye.z)
|
||
var th := absf((eye.y - h0) / dir.y)
|
||
var expected := eye + dir * th
|
||
main._unhandled_key_input(_press_key(KEY_T)) # ECHTER T-Druck (main-Verdrahtung)
|
||
await process_frame
|
||
await physics_frame
|
||
var np := body.position
|
||
_check(absf(np.x - expected.x) < 0.15 and absf(np.z - expected.z) < 0.15,
|
||
"Teleport-Ziel == Fadenkreuz-Punkt (%.2f,%.2f ≈ %.2f,%.2f)" % [
|
||
np.x, np.z, expected.x, expected.z])
|
||
var ground_here: float = body.call("terrain_height", np.x, np.z)
|
||
_check(absf((np.y - PlaytestBody.HALF_H) - ground_here) <= 0.05,
|
||
"Fusspunkt auf Terrainhöhe (Δ %.4f m)" % absf((np.y - PlaytestBody.HALF_H) - ground_here))
|
||
_check(absf(float(cam.position.y) - 0.65) < 0.001,
|
||
"Kamera-Offset 0.65 m über Kapsel-Mitte (identisch TestmapPlayer)")
|
||
var stored: Variant = main.get("test_start_point")
|
||
_check(stored != null and absf((stored as Vector3).x - np.x) < 0.01
|
||
and absf((stored as Vector3).z - np.z) < 0.01,
|
||
"T hat den Test-Start AUF den Fadenkreuz-Punkt gesetzt")
|
||
var msg: String = (pt.get("hud_msg") as Label).text
|
||
_check(msg.contains("Test-Start"), "HUD meldet Klartext: '%s'" % msg)
|
||
|
||
# b) Kollision: hohe Wand vor dem Spieler (echter StaticBody in der
|
||
# Physik-Welt), flacher Blick (−10°) trifft die Wand → ABGELEHNT
|
||
# (Kapsel am Zielpunkt nicht frei — wie im Spiel).
|
||
var welt: Node3D = main.get("world3d")
|
||
var vor := Vector3(body.position)
|
||
var wand := StaticBody3D.new()
|
||
var wcs := CollisionShape3D.new()
|
||
var wshape := BoxShape3D.new()
|
||
wshape.size = Vector3(4.0, 20.0, 0.6)
|
||
wcs.shape = wshape
|
||
wand.add_child(wcs)
|
||
wand.position = Vector3(vor.x, 5.0, vor.z - 2.7)
|
||
welt.add_child(wand)
|
||
body.set("yaw_deg", 0.0)
|
||
body.set("pitch_deg", -10.0)
|
||
body.call("_apply_look")
|
||
main._unhandled_key_input(_press_key(KEY_T))
|
||
await process_frame
|
||
await physics_frame
|
||
_check(body.position.distance_to(vor) < 0.001, "Im Objekt: Teleport abgelehnt, Position unverändert")
|
||
var blocked_msg: String = (pt.get("hud_msg") as Label).text
|
||
_check(blocked_msg.contains("nicht landen") or blocked_msg.contains("Weg"),
|
||
"Abgelehnt: Klartext im HUD: '%s'" % blocked_msg)
|
||
wand.queue_free()
|
||
await physics_frame
|
||
|
||
# c) Kisten-Dach: echter Kisten-StaticBody → oben drauf landen (Boden
|
||
# DARAUFD, nie im Objekt). Strahl von oben senkrecht auf die Kiste.
|
||
var cground: float = body.call("terrain_height", np.x + 5.0, np.z)
|
||
var kiste := StaticBody3D.new()
|
||
var ccs := CollisionShape3D.new()
|
||
var cshape := BoxShape3D.new()
|
||
cshape.size = Vector3(1.0, 1.0, 1.0)
|
||
ccs.shape = cshape
|
||
kiste.add_child(ccs)
|
||
kiste.position = Vector3(np.x + 5.0, cground + 0.5, np.z)
|
||
welt.add_child(kiste)
|
||
var roof: Dictionary = body.call("teleport_to_ground",
|
||
Vector3(np.x + 5.0, cground + 3.0, np.z), Vector3(0.0, -1.0, 0.0))
|
||
_check(bool(roof.get("ok")) and absf(float(roof.get("ground")) - (cground + 1.0)) < 0.01,
|
||
"Kisten-Dach: auf der Decke gelandet (ground %.2f == %.2f)" % [
|
||
float(roof.get("ground")), cground + 1.0])
|
||
kiste.queue_free()
|
||
await physics_frame
|
||
|
||
# d) ESC: zurück, Editor-Zustand intakt (Punkt bleibt gesetzt).
|
||
var doc_before := str(doc)
|
||
pt.call("handle_escape")
|
||
await process_frame
|
||
_check(main.get("playtest") == null, "ESC: zurück im Editor")
|
||
_check(str(main.get("doc")) == doc_before, "ESC: Dokument byte-identisch")
|
||
_check(main.get("test_start_point") != null, "ESC: gesetzter Test-Start bleibt")
|
||
main.call("_test_start_delete")
|
||
|
||
|
||
func _press_key(code: Key) -> InputEventKey:
|
||
var key := InputEventKey.new()
|
||
key.keycode = code
|
||
key.physical_keycode = code
|
||
key.pressed = true
|
||
return key
|