This commit is contained in:
Team3
2026-09-16 01:23:42 +02:00
parent 12cbb88692
commit 936773c4e9
13 changed files with 1300 additions and 314 deletions

View File

@@ -26,6 +26,7 @@ func _ready() -> void:
await test_journal()
await test_traumdorf()
await test_dorf_organisch()
await test_huegel()
await test_haus_betreten()
await test_hoersaal()
await test_mission_annehmen()
@@ -35,6 +36,8 @@ func _ready() -> void:
await test_wege_machbar()
await test_geist()
await test_verfolgung()
await test_charakter()
await test_kamera_und_etagen()
await test_gehen_bei_tempo()
await test_diagnose()
await test_notieren_im_spiel()
@@ -166,6 +169,8 @@ func _druecke(anfang: String) -> bool:
func laufe(von: Vector3, yaw: float, frames: int) -> Vector3:
main.player.set_mode(Player.Mode.TRAUM)
main.player.look_enabled = true
if welt() is DreamWorld:
von.y = maxf(von.y, DreamDB.hoehe_bei(von.x, von.z) + 1.0) # nie im Hügel starten
main.player.global_position = von
main.player.velocity = Vector3.ZERO
main.player.yaw = yaw
@@ -375,6 +380,103 @@ func test_traumdorf() -> void:
await get_tree().process_frame
## Die Insel ist ein Hügel: innen eben, außen ansteigend, jedes Gebäude auf
## einer Terrasse. Spieler und Bewohner laufen darauf, statt hindurchzufallen.
func test_huegel() -> void:
abschnitt("Die Insel ist ein Hügel")
var uhr_vorher := Game.time_h
var tag_vorher := Game.day
p(is_equal_approx(DreamDB.hang(0.0), 0.0) and is_equal_approx(DreamDB.hang(DreamDB.FLACH), 0.0),
"Bis %.0f Einheiten ist es eben" % DreamDB.FLACH)
p(DreamDB.hang(DreamDB.FLACH + 20.0) > 3.0 and DreamDB.hang(200.0) <= DreamDB.GIPFEL,
"Dahinter steigt es an, bis zum Gipfel bei %.0f (bei +20: %.1f)" %
[DreamDB.GIPFEL, DreamDB.hang(DreamDB.FLACH + 20.0)])
p(DreamDB.hoehe_bei(0.0, 2.0) == 0.0, "Der Dorfplatz liegt eben")
var tor_terr := DreamDB.terrasse("tor")
p(is_equal_approx(DreamDB.hoehe_bei(0.0, -34.0), tor_terr)
and is_equal_approx(DreamDB.hoehe_bei(0.0, -34.0 + DreamDB.TOR_FLAECHE * 0.5 + 2.0), tor_terr),
"Das Tor steht auf einer Terrasse, eben mit seiner Dorfseite (%.1f)" % tor_terr)
var stufe := absf(DreamDB.hoehe_bei(0.0, 25.4) - DreamDB.terrasse("hoersaal"))
p(stufe < 0.1, "Vor der Hörsaaltür ist keine Stufe (%.2f)" % stufe)
# Ein Dorf mit vielen Häusern: manche stehen am Hang, jedes auf einer Terrasse
var merker: Array = Game.completed.duplicate()
Game.completed.clear()
for i in range(14):
Game.completed.append("huegel_%d" % i)
Game.welt_erzeugen()
var am_hang := 0
var schief: Array = []
for key in DreamDB.PLACES:
var s: Array = DreamDB.PLACES[key]["size"]
if float(s[0]) <= 0.0:
continue
var c := Orte.center(DreamDB.PLACES, key)
var terr := DreamDB.terrasse(key)
if terr > 0.5:
am_hang += 1
for dx in [-1.0, 1.0]:
for dz in [-1.0, 1.0]:
var h := DreamDB.hoehe_bei(c.x + dx * float(s[0]) * 0.45, c.z + dz * float(s[1]) * 0.45)
if absf(h - terr) > 0.01:
schief.append(key)
p(am_hang >= 3, "Mit 14 Häusern stehen welche am Hang (%d)" % am_hang)
p(schief.is_empty(), "Jedes Gebäude steht innen eben auf seiner Terrasse%s" %
("" if schief.is_empty() else " — nicht: " + ", ".join(schief)))
main._traumwelt()
await get_tree().process_frame
await get_tree().process_frame
# Bewohner laufen einen Tag lang auf dem Boden, nicht darunter oder darüber
var abweichung := 0.0
Game.time_h = 0.0
for i in range(3000):
schritte(1)
for w in welt().bewohner:
var soll := DreamDB.hoehe_bei(w.global_position.x, w.global_position.z)
abweichung = maxf(abweichung, absf(w.global_position.y - soll))
p(abweichung < 0.05, "%d Bewohner bleiben auf dem Boden (Abweichung %.2f)" %
[welt().bewohner.size(), abweichung])
# Der Spieler kommt den Hang hinauf und fällt nicht hindurch. Dafür eine
# Richtung, in der kein Gebäude im Weg steht.
var richtung := Vector3.ZERO
var wenigste := 999
for k in range(24):
var a := TAU * float(k) / 24.0
var d := Vector3(cos(a), 0, sin(a))
var treffer := 0
for r in range(int(DreamDB.FLACH), int(DreamDB.FLACH) + 24, 2):
var q := d * float(r)
for key in DreamDB.PLACES:
var s: Array = DreamDB.PLACES[key]["size"]
if float(s[0]) <= 0.0:
continue
var c := Orte.center(DreamDB.PLACES, key)
if absf(q.x - c.x) < float(s[0]) * 0.5 + 2.5 and absf(q.z - c.z) < float(s[1]) * 0.5 + 2.5:
treffer += 1
if treffer < wenigste:
wenigste = treffer
richtung = d
p(wenigste == 0, "Eine freie Richtung den Hang hinauf gibt es (beste: %d Treffer)" % wenigste)
var start := richtung * (DreamDB.FLACH + 1.0) + Vector3(0, 1.0, 0)
var yaw := atan2(-richtung.x, -richtung.z)
var ende: Vector3 = await laufe(start, yaw, 140)
var boden_dort := DreamDB.hoehe_bei(ende.x, ende.z)
var weit := Vector2(ende.x, ende.z).length() - Vector2(start.x, start.z).length()
p(weit > 6.0, "Er läuft hangaufwärts (%.0f Einheiten weiter außen)" % weit)
p(ende.y > start.y + 1.0, "Und kommt dabei höher (y %.1f -> %.1f)" % [start.y, ende.y])
p(ende.y >= boden_dort - 0.2 and ende.y < boden_dort + 2.5,
"Er steht auf dem Boden, nicht darunter (y %.1f, Boden %.1f)" % [ende.y, boden_dort])
Game.completed.assign(merker)
Game.time_h = uhr_vorher
Game.day = tag_vorher
Game.welt_erzeugen()
main._traumwelt()
await get_tree().process_frame
func test_haus_betreten() -> void:
abschnitt("Orte betreten")
p(not Game.ort_offen(DreamDB.haus_key(0)), "Ohne Bewohner steht kein Haus")
@@ -586,8 +688,8 @@ func test_tagesablauf() -> void:
schritte(30, 0.05, 2.0)
p(mensch.activity == "schlaeft", "Um 02:00 schläft er (%s)" % mensch.activity)
p(mensch.global_position.distance_to(
Orte.center(HumanDB.PLACES, str(mensch.data["heim"]))) < 5.0,
p(_xz(mensch.global_position).distance_to(
_xz(Orte.center(HumanDB.PLACES, str(mensch.data["heim"])))) < 5.0,
"Und zwar zu Hause")
# Jede Station des eigenen Tagesablaufs wird pünktlich erreicht
@@ -704,6 +806,250 @@ func test_geist() -> void:
p(lena.global_position.distance_to(davor) < 2.0, "Menschen nehmen den Geist nicht wahr")
func _farbabstand(a: Color, b: Color) -> float:
return Vector3(a.r, a.g, a.b).distance_to(Vector3(b.r, b.g, b.b))
func test_charakter() -> void:
abschnitt("Der Geist und die Menschen sind am Körper lesbar")
ui().schliessen()
var sp: Player = main.player
sp.set_mode(Player.Mode.GEIST)
sp.look_enabled = true
sp.folgt = null
p(sp._augen.size() == 2 and sp._augen[0].position.z < 0.0, "Zwei Augen, vorn am Kopf")
p(sp._funken.size() == Player.FUNKEN_MAX, "%d Funken stehen bereit" % Player.FUNKEN_MAX)
for i in range(60):
await get_tree().physics_frame
var sichtbar := 0
for f in sp._funken:
if f.visible:
sichtbar += 1
var soll := clampi(Game.known.size(), 1, Player.FUNKEN_MAX)
p(sichtbar == soll, "Je gelernter Technik kreist ein Funke (%d bei %d Techniken)" %
[sichtbar, Game.known.size()])
p(_farbabstand(sp._farbe, Player.FARBE_GEIST) < 0.1, "Beim Beobachten leuchtet er kühl")
var mensch: Bewohner = welt().humans[0]
sp.global_position = mensch.global_position + Vector3(0, 1.5, 2.0)
sp.folgt = mensch
for i in range(90):
await get_tree().physics_frame
p(_farbabstand(sp._farbe, Player.FARBE_FOLGE) < 0.1, "Beim Verfolgen leuchtet er warm")
sp.folgt = null
Input.action_press("vor")
for i in range(40):
await get_tree().physics_frame
Input.action_release("vor")
var kopf: Vector3 = sp.body.to_global(Vector3(0, 1.1, 0))
var hinten := 0
for k in sp._schleppe:
if k.visible and (k as Node3D).global_position.distance_to(kopf) > 0.3:
hinten += 1
p(hinten >= 2, "Beim Fliegen zieht er einen Schweif hinter sich her (%d von 3)" % hinten)
for i in range(40):
await get_tree().physics_frame
var ruht := true
for k in sp._schleppe:
if k.visible:
ruht = false
p(ruht, "Im Stand verschwindet der Schweif")
# Haltung der Menschen: Angst duckt, Erschöpfung beugt -- bis überwunden
var merker := Game.ueberwunden.duplicate(true)
var muede: Bewohner = null
var bang: Bewohner = null
var frei: Bewohner = null
for m in welt().humans:
var pr: Array = m.data.get("probleme", [])
if muede == null and pr.has("erschoepfung"):
muede = m
elif bang == null and pr.has("angst"):
bang = m
elif frei == null and not pr.has("angst") and not pr.has("erschoepfung"):
frei = m
p(muede != null and bang != null and frei != null, "Müde, ängstliche und freie Menschen sind da")
if muede != null:
Game.ueberwunden.erase(str(muede.data["id"]))
if bang != null:
Game.ueberwunden.erase(str(bang.data["id"]))
schritte(80)
p(muede != null and muede._ober.rotation.x < -0.25,
"Erschöpfung beugt den Oberkörper (%.2f)" % (muede._ober.rotation.x if muede else 0.0))
p(bang != null and bang._ober.scale.x < 0.9,
"Angst macht klein (%.2f)" % (bang._ober.scale.x if bang else 0.0))
p(frei != null and absf(frei._ober.rotation.x) < 0.05 and frei._ober.scale.x > 0.98,
"Ohne diese Klemmen steht man aufrecht")
if muede != null:
Game.ueberwunden[str(muede.data["id"])] = {"erschoepfung": true}
schritte(200)
p(muede._ober.rotation.x > -0.05, "Überwunden richtet er sich auf (%.2f)" % muede._ober.rotation.x)
Game.ueberwunden = merker
# Hüte nach Beruf
var kappen: Dictionary = {}
for m in welt().humans:
var typ := str(HumanDB.BERUF_TAGESTYP.get(str(m.data.get("beruf", "")), ""))
kappen[typ] = m._ober.get_child_count()
p(kappen.has("heim") and kappen.has("frueh") and kappen.has("buero")
and int(kappen["frueh"]) > int(kappen["heim"]) and int(kappen["buero"]) > int(kappen["heim"]),
"Berufe tragen verschiedene Kopfbedeckungen (%s)" % str(kappen))
p(mensch._arme.size() == 2, "Jeder hat zwei Arme")
sp.velocity = Vector3.ZERO
func _xz(v: Vector3) -> Vector2:
return Vector2(v.x, v.z)
func test_kamera_und_etagen() -> void:
abschnitt("Feste Kamera, kleine Menschen, Stadt mit Stockwerken")
ui().schliessen()
var sp: Player = main.player
sp.set_mode(Player.Mode.GEIST)
sp.look_enabled = true
sp.folgt = null
p(Input.mouse_mode == Input.MOUSE_MODE_VISIBLE, "Die Maus ist frei")
p(is_equal_approx(sp.arm.rotation.x, Player.PITCH) and sp.cam.get_parent() == sp.arm,
"Die Kamera schaut fest von schräg oben (%.0f°)" % rad_to_deg(-Player.PITCH))
sp.yaw = Player.CAM_YAW
var yaw_vorher: float = sp.yaw
var dreh := InputEventAction.new()
dreh.action = "drehen"
dreh.pressed = true
Input.parse_input_event(dreh)
for i in range(80):
await get_tree().physics_frame
p(absf(angle_difference(yaw_vorher + Player.DREHSCHRITT, sp.yaw)) < 0.05,
"Q dreht die Kamera um 90° (%.0f°)" % rad_to_deg(sp.yaw - yaw_vorher))
var rad := InputEventMouseButton.new()
rad.button_index = MOUSE_BUTTON_WHEEL_DOWN
rad.pressed = true
Input.parse_input_event(rad)
for i in range(150):
await get_tree().physics_frame
p(absf(sp.cam.position.z - float(Player.ZOOM[1])) < 0.5,
"Mausrad zoomt heraus (%.1f)" % sp.cam.position.z)
rad = InputEventMouseButton.new()
rad.button_index = MOUSE_BUTTON_WHEEL_UP
rad.pressed = true
Input.parse_input_event(rad)
for i in range(150):
await get_tree().physics_frame
p(absf(sp.cam.position.z - float(Player.ZOOM[0])) < 0.5,
"Und wieder heran (%.1f)" % sp.cam.position.z)
var mensch: Bewohner = welt().humans[0]
p(mensch._visual.scale.x < 0.85 and sp.body.scale.x < 0.7,
"Menschen und Geist sind kleiner (%.2f / %.2f)" % [mensch._visual.scale.x, sp.body.scale.x])
# Stockwerke
var mehr := 0
for key in HumanDB.PLACES:
if Orte.etagen(HumanDB.PLACES, key) > 1:
mehr += 1
p(mehr >= 12, "%d Gebäude haben mehrere Stockwerke" % mehr)
var oben: Bewohner = null
for m in welt().humans:
if m.etage(str(m.data["heim"])) > 0:
oben = m
break
p(oben != null, "Jemand wohnt über dem Erdgeschoss")
var heim := str(oben.data["heim"])
var e := oben.etage(heim)
var c := Orte.center(HumanDB.PLACES, heim)
var g: Array = HumanDB.PLACES[heim]["size"]
var t0 := Orte.treppe(HumanDB.PLACES, heim, 0)
var t1 := Orte.treppe(HumanDB.PLACES, heim, 1)
var im_haus: bool = absf(t0.x - c.x) < float(g[0]) * 0.5 and absf(t0.z - c.z) < float(g[1]) * 0.5 \
and absf(t1.x - c.x) < float(g[0]) * 0.5 and absf(t1.z - c.z) < float(g[1]) * 0.5
p(im_haus and is_equal_approx(t1.y, Orte.ETAGE), "Das Treppenhaus steht im Haus, ein Podest je Etage")
var uhr := Game.time_h
var zuhause := false
for i in range(3000):
schritte(1, 0.05, 2.0)
if oben.place == heim and oben._path.is_empty():
zuhause = true
break
p(zuhause and absf(oben.global_position.y - Orte.hoehe(e)) < 0.2,
"Nachts liegt %s in seiner Wohnung im %d. Stock (y = %.1f)" %
[oben.data["name"], e, oben.global_position.y])
var weg_stunde := -1.0
for r in oben.data["routine"]:
if str(r[1]) != heim:
weg_stunde = float(r[0]) + 0.5
break
var draussen := false
var tief := 99.0
for i in range(3000):
schritte(1, 0.05, weg_stunde)
tief = minf(tief, oben.global_position.y)
if _xz(oben.global_position).distance_to(_xz(c)) > float(g[0]) * 0.5 + 3.0:
draussen = absf(oben.global_position.y) < 0.3
break
p(draussen, "Auf die Straße kommt er über die Treppe wieder nach unten (y = %.1f)" %
oben.global_position.y)
# Etagenschnitt
var bau: Dictionary = {}
for b in welt()._bauten:
if (b["c"] as Vector3).distance_to(c) < 0.1:
bau = b
var n := Orte.etagen(HumanDB.PLACES, heim)
p(not bau.is_empty() and (bau["etagen"] as Array).size() == n,
"Sein Haus steht mit allen %d Etagen da" % n)
sp.global_position = c + Vector3(0, 1.4, 0)
sp.velocity = Vector3.ZERO
sp.yaw = Player.CAM_YAW
await get_tree().physics_frame
welt()._process(0.05)
var obere_weg := true
for f in range(1, n):
if (bau["etagen"][f] as Node3D).visible:
obere_weg = false
var wand_weg := 0
for w in bau["waende"][0]:
if not (w as Node3D).visible:
wand_weg += 1
p(obere_weg and not (bau["dach"][0] as Node3D).visible,
"Steht der Geist im Erdgeschoss, verschwinden Dach und obere Etagen")
p(wand_weg >= 2 and wand_weg < (bau["waende"][0] as Array).size(),
"Die Wände zur Kamera hin sind weg, die hinteren bleiben (%d von %d Teile)" %
[wand_weg, (bau["waende"][0] as Array).size()])
Input.action_press("steigen")
for i in range(60):
await get_tree().physics_frame
Input.action_release("steigen")
p(absf(sp.global_position.y - (1.4 + Orte.ETAGE)) < 0.1,
"Leertaste hebt den Geist genau ein Stockwerk (y = %.1f)" % sp.global_position.y)
welt()._process(0.05)
p((bau["etagen"][0] as Node3D).visible and (bau["etagen"][1] as Node3D).visible
and (n < 3 or not (bau["etagen"][2] as Node3D).visible),
"Im ersten Stock sieht man Erdgeschoss und ersten Stock")
sp.global_position = c + Vector3(float(g[0]) + 4.0, 1.4, 0)
await get_tree().physics_frame
welt()._process(0.05)
var alles := (bau["dach"][0] as Node3D).visible
for f in range(n):
if not (bau["etagen"][f] as Node3D).visible:
alles = false
for w in bau["waende"][0]:
if not (w as Node3D).visible:
alles = false
p(alles, "Draußen steht das Haus wieder ganz")
# Klick auf einen Menschen
sp.global_position = oben.global_position + Vector3(2.0, 1.4, 2.0)
sp.velocity = Vector3.ZERO
await get_tree().physics_frame
var schirm := sp.cam.unproject_position(oben.global_position + Vector3(0, 0.8, 0))
main._klick(schirm)
p(sp.folgt == oben, "Ein Klick auf einen Menschen hängt den Geist an ihn")
main._klick(schirm)
p(sp.folgt == null, "Noch ein Klick löst ihn wieder")
Game.time_h = uhr
## Bei hohem Tempo wird der Schritt je Bild größer als die Ankunftsschwelle.
## Wer dann über den Wegpunkt hinausläuft, pendelt um ihn herum statt anzukommen.
func test_gehen_bei_tempo() -> void: