T3 t_aaff5dcc: Komfort im Testlauf — Fliegen/God-Mode (G, Noclip+Blickrichtung), Spawn-Sprung 1-4 (exakt, Terrain-Hoehe), HUD-Toggle H + Blick/Hoehe, Achsen-Linien X, Hilfe F1, Maus-Empfindlichkeit (PlaytestSettings + Einstellungen-Panel via Registry, main.gd unangetastet); Suite tests/test_playtest_comfort (51 Checks), test_ui_layout Tabs registry-getrieben
This commit is contained in:
parent
ab173ac8aa
commit
4fb883a44c
12 changed files with 822 additions and 5 deletions
|
|
@ -46,6 +46,10 @@ var mouse_sens: float = DEFAULT_MOUSE_SENS
|
|||
|
||||
## Map-Dokument (nur LESEND — Terrain-Höhenabfrage)
|
||||
var doc: Dictionary = {}
|
||||
## Fliegen / God-Mode (Karte t_aaff5dcc, Taste G): KEINE Gravitation, Bewegung
|
||||
## in Blickrichtung (inkl. Nickwinkel), Space = steigen, Kollision aus
|
||||
## (Noclip — der Tester kommt auch über Wände/Hügel schnell ans Ziel).
|
||||
var fly_mode := false
|
||||
## Vorgecachte Hindernis-AABBs (Weltkoordinaten) — von PlaytestMode gefüllt
|
||||
var obstacles: Array = [] # Array[AABB]
|
||||
## Höhenfeld-Cache (Zeile: res*res Meter) — von PlaytestMode via set_terrain()
|
||||
|
|
@ -84,6 +88,16 @@ func _apply_look() -> void:
|
|||
look_cam.position = Vector3(0.0, eye_height, 0.0)
|
||||
|
||||
|
||||
## Volle 3D-Blickrichtung (Karte t_aaff5dcc): -Z um Pitch (X) und Yaw (Y)
|
||||
## gedreht — genau die Blickachse der Kamera. Fliegen bewegt entlang DIESEM
|
||||
## Vektor (Blick nach unten = abwärts fliegen), Gehen nur horizontal.
|
||||
func view_direction() -> Vector3:
|
||||
var yaw_rad := deg_to_rad(yaw_deg)
|
||||
var pitch_rad := deg_to_rad(pitch_deg)
|
||||
return Vector3(-sin(yaw_rad) * cos(pitch_rad), sin(pitch_rad),
|
||||
-cos(yaw_rad) * cos(pitch_rad))
|
||||
|
||||
|
||||
## Eine Bewegungs-Schritt (physics frame) mit EXPLIZITEM Input ausführen.
|
||||
## input = {fwd: bool, back: bool, left: bool, right: bool, run: bool,
|
||||
## jump: bool} — die echte _physics_process baut das aus den
|
||||
|
|
@ -110,6 +124,25 @@ func step(delta: float, input: Dictionary) -> Dictionary:
|
|||
if move.length_squared() > 0.0:
|
||||
move = move.normalized()
|
||||
|
||||
# --- Fliegen / God-Mode (Karte t_aaff5dcc, Taste G) -----------------------
|
||||
# Keine Gravitation, Bewegung in der VOLLEN Blickrichtung (Blick nach
|
||||
# unten = abwärts fliegen), Space = zusätzlich steigen, Kollision AUS
|
||||
# (Noclip — schnell über die Map, auch über Wände/Hügel hinweg).
|
||||
if fly_mode:
|
||||
var fly_speed := move_speed * (run_mult if run else 1.0)
|
||||
var fly_yaw_rad := deg_to_rad(yaw_deg)
|
||||
var fly_right := Vector3(cos(fly_yaw_rad), 0.0, -sin(fly_yaw_rad))
|
||||
var wish3 := view_direction() * move.y + fly_right * move.x
|
||||
if jump:
|
||||
wish3 += Vector3.UP
|
||||
if wish3.length_squared() > 0.0:
|
||||
wish3 = wish3.normalized()
|
||||
velocity = wish3 * fly_speed
|
||||
position += velocity * delta
|
||||
on_floor = false
|
||||
_apply_look()
|
||||
return {"pos": position, "vel": velocity, "on_floor": on_floor}
|
||||
|
||||
# Welt-Richtung aus dem Blickwinkel (y-only, keine Sprung-Schräglage)
|
||||
var yaw_rad := deg_to_rad(yaw_deg)
|
||||
var forward := Vector3(-sin(yaw_rad), 0.0, -cos(yaw_rad))
|
||||
|
|
@ -359,3 +392,11 @@ func _sample(xi: int, zi: int) -> float:
|
|||
if idx < 0 or idx >= heights.size():
|
||||
return hmin
|
||||
return heights[idx]
|
||||
|
||||
|
||||
## Maus-Empfindlichkeit als Multiplikator setzen (Karte t_aaff5dcc, 0,1–5,0,
|
||||
## Standard 1,0): die Body-Basis (DEFAULT_MOUSE_SENS Grad/Pixel) wird mit dem
|
||||
## Multiplikator skaliert — jede PC/Maus-Kombination ist anders.
|
||||
func set_sensitivity(mult: float) -> void:
|
||||
var m := clampf(mult, PlaytestSettings.MIN_SENS, PlaytestSettings.MAX_SENS)
|
||||
mouse_sens = DEFAULT_MOUSE_SENS * m
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ var hud_pos: Label = null
|
|||
var hud_fps: Label = null
|
||||
var hud_hint: Label = null
|
||||
var hud_msg: Label = null # Klartext-Nachricht (z. B. Test-Start via T)
|
||||
var hud_dir: Label = null # Blickrichtung + Höhe (Karte t_aaff5dcc)
|
||||
## Hilfe-Overlay (F1, Karte t_aaff5dcc): alle Tasten im Klartext.
|
||||
var help_layer: CanvasLayer = null
|
||||
## Kartenmitte/Achsen-Linien (Taste X, Karte t_aaff5dcc — praktisch beim
|
||||
## gespiegelten Bauen, Karte M1): X-Rot/Z-Blau/Y-Grün durch die Kartenmitte.
|
||||
var _axis_node: Node3D = null
|
||||
## FPS-Smoothing (Anzeige, keine Physik)
|
||||
var _fps_smooth := 0.0
|
||||
## Ist der Lauf-Modus gerade aktiv? (Reentrancy-Schutz für enter/exit)
|
||||
|
|
@ -67,6 +73,9 @@ func enter(doc: Dictionary, view_builder: Node3D, start_override: Variant = null
|
|||
start = Vector3(sp.x, 0.0, sp.z)
|
||||
body.position = Vector3(start.x, body.terrain_height(start.x, start.z), start.z)
|
||||
add_child(body)
|
||||
# Maus-Empfindlichkeit aus den Einstellungen (Karte t_aaff5dcc): der
|
||||
# gespeicherte Multiplikator (0,1–5,0) skaliert die Body-Basis.
|
||||
body.call("set_sensitivity", PlaytestSettings.load_sens())
|
||||
|
||||
_build_hud()
|
||||
exit_requested.connect(_on_exit_requested, CONNECT_ONE_SHOT)
|
||||
|
|
@ -87,6 +96,12 @@ func exit() -> void:
|
|||
if hud != null:
|
||||
hud.queue_free()
|
||||
hud = null
|
||||
if help_layer != null:
|
||||
help_layer.queue_free()
|
||||
help_layer = null
|
||||
if _axis_node != null:
|
||||
_axis_node.queue_free()
|
||||
_axis_node = null
|
||||
|
||||
|
||||
func _on_exit_requested() -> void:
|
||||
|
|
@ -171,9 +186,10 @@ func _build_hud() -> void:
|
|||
hud_fps = Label.new()
|
||||
hud_hint = Label.new()
|
||||
hud_msg = Label.new()
|
||||
hud_hint.text = "WASD + Maus · Shift = rennen · Space = springen · T = zum Blickpunkt versetzen · ESC = zurück zum Editor"
|
||||
hud_dir = Label.new()
|
||||
hud_hint.text = "WASD + Maus · Shift = rennen · Space = springen/steigen · G = Fliegen · 1-4 = Spawnpunkt · T = Test-Start · H = HUD · X = Achsen · F1 = Hilfe · ESC = zurück zum Editor"
|
||||
hud_msg.text = ""
|
||||
for l: Label in [hud_pos, hud_fps, hud_hint, hud_msg]:
|
||||
for l: Label in [hud_pos, hud_dir, hud_fps, hud_hint, hud_msg]:
|
||||
l.add_theme_font_size_override("font_size", 16)
|
||||
l.add_theme_color_override("font_color", Color(0.55, 0.8, 1.0))
|
||||
vbox.add_child(l)
|
||||
|
|
@ -218,6 +234,9 @@ func _process(delta: float) -> void:
|
|||
_fps_smooth = lerpf(_fps_smooth, 1.0 / maxf(delta, 0.0001), 0.1)
|
||||
if hud_pos != null:
|
||||
hud_pos.text = "Pos: %.1f, %.1f, %.1f" % [body.position.x, body.position.y, body.position.z]
|
||||
if hud_dir != null:
|
||||
hud_dir.text = "Blick: %.0f° / %.0f° · Höhe: %.1f m" % [
|
||||
float(body.get("yaw_deg")), float(body.get("pitch_deg")), body.position.y]
|
||||
if hud_fps != null:
|
||||
hud_fps.text = "FPS: %d" % int(round(_fps_smooth))
|
||||
|
||||
|
|
@ -232,3 +251,203 @@ func wants_capture() -> bool:
|
|||
func handle_escape() -> void:
|
||||
if active and body != null:
|
||||
exit_requested.emit()
|
||||
|
||||
|
||||
# ------------------------------------------------- Komfort (t_aaff5dcc) -----
|
||||
|
||||
## Tasten im Lauf-Modus (Karte t_aaff5dcc): G = Fliegen/God-Mode, 1–4 =
|
||||
## Sprung zu Spawnpunkt, H = HUD an/aus, F1 = Hilfe, X = Achsen. ESC/T bleiben
|
||||
## bewusst bei main.gd (Esc-Kette + Test-Start-Vertrag). Die Editor-Tasten
|
||||
## sind im Lauf-Modus eingefroren (main gibt hier nichts anderes durch).
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not active or body == null:
|
||||
return
|
||||
var key_evt := event as InputEventKey
|
||||
if key_evt == null or not key_evt.is_pressed() or key_evt.echo:
|
||||
return
|
||||
var key := key_evt.keycode
|
||||
var handled := true
|
||||
match key:
|
||||
KEY_G:
|
||||
toggle_fly()
|
||||
KEY_1:
|
||||
jump_to_spawn(1)
|
||||
KEY_2:
|
||||
jump_to_spawn(2)
|
||||
KEY_3:
|
||||
jump_to_spawn(3)
|
||||
KEY_4:
|
||||
jump_to_spawn(4)
|
||||
KEY_H:
|
||||
toggle_hud()
|
||||
KEY_F1:
|
||||
toggle_help()
|
||||
KEY_X:
|
||||
toggle_axis()
|
||||
_:
|
||||
handled = false
|
||||
if handled:
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
## Empfindlichkeit live setzen (aus dem Einstellungen-Panel, Registry-Weg —
|
||||
## main.gd bleibt unangetastet). mult = Multiplikator 0,1–5,0.
|
||||
func apply_sensitivity(mult: float) -> void:
|
||||
if body != null:
|
||||
body.call("set_sensitivity", mult)
|
||||
|
||||
|
||||
## Fliegen / God-Mode umschalten (Taste G). Rückgabe: neuer Zustand.
|
||||
func toggle_fly() -> bool:
|
||||
if body == null:
|
||||
return false
|
||||
var new_state: bool = not bool(body.get("fly_mode"))
|
||||
body.set("fly_mode", new_state)
|
||||
body.set("velocity", Vector3.ZERO) # kein Fall-Übernehmen aus dem Gang
|
||||
if new_state:
|
||||
show_message("Fliegen AN (God-Mode): keine Gravitation — Blickrichtung fliegen, Space = steigen, G = aus.")
|
||||
else:
|
||||
show_message("Fliegen AUS — wieder mit Gravitation.")
|
||||
return new_state
|
||||
|
||||
|
||||
## Spawnpunkt-Punkt für Taste index (1 = Team A/Zone 0, 2 = Team B/Zone 1,
|
||||
## 3/4 = weitere Zonen 2/3): erster Punkt der Zone. Rückgabe {} = keine Zone.
|
||||
## pos = [x,y,z] aus dem Dokument.
|
||||
func _spawn_point_for(index: int) -> Dictionary:
|
||||
if body == null:
|
||||
return {}
|
||||
var zones: Dictionary = (body.get("doc") as Dictionary).get("spawn_zones", {})
|
||||
var zone: Variant = zones.get(str(index - 1))
|
||||
if not (zone is Dictionary):
|
||||
return {}
|
||||
var points: Array = (zone as Dictionary).get("points", [])
|
||||
if points.is_empty() or not (points[0] is Dictionary):
|
||||
return {}
|
||||
var p := points[0] as Dictionary
|
||||
var pos: Array = p.get("pos", [])
|
||||
if pos.size() < 3:
|
||||
return {}
|
||||
return {"pos": Vector3(float(pos[0]), float(pos[1]), float(pos[2])),
|
||||
"label": str((zone as Dictionary).get("label", "Zone %d" % index))}
|
||||
|
||||
|
||||
## Sprung zu einem Spawnpunkt (Tasten 1–4): exakt auf den Punkt gesetzt
|
||||
## (x/z aus dem Dokument, Höhe vom Terrain — dieselbe Konvention wie der
|
||||
## Lauf-Modus-Start). Rückgabe true = gelandet.
|
||||
func jump_to_spawn(index: int) -> bool:
|
||||
var sp := _spawn_point_for(index)
|
||||
if sp.is_empty() or body == null:
|
||||
show_message("Kein Spawnpunkt für Taste %d — die Karte hat diese Zone nicht." % index)
|
||||
return false
|
||||
var target: Vector3 = sp["pos"]
|
||||
var y: float = body.terrain_height(target.x, target.z)
|
||||
body.set("position", Vector3(target.x, y, target.z))
|
||||
body.set("velocity", Vector3.ZERO)
|
||||
body.set("on_floor", true)
|
||||
show_message("Spawnpunkt %d = %s: x=%.1f z=%.1f (Höhe %.1f) — Blickrichtung steht im HUD." % [
|
||||
index, str(sp["label"]), target.x, target.z, y])
|
||||
return true
|
||||
|
||||
|
||||
## HUD (Position/Blick/FPS/Hinweis) an/aus (Taste H). Rückgabe: sichtbar?
|
||||
func toggle_hud() -> bool:
|
||||
if hud == null:
|
||||
return false
|
||||
hud.visible = not hud.visible
|
||||
if hud.visible:
|
||||
show_message("HUD an.") # aus: Meldung wäre selbst unsichtbar
|
||||
return hud.visible
|
||||
|
||||
|
||||
## Hilfe-Overlay an/aus (Taste F1). Rückgabe: sichtbar?
|
||||
func toggle_help() -> bool:
|
||||
if help_layer == null:
|
||||
_build_help()
|
||||
help_layer.visible = not help_layer.visible
|
||||
return help_layer.visible
|
||||
|
||||
|
||||
## Kartenmitte/Achsen-Linien an/aus (Taste X). Rückgabe: sichtbar?
|
||||
func toggle_axis() -> bool:
|
||||
if _axis_node == null:
|
||||
_build_axis()
|
||||
_axis_node.visible = not _axis_node.visible
|
||||
if _axis_node.visible:
|
||||
show_message("Kartenmitte/Achsen AN (X = aus): X-Rot, Z-Blau, Mitte Grün.")
|
||||
else:
|
||||
show_message("Kartenmitte/Achsen AUS.")
|
||||
return _axis_node.visible
|
||||
|
||||
|
||||
## Hilfe-Overlay (F1): Panel mit allen Tasten im Klartext, mittig unten.
|
||||
func _build_help() -> void:
|
||||
help_layer = CanvasLayer.new()
|
||||
help_layer.layer = 101
|
||||
var center := CenterContainer.new()
|
||||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
center.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
help_layer.add_child(center)
|
||||
var panel := PanelContainer.new()
|
||||
center.add_child(panel)
|
||||
var margin := MarginContainer.new()
|
||||
margin.add_theme_constant_override("margin_left", 16)
|
||||
margin.add_theme_constant_override("margin_right", 16)
|
||||
margin.add_theme_constant_override("margin_top", 12)
|
||||
margin.add_theme_constant_override("margin_bottom", 12)
|
||||
panel.add_child(margin)
|
||||
var label := Label.new()
|
||||
label.text = "TESTLAUF — ALLE TASTEN
|
||||
WASD — bewegen · Maus — umsehen
|
||||
Shift — rennen · Space — springen / im Flug steigen
|
||||
G — Fliegen (God-Mode) an/aus: keine Gravitation, Blickrichtung fliegen
|
||||
1 — Sprung zu Team A (Spawnpunkt) · 2 — Team B
|
||||
3 / 4 — weitere Spawnpunkte (Zonen 2/3)
|
||||
T — Test-Start auf den Blickpunkt setzen (sofort dort)
|
||||
H — HUD an/aus (Position, Blick, FPS, Höhe)
|
||||
X — Kartenmitte/Achsen ein/aus (X-Rot, Z-Blau)
|
||||
F1 — diese Hilfe · ESC — zurück zum Editor"
|
||||
label.add_theme_font_size_override("font_size", 18)
|
||||
margin.add_child(label)
|
||||
add_child(help_layer)
|
||||
help_layer.visible = false
|
||||
|
||||
|
||||
## Achsen-Linien durch die Kartenmitte (Taste X): X-Achse rot, Z-Achse blau,
|
||||
## senkrechte Mitte grün — beim gespiegelten Bauen (M1) auf einen Blick.
|
||||
func _build_axis() -> void:
|
||||
var doc: Dictionary = (body.get("doc") if body != null else {}) as Dictionary
|
||||
var bounds: Dictionary = doc.get("bounds", {})
|
||||
var mn: Array = bounds.get("min", [-50.0, -10.0, -50.0])
|
||||
var mx: Array = bounds.get("max", [50.0, 80.0, 50.0])
|
||||
var cx := (float(mn[0]) + float(mx[0])) * 0.5
|
||||
var cz := (float(mn[2]) + float(mx[2])) * 0.5
|
||||
var y0: float = body.terrain_height(cx, cz) if body != null else 0.0
|
||||
var mesh := ImmediateMesh.new()
|
||||
mesh.surface_begin(Mesh.PRIMITIVE_LINES)
|
||||
# X-Achse (rot): ganze Kartenbreite durch die Mitte.
|
||||
mesh.surface_set_color(Color(1.0, 0.25, 0.25))
|
||||
mesh.surface_add_vertex(Vector3(float(mn[0]), y0, cz))
|
||||
mesh.surface_add_vertex(Vector3(float(mx[0]), y0, cz))
|
||||
# Z-Achse (blau): ganze Kartentiefe durch die Mitte.
|
||||
mesh.surface_set_color(Color(0.3, 0.5, 1.0))
|
||||
mesh.surface_add_vertex(Vector3(cx, y0, float(mn[2])))
|
||||
mesh.surface_add_vertex(Vector3(cx, y0, float(mx[2])))
|
||||
# Senkrechte Mitte (grün): vom Terrain bis zur oberen Grenze.
|
||||
mesh.surface_set_color(Color(0.3, 1.0, 0.4))
|
||||
mesh.surface_add_vertex(Vector3(cx, y0, cz))
|
||||
mesh.surface_add_vertex(Vector3(cx, float(mx[1]), cz))
|
||||
mesh.surface_end()
|
||||
var mi := MeshInstance3D.new()
|
||||
mi.name = "PlaytestAchsen"
|
||||
mi.mesh = mesh
|
||||
# Vertexfarben sichtbar machen: unshaded + vertex_color_use_as_albedo
|
||||
# (ohne das ignoriert der Default-Material-Shader die Linien-Farben).
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mat.vertex_color_use_as_albedo = true
|
||||
mat.no_fog = true
|
||||
mi.material_override = mat
|
||||
mi.position = Vector3.ZERO
|
||||
_axis_node = mi
|
||||
add_child(_axis_node)
|
||||
|
|
|
|||
53
src/editor/playtest/playtest_settings.gd
Normal file
53
src/editor/playtest/playtest_settings.gd
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
extends RefCounted
|
||||
class_name PlaytestSettings
|
||||
## Einstellungen-Speicher für den Lauf-Modus (Karte t_aaff5dcc, T3 Komfort) —
|
||||
## bewusst NICHT im .svmap-Format und NICHT in main.gd: der Operator hat für
|
||||
## Einstellungen den Registry-Weg vorgegeben (Panel meldet sich in
|
||||
## src/ui/panels/registry.gd an), der Zustand lebt HIER als statischer Store.
|
||||
##
|
||||
## Ablage: user://scopedvr_playtest.cfg, Abschnitt [playtest].
|
||||
## Wert: Maus-Empfindlichkeit als Multiplikator (0,1–5,0, Standard 1,0) auf
|
||||
## die Body-Basis 0,25 Grad/Pixel — jede PC/Maus-Kombination ist anders.
|
||||
##
|
||||
## Statischer Cache: ein Lese-Vorgang pro Editor-Start; save_sens() hält den
|
||||
## Cache frisch. Tests können mit reset_cache() neu laden erzwingen.
|
||||
|
||||
const CFG_PATH := "user://scopedvr_playtest.cfg"
|
||||
const SECTION := "playtest"
|
||||
const KEY_SENS := "maus_empfindlichkeit"
|
||||
const DEFAULT_SENS := 1.0
|
||||
const MIN_SENS := 0.1
|
||||
const MAX_SENS := 5.0
|
||||
|
||||
## Cache (null = noch nicht gelesen). static var ist Godot 4.1+.
|
||||
static var _cache: Variant = null
|
||||
|
||||
|
||||
## Gespeicherte Empfindlichkeit lesen (geclampt auf 0,1–5,0), Standard 1,0.
|
||||
static func load_sens() -> float:
|
||||
if _cache != null:
|
||||
return float(_cache)
|
||||
var cfg := ConfigFile.new()
|
||||
var raw: Variant = DEFAULT_SENS
|
||||
if cfg.load(CFG_PATH) == OK:
|
||||
raw = cfg.get_value(SECTION, KEY_SENS, DEFAULT_SENS)
|
||||
var val := clampf(float(raw), MIN_SENS, MAX_SENS)
|
||||
_cache = val
|
||||
return val
|
||||
|
||||
|
||||
## Empfindlichkeit speichern (geclampt auf 0,1–5,0) und Cache frisch halten.
|
||||
## Rückgabe: der tatsächlich gespeicherte Wert.
|
||||
static func save_sens(value: float) -> float:
|
||||
var val := clampf(value, MIN_SENS, MAX_SENS)
|
||||
var cfg := ConfigFile.new()
|
||||
cfg.load(CFG_PATH) # Fehler → leere Config, nur unser Key wird angelegt.
|
||||
cfg.set_value(SECTION, KEY_SENS, val)
|
||||
cfg.save(CFG_PATH)
|
||||
_cache = val
|
||||
return val
|
||||
|
||||
|
||||
## Cache verwerfen (für Tests / frisches Neulesen von der Platte).
|
||||
static func reset_cache() -> void:
|
||||
_cache = null
|
||||
1
src/editor/playtest/playtest_settings.gd.uid
Normal file
1
src/editor/playtest/playtest_settings.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://1m7xfphtjvwd
|
||||
|
|
@ -38,6 +38,10 @@ const PANELS: Array = [
|
|||
"skript": "res://src/ui/panels/check_panel.gd"},
|
||||
{"id": "teststart", "titel": "Test-Start", "gruppe": "rechts", "order": 60,
|
||||
"skript": "res://src/ui/panels/test_start_panel.gd"},
|
||||
# T3 Komfort (Karte t_aaff5dcc): Maus-Empfindlichkeit u. a. — Registry-Weg
|
||||
# statt main.gd (Operator-Vorgabe 20.10.).
|
||||
{"id": "einstellungen", "titel": "Einstellungen", "gruppe": "rechts", "order": 70,
|
||||
"skript": "res://src/ui/panels/settings_panel.gd"},
|
||||
# --- links: Vorbereitet für U2 (Szenen-Baum); Bibliothek ist UNTEN ---
|
||||
{"id": "szene", "titel": "Map-Inhalt", "gruppe": "links", "order": 10,
|
||||
"skript": "res://src/ui/panels/scene_tree_panel.gd"},
|
||||
|
|
|
|||
43
src/ui/panels/settings_panel.gd
Normal file
43
src/ui/panels/settings_panel.gd
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
extends "res://src/ui/panels/editor_panel.gd"
|
||||
## Einstellungen-Bereich (rechte Spalte — Karte t_aaff5dcc, T3 Komfort).
|
||||
##
|
||||
## Registry-Weg (Operator-Vorgabe 20.10.): main.gd bleibt unangetastet, das
|
||||
## Modul meldet sich nur über src/ui/panels/registry.gd an. Der ZUSTAND lebt
|
||||
## in src/editor/playtest/playtest_settings.gd (PlaytestSettings, user://-cfg) —
|
||||
## dieses Panel zeigt/anpasst nur. Läuft gerade ein Testlauf, wird die neue
|
||||
## Maus-Empfindlichkeit SOFORT über main.playtest.apply_sensitivity wirksam.
|
||||
|
||||
var sens_box: SpinBox
|
||||
|
||||
|
||||
static func spec() -> Dictionary:
|
||||
return {"titel": "Einstellungen", "gruppe": "rechts", "order": 70}
|
||||
|
||||
|
||||
func panel_title() -> String:
|
||||
return "Einstellungen"
|
||||
|
||||
|
||||
func build() -> void:
|
||||
add_child(_header_label("Testlauf-Einstellungen"))
|
||||
|
||||
add_child(_field_label("Maus-Empfindlichkeit (0,1 – 5,0)"))
|
||||
sens_box = SpinBox.new()
|
||||
sens_box.min_value = PlaytestSettings.MIN_SENS
|
||||
sens_box.max_value = PlaytestSettings.MAX_SENS
|
||||
sens_box.step = 0.1
|
||||
sens_box.value = PlaytestSettings.load_sens()
|
||||
sens_box.custom_minimum_size = UIScale.v2(0, 36)
|
||||
sens_box.tooltip_text = "Multiplikator auf die Blick-Geschwindigkeit im Testlauf. 1,0 = Standard; kleine Werte = feinfühlig, große Werte = schnell."
|
||||
sens_box.value_changed.connect(_on_sens_changed)
|
||||
add_child(sens_box)
|
||||
|
||||
add_child(_separator())
|
||||
add_child(_muted_label("Gilt im Lauf-Modus („▶ Karte testen“). Standard 1,0 — jede PC-/Maus-Kombination ist anders. Wird automatisch gespeichert."))
|
||||
|
||||
|
||||
func _on_sens_changed(value: float) -> void:
|
||||
PlaytestSettings.save_sens(value)
|
||||
var pt: Variant = main.get("playtest")
|
||||
if pt != null and pt.has_method("apply_sensitivity"):
|
||||
pt.call("apply_sensitivity", value)
|
||||
1
src/ui/panels/settings_panel.gd.uid
Normal file
1
src/ui/panels/settings_panel.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bp0ii5exucqcy
|
||||
114
tests/probe_playtest_comfort.gd
Normal file
114
tests/probe_playtest_comfort.gd
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
extends SceneTree
|
||||
## WEGWERF-Renderprobe (Karte t_aaff5dcc, T3 Komfort im Testlauf) —
|
||||
## (a) Vogelperspektive per God-Mode: Fliegen an, 80 m über der Kartenmitte,
|
||||
## Blick nach unten, Achsen-Linien an (X-Rot/Z-Blau, Karte M1)
|
||||
## (b) Sprung zum Spawnpunkt: Taste 2 (Team Rot) — HUD zeigt Pos/Blick/Höhe
|
||||
## (c) Hilfe-Overlay (F1): alle Tasten im Klartext
|
||||
## Ausführen:
|
||||
## xvfb-run -a godot4 --path . --rendering-driver opengl3 -s res://tests/probe_playtest_comfort.gd
|
||||
## Beweise nach /opt/data/scopedvr/work/taaff5dcc_beweise/.
|
||||
|
||||
var main: Control
|
||||
var subvp: SubViewport
|
||||
|
||||
func _initialize() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
func _png(name: String) -> String:
|
||||
return "/opt/data/scopedvr/work/taaff5dcc_beweise/" + name
|
||||
|
||||
func _shot(name: String) -> void:
|
||||
await process_frame
|
||||
await process_frame
|
||||
var img := subvp.get_texture().get_image()
|
||||
img.save_png(_png(name))
|
||||
print("PROBE_PNG=%s (%dx%d)" % [_png(name), img.get_width(), img.get_height()])
|
||||
|
||||
func _find_subvp(node: Node) -> SubViewport:
|
||||
if node is SubViewport:
|
||||
return node as SubViewport
|
||||
for c in node.get_children():
|
||||
var f := _find_subvp(c)
|
||||
if f != null:
|
||||
return f
|
||||
return null
|
||||
|
||||
func _run() -> void:
|
||||
DirAccess.make_dir_recursive_absolute("/opt/data/scopedvr/work/taaff5dcc_beweise")
|
||||
var scene: PackedScene = load("res://scenes/main.tscn")
|
||||
main = scene.instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
await process_frame
|
||||
subvp = _find_subvp(main)
|
||||
var container: SubViewportContainer = subvp.get_parent()
|
||||
container.stretch = false
|
||||
subvp.size = Vector2i(1600, 900)
|
||||
await process_frame
|
||||
|
||||
# Test-Karte mit ein paar Kisten als Landmarken.
|
||||
var doc: Dictionary = SvmapDocument.create_new("T3-Komfort-Probe", 512.0, "mittag")
|
||||
doc["objects"] = [
|
||||
{"id": "crate_wood", "pos": [30.0, 0.0, -40.0], "scale": 1.0, "rot_y_deg": 0.0},
|
||||
{"id": "crate_wood", "pos": [-60.0, 0.0, 20.0], "scale": 1.0, "rot_y_deg": 0.0},
|
||||
]
|
||||
main.set("doc", doc)
|
||||
main.call("_refresh_all")
|
||||
await process_frame
|
||||
await process_frame
|
||||
|
||||
main.call("_on_test_run")
|
||||
await process_frame
|
||||
await process_frame
|
||||
var pt: Node = main.get("playtest")
|
||||
var body: Node3D = pt.get("body")
|
||||
print("ZAHLEN start: body=", body.position, " fly=", body.get("fly_mode"))
|
||||
|
||||
# --- (a) God-Mode: hoch über die Kartenmitte, Blick nach unten ------------
|
||||
pt.call("toggle_fly")
|
||||
pt.call("toggle_axis") # Kartenmitte/Achsen sichtbar (X-Rot/Z-Blau/Grün)
|
||||
var bounds: Dictionary = doc.get("bounds", {})
|
||||
var mn: Array = bounds.get("min", [-256.0, -10.0, -256.0])
|
||||
var mx: Array = bounds.get("max", [256.0, 80.0, 256.0])
|
||||
var cx := (float(mn[0]) + float(mx[0])) * 0.5
|
||||
var cz := (float(mn[2]) + float(mx[2])) * 0.5
|
||||
body.set("position", Vector3(cx, 80.0, cz))
|
||||
body.set("yaw_deg", 45.0)
|
||||
body.set("pitch_deg", -80.0)
|
||||
print("ZAHLEN (a) fly=", body.get("fly_mode"), " pos=", body.position,
|
||||
" axis_an=", pt.call("toggle_axis") == false)
|
||||
await _shot("t3_a_vogelperspektive_godmode.png")
|
||||
|
||||
# --- (b) Sprung zum Spawnpunkt Team Rot (Taste 2) -------------------------
|
||||
pt.call("toggle_fly") # God-Mode AUS — dann zeigt die Meldung den Sprung
|
||||
var ok2 := bool(pt.call("jump_to_spawn", 2))
|
||||
print("ZAHLEN (b) jump_to_spawn(2)=", ok2, " pos=", body.position,
|
||||
" terrain_dort=", body.terrain_height(body.position.x, body.position.z))
|
||||
body.set("yaw_deg", 127.0) # Blick Richtung Kartenmitte
|
||||
body.set("pitch_deg", 3.0)
|
||||
print("ZAHLEN (b) fly=", body.get("fly_mode"),
|
||||
" HUD pos='", (pt.get("hud_pos") as Label).text, "' dir='",
|
||||
(pt.get("hud_dir") as Label).text, "' msg='",
|
||||
(pt.get("hud_msg") as Label).text, "'")
|
||||
await _shot("t3_b_spawnpunkt_team_rot.png")
|
||||
|
||||
# --- (c) Hilfe-Overlay (F1) -----------------------------------------------
|
||||
var help_vor := bool(pt.call("toggle_help"))
|
||||
print("ZAHLEN (c) F1 hilfe_an=", help_vor)
|
||||
await _shot("t3_c_hilfe_f1.png")
|
||||
|
||||
# --- Aufräumen: zurück in den Editor --------------------------------------
|
||||
main._unhandled_key_input(_make_key(KEY_ESCAPE))
|
||||
await process_frame
|
||||
await process_frame
|
||||
print("ZAHLEN (d) playtest null=", main.get("playtest") == null,
|
||||
" panels=", main.get("ui_top_bar").visible)
|
||||
print("PROBE_FERTIG")
|
||||
quit(0)
|
||||
|
||||
func _make_key(code: Key) -> InputEventKey:
|
||||
var evt := InputEventKey.new()
|
||||
evt.keycode = code
|
||||
evt.physical_keycode = code
|
||||
evt.pressed = true
|
||||
return evt
|
||||
1
tests/probe_playtest_comfort.gd.uid
Normal file
1
tests/probe_playtest_comfort.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://rrp5fjepprgx
|
||||
333
tests/test_playtest_comfort.gd
Normal file
333
tests/test_playtest_comfort.gd
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
extends SceneTree
|
||||
## Editor-Test (headless): Karte t_aaff5dcc — T3 Komfort im Testlauf.
|
||||
## Ausführen: godot4 --headless --path . --import && godot4 --headless --path . --script res://tests/test_playtest_comfort.gd
|
||||
## Exit 0 = alles grün.
|
||||
##
|
||||
## Deckt ab (alles im Lauf-Modus, keine neuen Abhängigkeiten):
|
||||
## 1. Fliegen/God-Mode (Taste G → toggle_fly): Gravitation AUS, Bewegung in
|
||||
## Blickrichtung (inkl. Nickwinkel), Noclip durch Wände, Space = steigen,
|
||||
## Ausschalten → Gravitation wirkt wieder.
|
||||
## 2. Sprung zu Spawnpunkten (Taste 1–4 → jump_to_spawn): landet EXAKT auf
|
||||
## den Spawnpunkten der Karte (x/z aus dem Dokument, Höhe vom Terrain,
|
||||
## Toleranz ≤ 1 mm); fehlende Zone → abgelehnt, Position unverändert.
|
||||
## 3. HUD-Toggle (Taste H): Sichtbarkeit wechselt; HUD zeigt Blickrichtung
|
||||
## und Höhe.
|
||||
## 4. Maus-Empfindlichkeit: gespeichert + geladen (Clamp 0,1–5,0), live
|
||||
## angewandt (apply_sensitivity) und beim Start aus den Einstellungen
|
||||
## gelesen; Panel über Registry angemeldet (main.gd unangetastet).
|
||||
## 5. Rückkehr in den Editor weiterhin vollkommen sauber (Dokument +
|
||||
## Undo unverändert, Panels sichtbar, Achsen/Hilfe/HUD abgeräumt).
|
||||
|
||||
var failures := 0
|
||||
var checks := 0
|
||||
var main: Control
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_run_all.call_deferred()
|
||||
|
||||
|
||||
func _run_all() -> void:
|
||||
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
|
||||
|
||||
# Echte Map (Neu-Dialog-Konvention) + zwei weitere Spawn-Zonen für 3/4.
|
||||
var doc: Dictionary = SvmapDocument.create_new("Komfort-Karte", 512.0, "klar")
|
||||
var zones: Dictionary = doc.get("spawn_zones", {})
|
||||
zones["2"] = {"label": "Zone 3", "points": [{"id": "C1", "pos": [7.5, 0.0, -12.5]}]}
|
||||
zones["3"] = {"label": "Zone 4", "points": [{"id": "D1", "pos": [-13.5, 0.0, 21.5]}]}
|
||||
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")
|
||||
await process_frame
|
||||
|
||||
# Einstellungen auf bekannten Startwert (Standard).
|
||||
PlaytestSettings.reset_cache()
|
||||
PlaytestSettings.save_sens(1.0)
|
||||
|
||||
# Einstellungen-Panel ist über die Registry im Baum (Haupttest der Anbindung).
|
||||
var registry_script: GDScript = load("res://src/ui/panels/registry.gd")
|
||||
var entry: Dictionary = registry_script.entry("einstellungen")
|
||||
_check(not entry.is_empty(), "Registry: Eintrag 'einstellungen' vorhanden")
|
||||
var panel: Node = main.find_child("einstellungen", true, false)
|
||||
_check(panel != null, "Einstellungen-Panel im Baum (Registry-Weg, main.gd unangetastet)")
|
||||
if panel != null:
|
||||
_check(panel.get("sens_box") != null, "Einstellungen: Empfindlichkeits-Feld vorhanden")
|
||||
var box: SpinBox = panel.get("sens_box")
|
||||
_check(is_equal_approx(box.min_value, 0.1) and is_equal_approx(box.max_value, 5.0),
|
||||
"Einstellungen: Bereich 0,1–5,0 im Feld")
|
||||
_check(is_equal_approx(box.value, 1.0), "Einstellungen: Standard 1,0 vorgeblendet")
|
||||
|
||||
main.call("_on_test_run")
|
||||
await process_frame
|
||||
await process_frame
|
||||
|
||||
await _test_fly_godmode()
|
||||
await _test_spawn_jumps()
|
||||
await _test_hud_toggle()
|
||||
await _test_sensitivity()
|
||||
await _test_exit_restores_editor()
|
||||
|
||||
# Aufräumen: Standard-Empfindlichkeit für den echten Editor hinterlassen.
|
||||
PlaytestSettings.save_sens(1.0)
|
||||
_finish()
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
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 _pt() -> Node:
|
||||
return main.get("playtest")
|
||||
|
||||
|
||||
func _body() -> Node3D:
|
||||
var pt := _pt()
|
||||
if pt == null:
|
||||
return null
|
||||
return pt.get("body")
|
||||
|
||||
|
||||
func _key_event(code: Key) -> InputEventKey:
|
||||
var evt := InputEventKey.new()
|
||||
evt.keycode = code
|
||||
evt.physical_keycode = code
|
||||
evt.pressed = true
|
||||
return evt
|
||||
|
||||
|
||||
const NO_INPUT := {"fwd": false, "back": false, "left": false, "right": false,
|
||||
"run": false, "jump": false}
|
||||
|
||||
|
||||
## --- Test 1: Fliegen / God-Mode ---------------------------------------------
|
||||
func _test_fly_godmode() -> void:
|
||||
print("Testgruppe: Fliegen/God-Mode (G) — Gravitation aus, Blickrichtung, Noclip")
|
||||
var pt := _pt()
|
||||
if pt == null:
|
||||
_check(false, "PlaytestMode aktiv (Vorbedingung)")
|
||||
return
|
||||
var body: Node3D = _body()
|
||||
_check(not bool(body.get("fly_mode")), "Standard: Fliegen AUS")
|
||||
|
||||
# Taste G über den echten Dispatch-Weg (mode._unhandled_input).
|
||||
pt.call("_unhandled_input", _key_event(KEY_G))
|
||||
_check(bool(body.get("fly_mode")), "Taste G schaltet Fliegen AN (Dispatch)")
|
||||
|
||||
# Gravitation AUS: in der Luft schwebt der Spieler ohne Input.
|
||||
body.set("position", Vector3(body.position.x, 10.0, body.position.z))
|
||||
var y0: float = body.position.y
|
||||
for i in 30:
|
||||
body.call("step", 1.0 / 30.0, NO_INPUT)
|
||||
_check(absf(float(body.position.y) - y0) < 0.001,
|
||||
"Keine Gravitation: Höhe hält (10 m, %.3f nach 1 s)" % float(body.position.y))
|
||||
|
||||
# Noclip: Wand quer vor dem Spieler, Fliegen geht DURCH (kein Block).
|
||||
var start := Vector3(body.position)
|
||||
body.set("yaw_deg", 0.0) # Blick nach -Z
|
||||
body.get("obstacles").append(AABB(Vector3(start.x - 2.0, -5.0, start.z - 3.0),
|
||||
Vector3(4.0, 10.0, 0.6)))
|
||||
for i in 60:
|
||||
body.call("step", 1.0 / 30.0, {"fwd": true, "back": false, "left": false,
|
||||
"right": false, "run": false, "jump": false})
|
||||
var dist: float = start.distance_to(body.position)
|
||||
_check(dist > 3.5, "Fliegen geht durch die Wand (%.2f m Weg, Wand bei 2.4–3.0)" % dist)
|
||||
body.get("obstacles").clear()
|
||||
|
||||
# Bewegung in Blickrichtung: Blick 30° nach unten → Wegstrecke teilt sich
|
||||
# in -z und -y (sin30 = 0.5, cos30 ≈ 0.866; 4 m in 1 s).
|
||||
var start2 := Vector3(body.position)
|
||||
body.set("yaw_deg", 0.0)
|
||||
body.set("pitch_deg", -30.0)
|
||||
for i in 30:
|
||||
body.call("step", 1.0 / 30.0, {"fwd": true, "back": false, "left": false,
|
||||
"right": false, "run": false, "jump": false})
|
||||
var moved: Vector3 = body.position - start2
|
||||
_check(absf(moved.z + 3.46) < 0.05 and absf(moved.y + 2.0) < 0.05,
|
||||
"Blickrichtung inkl. Nickwinkel (Δz=%.2f, Δy=%.2f, Soll -3.46/-2.0)" % [moved.z, moved.y])
|
||||
|
||||
# Space = steigen.
|
||||
var y_up: float = body.position.y
|
||||
for i in 15:
|
||||
body.call("step", 1.0 / 30.0, {"fwd": false, "back": false, "left": false,
|
||||
"right": false, "run": false, "jump": true})
|
||||
_check(float(body.position.y) > y_up + 1.0,
|
||||
"Space steigt im Flug (+%.1f m)" % float(body.position.y - y_up))
|
||||
|
||||
# Ausschalten: Gravitation wirkt wieder (Fällen zum Terrain).
|
||||
pt.call("_unhandled_input", _key_event(KEY_G))
|
||||
_check(not bool(body.get("fly_mode")), "Zweites G schaltet Fliegen AUS")
|
||||
var y_fall: float = body.position.y
|
||||
var landed := false
|
||||
for i in 120:
|
||||
var state: Dictionary = body.call("step", 1.0 / 30.0, NO_INPUT)
|
||||
if bool(state["on_floor"]):
|
||||
landed = true
|
||||
break
|
||||
_check(landed and float(body.position.y) < y_fall,
|
||||
"Fliegen aus: Spieler fällt und landet (y=%.2f)" % float(body.position.y))
|
||||
var terrain_y: float = body.call("terrain_height", body.position.x, body.position.z)
|
||||
_check(is_equal_approx(float(body.position.y), terrain_y),
|
||||
"Landung auf Terrainhöhe (%.3f == %.3f)" % [float(body.position.y), terrain_y])
|
||||
|
||||
|
||||
## --- Test 2: Sprung zu den Spawnpunkten --------------------------------------
|
||||
func _test_spawn_jumps() -> void:
|
||||
print("Testgruppe: Sprung zu Spawnpunkten (1–4) — exakt, Toleranz ≤ 1 mm")
|
||||
var pt := _pt()
|
||||
if pt == null:
|
||||
_check(false, "PlaytestMode aktiv (Vorbedingung)")
|
||||
return
|
||||
var body: Node3D = _body()
|
||||
var doc: Dictionary = main.get("doc")
|
||||
var zones: Dictionary = doc.get("spawn_zones", {})
|
||||
|
||||
var idx := 1
|
||||
for zone_key in ["0", "1", "2", "3"]:
|
||||
var zone: Dictionary = zones.get(zone_key, {})
|
||||
var pts: Array = zone.get("points", [])
|
||||
if pts.is_empty() or not (pts[0] is Dictionary):
|
||||
_check(false, "Testmap braucht Zone %s" % zone_key)
|
||||
idx += 1
|
||||
continue
|
||||
var pos: Array = (pts[0] as Dictionary).get("pos", [0.0, 0.0, 0.0])
|
||||
var expected := Vector3(float(pos[0]),
|
||||
body.call("terrain_height", float(pos[0]), float(pos[2])), float(pos[2]))
|
||||
# Vorher woanders hin (sichert, dass wirklich gesprungen wird).
|
||||
body.set("position", Vector3(99.0, 5.0, 99.0))
|
||||
var ok := bool(pt.call("jump_to_spawn", idx))
|
||||
_check(ok, "Sprung %d (Zone %s '%s') gelungen" % [idx, zone_key, str(zone.get("label", ""))])
|
||||
var delta: float = body.position.distance_to(expected)
|
||||
_check(delta <= 0.001,
|
||||
"Landung exakt auf Spawnpunkt %d (%.4f mm Abweichung: Soll %s, Ist %s)" % [
|
||||
idx, delta * 1000.0, expected, body.position])
|
||||
idx += 1
|
||||
|
||||
# Fehlende Zone (Taste 5 wäre Zone 4): abgelehnt, Position UNVERÄNDERT.
|
||||
body.set("position", Vector3(3.0, 1.0, 4.0))
|
||||
var rejected := not bool(pt.call("jump_to_spawn", 5))
|
||||
_check(rejected, "Fehlende Zone: Sprung abgelehnt")
|
||||
_check(body.position == Vector3(3.0, 1.0, 4.0), "Fehlende Zone: Position unverändert")
|
||||
|
||||
|
||||
## --- Test 3: HUD-Toggle + Blickrichtung/Höhe --------------------------------
|
||||
func _test_hud_toggle() -> void:
|
||||
print("Testgruppe: HUD-Toggle (H) + Blickrichtung/Höhe im HUD")
|
||||
var pt := _pt()
|
||||
if pt == null:
|
||||
_check(false, "PlaytestMode aktiv (Vorbedingung)")
|
||||
return
|
||||
var hud: CanvasLayer = pt.get("hud")
|
||||
_check(hud != null and hud.visible, "HUD standardmäßig sichtbar")
|
||||
pt.call("_unhandled_input", _key_event(KEY_H))
|
||||
_check(not hud.visible, "Taste H: HUD AUS")
|
||||
pt.call("_unhandled_input", _key_event(KEY_H))
|
||||
_check(hud.visible, "Taste H: HUD wieder AN")
|
||||
var hud_dir: Label = pt.get("hud_dir")
|
||||
_check(hud_dir != null, "HUD-Blickrichtung-Label vorhanden")
|
||||
await process_frame
|
||||
_check(str(hud_dir.text).begins_with("Blick:"), "HUD zeigt Blickrichtung ('%s')" % hud_dir.text)
|
||||
_check("Höhe" in str(hud_dir.text), "HUD zeigt Höhe ('%s')" % hud_dir.text)
|
||||
|
||||
|
||||
## --- Test 4: Maus-Empfindlichkeit -------------------------------------------
|
||||
func _test_sensitivity() -> void:
|
||||
print("Testgruppe: Maus-Empfindlichkeit — speichern/laden, Clamp, live")
|
||||
PlaytestSettings.reset_cache()
|
||||
var saved: float = PlaytestSettings.save_sens(2.5)
|
||||
_check(is_equal_approx(saved, 2.5), "save_sens(2.5) gespeichert")
|
||||
PlaytestSettings.reset_cache()
|
||||
_check(is_equal_approx(PlaytestSettings.load_sens(), 2.5),
|
||||
"load_sens liest 2,5 zurück (Neustart-Simulation via reset_cache)")
|
||||
_check(is_equal_approx(PlaytestSettings.save_sens(9.0), 5.0), "Clamp oben: 9,0 → 5,0")
|
||||
_check(is_equal_approx(PlaytestSettings.save_sens(0.01), 0.1), "Clamp unten: 0,01 → 0,1")
|
||||
|
||||
# Live-Anwendung: Body-Basis 0,25 Grad/Pixel × Multiplikator.
|
||||
var body: Node3D = _body()
|
||||
var pt := _pt()
|
||||
pt.call("apply_sensitivity", 2.0)
|
||||
_check(is_equal_approx(float(body.get("mouse_sens")), 0.5),
|
||||
"apply_sensitivity(2.0): mouse_sens = 0.25 × 2 = 0.5 (Ist %.3f)" % float(body.get("mouse_sens")))
|
||||
|
||||
# Beim (Neu-)Start aus den Einstellungen gelesen: Store = 0.1 → Basis 0.025.
|
||||
PlaytestSettings.save_sens(0.1)
|
||||
pt.call("handle_escape")
|
||||
await process_frame
|
||||
_check(main.get("playtest") == null, "Zwischen-Exit sauber (Vorbedingung Neustart)")
|
||||
main.call("_on_test_run")
|
||||
await process_frame
|
||||
await process_frame
|
||||
var pt2 := _pt()
|
||||
_check(pt2 != null, "PlaytestMode neu gestartet")
|
||||
var body2: Node3D = pt2.get("body")
|
||||
_check(is_equal_approx(float(body2.get("mouse_sens")), 0.025),
|
||||
"Start liest Empfindlichkeit (0,1 → Basis 0.025, Ist %.4f)" % float(body2.get("mouse_sens")))
|
||||
_check(bool(body2.get("fly_mode")) == false, "Neustart: Fliegen wieder AUS (Body-Default)")
|
||||
|
||||
# Panel-Änderung wirkt SOFORT auf den laufenden Testlauf (Panel→apply_sensitivity).
|
||||
var panel: Node = main.find_child("einstellungen", true, false)
|
||||
if panel != null:
|
||||
var box: SpinBox = panel.get("sens_box")
|
||||
box.value = 3.0
|
||||
_check(is_equal_approx(float(body2.get("mouse_sens")), 0.75),
|
||||
"Panel 3,0 wirkt sofort (0.25 × 3 = 0.75, Ist %.3f)" % float(body2.get("mouse_sens")))
|
||||
else:
|
||||
_check(false, "Einstellungen-Panel für Live-Test vorhanden")
|
||||
|
||||
|
||||
## --- Test 5: Rückkehr in den Editor sauber ----------------------------------
|
||||
func _test_exit_restores_editor() -> void:
|
||||
print("Testgruppe: Rückkehr in den Editor (Dokument + Undo unverändert)")
|
||||
var pt := _pt()
|
||||
if pt == null:
|
||||
_check(false, "PlaytestMode aktiv (Vorbedingung)")
|
||||
return
|
||||
var doc_before: Dictionary = main.get("doc")
|
||||
var doc_before_str := str(doc_before)
|
||||
var hist: RefCounted = main.get("history")
|
||||
var undo_before: bool = hist.call("can_undo")
|
||||
var redo_before: bool = hist.call("can_redo")
|
||||
|
||||
# Komfort-Zustände an: Achsen + Hilfe — auch die müssen exit() abräumen.
|
||||
pt.call("toggle_axis")
|
||||
pt.call("toggle_help")
|
||||
_check(pt.get("_axis_node") != null, "Achsen-Linien an (Vorbedingung Aufräumen)")
|
||||
_check(pt.get("help_layer") != null, "Hilfe-Overlay an (Vorbedingung Aufräumen)")
|
||||
|
||||
pt.call("handle_escape")
|
||||
await process_frame
|
||||
await process_frame
|
||||
|
||||
_check(main.get("playtest") == null, "PlaytestMode abgeräumt")
|
||||
for pair in [["ui_top_bar", "Top-Bar"], ["ui_left_panel", "linke Spalte"],
|
||||
["ui_right_panel", "rechte Spalte"], ["ui_status_bar", "Statusleiste"]]:
|
||||
var panel: Control = main.get(pair[0])
|
||||
_check(panel != null and panel.visible, "%s wieder sichtbar" % pair[1])
|
||||
_check(str(main.get("doc")) == doc_before_str, "Dokument byte-identisch")
|
||||
_check(hist.can_undo() == undo_before and hist.can_redo() == redo_before,
|
||||
"Undo/Redo-Stapel unverändert")
|
||||
_check(Input.mouse_mode == Input.MOUSE_MODE_VISIBLE, "Maus wieder frei")
|
||||
1
tests/test_playtest_comfort.gd.uid
Normal file
1
tests/test_playtest_comfort.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bvhnpmmu1mwca
|
||||
|
|
@ -101,9 +101,15 @@ func _init() -> void:
|
|||
_check(left != null and left is TabContainer, "linke Spalte ist ein TabContainer (U2-vorbereitet)")
|
||||
_check(bottom != null and bottom is VSplitContainer, "unten: VSplit (größenverstellbar)")
|
||||
if right != null:
|
||||
# 20.10.: „Test-Start" ist als 6. Reiter dazugekommen (Wiederanbindung T1b,
|
||||
# nachdem U1 die Verdrahtung verloren hatte).
|
||||
_check(right.get_child_count() == 6, "rechts: 6 Tabs (Inspektor/Auswahl/Map/Terrain/Prüfen/Test-Start) — hat %d" % right.get_child_count())
|
||||
# Registry-getrieben (T3, Karte t_aaff5dcc): die erwartete Tab-Anzahl
|
||||
# kommt aus der Registry-Tabelle (gruppe "rechts") statt hart kodiert —
|
||||
# neue Panels melden sich dort an und der Check bleibt wahr.
|
||||
var expected_right := 0
|
||||
for entry in registry_script.PANELS:
|
||||
if str(entry["gruppe"]) == "rechts":
|
||||
expected_right += 1
|
||||
_check(right.get_child_count() == expected_right,
|
||||
"rechts: %d Tabs aus der Registry — hat %d" % [expected_right, right.get_child_count()])
|
||||
_check(right.get_tab_count() == right.get_child_count(),
|
||||
"rechts: alle Bereiche als Tab in der Leiste erreichbar (Titel je Bereich)")
|
||||
for i in right.get_tab_count():
|
||||
|
|
|
|||
Loading…
Reference in a new issue