92 lines
3.6 KiB
GDScript
92 lines
3.6 KiB
GDScript
extends RefCounted
|
||
class_name TestStart
|
||
## Test-Startpunkt (Karte t_96e3c4ee): der Punkt, an dem „▶ Karte testen“ (F5)
|
||
## den Spieler absetzt — KEIN Spiel-Spawnpunkt (taucht nicht in doc,
|
||
## Spawn-Checks/Analysen oder Validator auf, ändert keine Zonen).
|
||
##
|
||
## Bewusste Entscheidungen dieser Karte:
|
||
## - Der Punkt lebt NUR editor-seitig (Sidecar, test_start_store.gd) — das
|
||
## .svmap-Format (Validator V1–V16, Client/Server) bleibt UNBERÜHRT.
|
||
## - Gespeichert wird nur x/z. Die Höhe kommt IMMER automatisch aus dem
|
||
## Terrain an der Stelle (SvmapDocument.terrain_height_at) — Terrain
|
||
## formen lässt den Punkt nicht schweben und versinken.
|
||
## - Fallback-Reihenfolge F5 (je mit Klartext-Hinweis): gesetzter Test-Start
|
||
## → erster Team-Spawnpunkt (Team Blau, wie bisher) → Kartenmitte.
|
||
## - Trifft der Klick ein Objekt/Bauteil: landet der Punkt auf dem BODEN
|
||
## DARUNTER (nie im Objekt schwebend) — das Tool meldet es im Klartext.
|
||
##
|
||
## Alles pure Statics (kein UI, kein Node-Zugriff) → headless testbar in
|
||
## tests/test_playtest_spawn.gd.
|
||
|
||
## Kein Punkt gesetzt (Sentinel-Typ für Variant-Vergleiche in main.gd).
|
||
|
||
## Start-Punkt für den Testlauf auflösen (Fallback-Kette). test_point ist
|
||
## Vector3 (nur x/z zählen) oder null. Rückgabe:
|
||
## {pos: Vector3 (y = Terrainhöhe), quelle: "test"|"team"|"mitte",
|
||
## status: Klartext für die Statuszeile}
|
||
static func resolve_start(doc: Dictionary, test_point: Variant) -> Dictionary:
|
||
if test_point != null:
|
||
var p: Vector3 = test_point
|
||
var y := SvmapDocument.terrain_height_at(doc, p.x, p.z)
|
||
return {
|
||
"pos": Vector3(p.x, y, p.z),
|
||
"quelle": "test",
|
||
"status": "Start an deinem gesetzten Test-Startpunkt (%.0f, %.0f, Höhe automatisch)." % [p.x, p.z],
|
||
}
|
||
# Erster Team-Spawnpunkt (Team Blau = „0“) — exakt wie bisher (t_1cdc1a72).
|
||
var zones: Dictionary = doc.get("spawn_zones", {})
|
||
var team0 = zones.get("0", {})
|
||
if team0 is Dictionary:
|
||
var points: Array = (team0 as Dictionary).get("points", [])
|
||
if not points.is_empty() and points[0] is Dictionary:
|
||
var pos = (points[0] as Dictionary).get("pos", [])
|
||
if pos is Array and (pos as Array).size() >= 3:
|
||
return {
|
||
"pos": Vector3(float(pos[0]), float(pos[1]), float(pos[2])),
|
||
"quelle": "team",
|
||
"status": "Kein Test-Start gesetzt — Start am ersten Team-Blau-Spawnpunkt.",
|
||
}
|
||
# Kartenmitte aus den Bounds.
|
||
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
|
||
return {
|
||
"pos": Vector3(cx, SvmapDocument.terrain_height_at(doc, cx, cz), cz),
|
||
"quelle": "mitte",
|
||
"status": "Weder Test-Start noch Team-Spawnpunkte — Start in der Kartenmitte.",
|
||
}
|
||
|
||
|
||
## Strahl → erster Treffer auf einer AABB (Slab-Methode) oder -1.0.
|
||
## Für „Klick trifft Bauteil“ im Editor-Tool und „Fadenkreuz trifft Hindernis“
|
||
## im Testlauf (kein Physik-Server im Editor/SubViewport).
|
||
## Rückgabe: Distanz t >= 0 (origin + dir * t) oder -1.0 = kein Treffer.
|
||
static func ray_aabb(origin: Vector3, dir: Vector3, box: AABB) -> float:
|
||
var d := dir
|
||
if d.length_squared() < 0.000000001:
|
||
return -1.0
|
||
d = d.normalized()
|
||
var tmin := 0.0
|
||
var tmax := INF
|
||
for axis in 3:
|
||
var o := origin[axis]
|
||
var dv := d[axis]
|
||
var lo := box.position[axis]
|
||
var hi := box.end[axis]
|
||
if absf(dv) < 0.0000001:
|
||
if o < lo or o > hi:
|
||
return -1.0
|
||
continue
|
||
var t1 := (lo - o) / dv
|
||
var t2 := (hi - o) / dv
|
||
if t1 > t2:
|
||
var tmp := t1
|
||
t1 = t2
|
||
t2 = tmp
|
||
tmin = maxf(tmin, t1)
|
||
tmax = minf(tmax, t2)
|
||
if tmin > tmax:
|
||
return -1.0
|
||
return tmin
|