update
This commit is contained in:
350
tests/verify.gd
350
tests/verify.gd
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user