update
This commit is contained in:
64
tests/flimmer.gd
Normal file
64
tests/flimmer.gd
Normal file
@@ -0,0 +1,64 @@
|
||||
extends Node
|
||||
## Flimmer-Probe: Szene anhalten, Kamera um Millimeter rücken, Bilder vergleichen.
|
||||
## Deckungsgleiche Flächen (Z-Fighting) kippen dabei großflächig, echte Kanten
|
||||
## wandern nur um Bruchteile eines Pixels.
|
||||
const GRENZE := 0.4 # Prozent veränderter Pixel, ab der es flimmert
|
||||
var main: Node3D
|
||||
|
||||
func _ready() -> void:
|
||||
main = load("res://scenes/main.tscn").instantiate()
|
||||
add_child(main)
|
||||
await get_tree().process_frame
|
||||
main.ui.schliessen()
|
||||
Game.time_h = 12.0
|
||||
var hs := Orte.center(DreamDB.PLACES, "hoersaal")
|
||||
main.player.global_position = hs + Vector3(1.0, 1.0, 2.0)
|
||||
var traum := await _messen()
|
||||
main.player.global_position = Vector3(0, 2.0, 20.0)
|
||||
var dorf := await _messen()
|
||||
main._erde()
|
||||
await get_tree().process_frame
|
||||
main.player.global_position = Vector3(0, 0.3, 12.0)
|
||||
var park := await _messen()
|
||||
var heim := ""
|
||||
for m in main.welt.humans:
|
||||
heim = str(m.data["heim"])
|
||||
break
|
||||
main.player.global_position = Orte.center(HumanDB.PLACES, heim) + Vector3(0, 0.3, 0)
|
||||
var wohnung := await _messen()
|
||||
var schlimmste: float = [traum, dorf, park, wohnung].max()
|
||||
print("FLIMMERN %s hoersaal=%.2f%% dorf=%.2f%% park=%.2f%% wohnung=%.2f%%" % [
|
||||
"OK" if schlimmste <= GRENZE else "ZU VIEL", traum, dorf, park, wohnung])
|
||||
get_tree().quit(0 if schlimmste <= GRENZE else 1)
|
||||
|
||||
|
||||
## Prozent der Pixel, die sich zwischen zwei Bildern bei winziger Kamerabewegung stark ändern.
|
||||
func _messen() -> float:
|
||||
for i in range(20):
|
||||
await get_tree().process_frame
|
||||
get_tree().paused = true
|
||||
var cam: Camera3D = main.player.cam
|
||||
var bilder: Array = []
|
||||
for i in range(4):
|
||||
cam.position.x += 0.002
|
||||
await RenderingServer.frame_post_draw
|
||||
await RenderingServer.frame_post_draw
|
||||
var img := get_viewport().get_texture().get_image()
|
||||
img.resize(img.get_width() / 2, img.get_height() / 2, Image.INTERPOLATE_NEAREST)
|
||||
bilder.append(img)
|
||||
cam.position.x -= 0.008
|
||||
get_tree().paused = false
|
||||
var schlimm := 0.0
|
||||
for k in range(1, bilder.size()):
|
||||
var a: Image = bilder[k - 1]
|
||||
var b: Image = bilder[k]
|
||||
var anders := 0
|
||||
for y in range(0, a.get_height(), 2):
|
||||
for x in range(0, a.get_width(), 2):
|
||||
var ca := a.get_pixel(x, y)
|
||||
var cb := b.get_pixel(x, y)
|
||||
if absf(ca.r - cb.r) > 0.19 or absf(ca.g - cb.g) > 0.19 or absf(ca.b - cb.b) > 0.19:
|
||||
anders += 1
|
||||
var anteil := 100.0 * float(anders) / float((a.get_width() / 2) * (a.get_height() / 2))
|
||||
schlimm = maxf(schlimm, anteil)
|
||||
return schlimm
|
||||
6
tests/flimmer.tscn
Normal file
6
tests/flimmer.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/flimmer.gd" id="1_f"]
|
||||
|
||||
[node name="Flimmer" type="Node"]
|
||||
script = ExtResource("1_f")
|
||||
37
tests/rauch.gd
Normal file
37
tests/rauch.gd
Normal file
@@ -0,0 +1,37 @@
|
||||
extends Node
|
||||
## Rauchtest: das echte Spiel, echte Aushänge-Datei, ein paar Sekunden spielen.
|
||||
## Jede ERROR-Zeile im Log lässt tests/run.sh durchfallen.
|
||||
var main: Node3D
|
||||
|
||||
func _ready() -> void:
|
||||
main = load("res://scenes/main.tscn").instantiate()
|
||||
add_child(main)
|
||||
await _sekunden(1.5)
|
||||
main.ui.schliessen()
|
||||
Input.action_press("vor")
|
||||
await _sekunden(1.0)
|
||||
Input.action_release("vor")
|
||||
main._erde()
|
||||
await _sekunden(1.5)
|
||||
var leute: Array = main.welt.humans
|
||||
if leute.is_empty():
|
||||
print("RAUCHTEST FEHLT: keine Menschen")
|
||||
get_tree().quit(1)
|
||||
return
|
||||
main.player.anhaengen(leute[0])
|
||||
await _sekunden(3.0)
|
||||
main.player.folgt = null
|
||||
Input.action_press("links")
|
||||
await _sekunden(1.0)
|
||||
Input.action_release("links")
|
||||
main.ui.journal_panel()
|
||||
await _sekunden(0.5)
|
||||
main.ui.schliessen()
|
||||
main._traumwelt()
|
||||
await _sekunden(1.0)
|
||||
print("RAUCHTEST OK: %d Menschen, Tag %d %02d:%02d" % [leute.size(), Game.day, int(Game.time_h), int(fmod(Game.time_h, 1.0) * 60.0)])
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
func _sekunden(s: float) -> void:
|
||||
await get_tree().create_timer(s).timeout
|
||||
6
tests/rauch.tscn
Normal file
6
tests/rauch.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/rauch.gd" id="1_r"]
|
||||
|
||||
[node name="Rauch" type="Node"]
|
||||
script = ExtResource("1_r")
|
||||
16
tests/run.sh
16
tests/run.sh
@@ -12,6 +12,22 @@ if ! timeout 90 godot --headless --path . tests/parse.tscn > "$log/parse.log" 2>
|
||||
fi
|
||||
timeout 600 godot --headless --path . tests/verify.tscn -- "$@" > "$log/suite.log" 2>&1
|
||||
code=$?
|
||||
if [ -n "${DISPLAY:-}" ] && [ "${1:-}" != "schnell" ]; then
|
||||
# Rauchtest: das echte Spiel mit der echten Aushänge-Datei, ein paar Sekunden
|
||||
timeout 120 godot --path . tests/rauch.tscn > "$log/rauch.log" 2>&1
|
||||
rauch=$(grep -E "^(SCRIPT )?ERROR:" "$log/rauch.log" | grep -v leaked | sort | uniq -c | sort -rn)
|
||||
if [ -n "$rauch" ] || ! grep -q "RAUCHTEST OK" "$log/rauch.log"; then
|
||||
echo "Rauchtest fehlgeschlagen:"; echo "$rauch" | head -5; grep "RAUCHTEST" "$log/rauch.log"; [ $code -eq 0 ] && code=4
|
||||
else
|
||||
echo "Rauchtest OK"
|
||||
fi
|
||||
# Flimmer-Probe: stehende Szene, Kamera minimal bewegt, Bilder verglichen
|
||||
timeout 120 godot --path . tests/flimmer.tscn > "$log/flimmer.log" 2>&1
|
||||
grep "FLIMMERN" "$log/flimmer.log"
|
||||
if ! grep -q "FLIMMERN OK" "$log/flimmer.log"; then [ $code -eq 0 ] && code=5; fi
|
||||
else
|
||||
echo "Rauchtest und Flimmer-Probe übersprungen (kein Display oder Schnelllauf)"
|
||||
fi
|
||||
echo "OK: $(grep -c ' OK ' "$log/suite.log")"
|
||||
grep -E "FEHLT|Fehlende|SCRIPT ERROR|Parse Error|Langsamste|Schnelllauf" "$log/suite.log"
|
||||
fehler=$(grep -E "^(SCRIPT )?ERROR:" "$log/suite.log" | grep -v "leaked" | sort | uniq -c | sort -rn)
|
||||
|
||||
524
tests/verify.gd
524
tests/verify.gd
@@ -8,7 +8,7 @@ var main: Node3D
|
||||
const ZEITRAFFER := 8.0 # die Engine läuft im Test achtmal schneller
|
||||
## Abschnitte, die ganze Tage oder viele Läufe simulieren -- im Schnelllauf übersprungen.
|
||||
const LANGSAM := ["test_tagesablauf", "test_verfolgung", "test_erster_traum_schaffbar",
|
||||
"test_huegel", "test_dorf_organisch", "test_welt_waechst"]
|
||||
"test_huegel", "test_dorf_organisch", "test_welt_waechst", "test_zufallslauf"]
|
||||
var schnell := false
|
||||
var _uhr_abschnitt := 0
|
||||
var _dauern: Array = []
|
||||
@@ -46,6 +46,16 @@ func lauf(name: String) -> void:
|
||||
if schnell and LANGSAM.has(name):
|
||||
_uebersprungen += 1
|
||||
return
|
||||
# Ausgangslage für jeden Abschnitt: Oberfläche zu, niemand verfolgt, kein
|
||||
# Moment, keine Taste gedrückt -- sonst erbt ein Abschnitt die Fehler des vorigen
|
||||
ui().schliessen()
|
||||
main.player.folgt = null
|
||||
main.player.look_enabled = true
|
||||
main.player.velocity = Vector3.ZERO
|
||||
Game.situation = {}
|
||||
Game.notiz_sperre = 0.0
|
||||
for t in ["vor", "zurueck", "links", "rechts", "steigen", "drehen"]:
|
||||
Input.action_release(t)
|
||||
await Callable(self, name).call()
|
||||
|
||||
|
||||
@@ -71,6 +81,12 @@ func _ready() -> void:
|
||||
await lauf("test_hoersaal")
|
||||
await lauf("test_mission_annehmen")
|
||||
await lauf("test_reise_zur_erde")
|
||||
await lauf("test_welten_bleiben")
|
||||
await lauf("test_spuren")
|
||||
await lauf("test_ankunft")
|
||||
await lauf("test_akte")
|
||||
await lauf("test_passanten")
|
||||
await lauf("test_wesen_helfen")
|
||||
await lauf("test_bezirk")
|
||||
await lauf("test_tagesablauf")
|
||||
await lauf("test_wege_machbar")
|
||||
@@ -78,6 +94,7 @@ func _ready() -> void:
|
||||
await lauf("test_verfolgung")
|
||||
await lauf("test_charakter")
|
||||
await lauf("test_kamera_und_etagen")
|
||||
await lauf("test_zufallslauf")
|
||||
await lauf("test_gehen_bei_tempo")
|
||||
await lauf("test_diagnose")
|
||||
await lauf("test_notieren_im_spiel")
|
||||
@@ -135,7 +152,7 @@ func schritte(n: int, dt: float = 0.05, zeit: float = -1.0) -> void:
|
||||
|
||||
func _panel_text() -> String:
|
||||
var out: Array = []
|
||||
for c in ui()._content.get_children():
|
||||
for c in _alle_kinder(ui()._content):
|
||||
if c is RichTextLabel:
|
||||
out.append((c as RichTextLabel).get_parsed_text())
|
||||
return "\n".join(out)
|
||||
@@ -193,21 +210,21 @@ func _hingehen(ziel: Node3D) -> void:
|
||||
## Drückt den ersten aktiven Knopf, dessen Beschriftung so beginnt.
|
||||
func _knopf_texte() -> String:
|
||||
var out: Array = []
|
||||
for c in ui()._content.get_children():
|
||||
for c in _alle_kinder(ui()._content):
|
||||
if c is Button:
|
||||
out.append((c as Button).text)
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
func _knopf_da(anfang: String) -> bool:
|
||||
for c in ui()._content.get_children():
|
||||
for c in _alle_kinder(ui()._content):
|
||||
if c is Button and (c as Button).text.begins_with(anfang):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _druecke(anfang: String) -> bool:
|
||||
for c in ui()._content.get_children():
|
||||
for c in _alle_kinder(ui()._content):
|
||||
if c is Button and not (c as Button).disabled and (c as Button).text.begins_with(anfang):
|
||||
(c as Button).pressed.emit()
|
||||
return true
|
||||
@@ -420,6 +437,10 @@ func test_traumdorf() -> void:
|
||||
if w.place == r[0]:
|
||||
zuhause += 1
|
||||
p(zuhause == 4, "Um 01:00 sind alle vier an ihrem Nachtplatz (%d)" % zuhause)
|
||||
var ohne_netz := 0
|
||||
for w in welt().bewohner:
|
||||
ohne_netz += int(w.nav_fehl)
|
||||
p(ohne_netz == 0, "Auch sie laufen auf dem Navigationsnetz der Insel (%d Rückfälle)" % ohne_netz)
|
||||
|
||||
# Ausgangslage wiederherstellen: das Dorf ist wieder leer
|
||||
Game.completed.clear()
|
||||
@@ -583,7 +604,7 @@ func test_hoersaal() -> void:
|
||||
basis_da = false
|
||||
p(basis_da, "Die %d Basiswege werden gelehrt" % 1)
|
||||
var lernbar := 0
|
||||
for c in ui()._content.get_children():
|
||||
for c in _alle_kinder(ui()._content):
|
||||
if c is Button and (c as Button).text.begins_with("Diesen Weg"):
|
||||
lernbar += 1
|
||||
p(lernbar <= 1, "Und höchstens einer davon ist zu lernen (%d)" % lernbar)
|
||||
@@ -726,6 +747,237 @@ func test_reise_zur_erde() -> void:
|
||||
"%d Menschen leben in der Stadt" % welt().humans.size())
|
||||
|
||||
|
||||
## Beide Welten bleiben bestehen: wer zurückkehrt, findet dieselbe Stadt, und
|
||||
## die Menschen haben derweil ihren Tag weitergelebt.
|
||||
func test_welten_bleiben() -> void:
|
||||
abschnitt("Die Welten bleiben bestehen")
|
||||
var erde_vorher: Node3D = welt()
|
||||
var mensch: Bewohner = welt().humans[0]
|
||||
Game.time_h = 9.5
|
||||
schritte(20, 0.05, 9.5)
|
||||
var stand: Vector3 = mensch.global_position
|
||||
main._traumwelt()
|
||||
await get_tree().process_frame
|
||||
p(welt() is DreamWorld and is_instance_valid(erde_vorher) and not erde_vorher.visible,
|
||||
"Im Traum bleibt die Erde bestehen, nur unsichtbar")
|
||||
var himmel := 0
|
||||
for we in get_tree().root.find_children("*", "WorldEnvironment", true, false):
|
||||
if (we as WorldEnvironment).environment != null:
|
||||
himmel += 1
|
||||
p(himmel == 1, "Genau ein Himmel ist aktiv (%d)" % himmel)
|
||||
for i in range(400):
|
||||
Game.time_h = 12.5
|
||||
for m in erde_vorher.humans:
|
||||
m._process(0.05)
|
||||
main._erde()
|
||||
await get_tree().process_frame
|
||||
p(welt() == erde_vorher, "Zurück auf der Erde ist es dieselbe Stadt")
|
||||
p(mensch.global_position.distance_to(stand) > 3.0,
|
||||
"%s hat derweil ihren Tag weitergelebt (%.0f m weiter)" % [mensch.data["name"],
|
||||
mensch.global_position.distance_to(stand)])
|
||||
p(main.player.collision_mask == 2, "Der Geist stößt nur an die Erde, nicht an die unsichtbare Insel")
|
||||
|
||||
|
||||
## Spuren in der Wohnung: wer hineingeht und hinsieht, kennt die Ursache und
|
||||
## weiß, welche Stunden im Tag sich lohnen.
|
||||
func test_spuren() -> void:
|
||||
abschnitt("Spuren in der Wohnung")
|
||||
if not main.auf_erde:
|
||||
main._erde()
|
||||
await get_tree().process_frame
|
||||
ui().schliessen()
|
||||
var sp: Player = main.player
|
||||
sp.folgt = null
|
||||
var mensch: Bewohner = welt().humans[0]
|
||||
var id := str(mensch.data["id"])
|
||||
var heim := str(mensch.data["heim"])
|
||||
var ursachen: Array = mensch.data["ursachen"]
|
||||
Game.active_mission = id
|
||||
Game.spuren.erase(id)
|
||||
var spuren: Array = []
|
||||
for n in get_tree().get_nodes_in_group("interactable"):
|
||||
if n.get_meta("kind", "") == "spur" and str(n.get_meta("human")) == id:
|
||||
spuren.append(n)
|
||||
p(spuren.size() >= 1 and spuren.size() <= ursachen.size(),
|
||||
"In %s liegen %d Spuren, je Ursache höchstens eine (%d Ursachen)" %
|
||||
[HumanDB.PLACES[heim]["name"], spuren.size(), ursachen.size()])
|
||||
var c := Orte.center(HumanDB.PLACES, heim)
|
||||
var g: Array = HumanDB.PLACES[heim]["size"]
|
||||
var drinnen := 0
|
||||
for n in spuren:
|
||||
var q: Vector3 = (n as Node3D).global_position
|
||||
if absf(q.x - c.x) < float(g[0]) * 0.5 and absf(q.z - c.z) < float(g[1]) * 0.5 \
|
||||
and absf(q.y) < 2.0:
|
||||
drinnen += 1
|
||||
p(drinnen == spuren.size(), "Alle liegen in seiner Wohnung")
|
||||
if spuren.is_empty():
|
||||
Game.active_mission = ""
|
||||
return
|
||||
var spur: Node3D = spuren[0]
|
||||
var u := str(spur.get_meta("arg"))
|
||||
p(not Game.ursache_bekannt(id, u), "Vorher ist die Ursache '%s' unbekannt" % Ursachen.LISTE[u]["name"])
|
||||
var verdaechtig_vorher := 0
|
||||
for nr in range(Game.slots(id).size()):
|
||||
if Game.slot_zustand(id, nr) == Game.Stunde.VERDAECHTIG:
|
||||
verdaechtig_vorher += 1
|
||||
sp.global_position = spur.global_position + Vector3(0.8, 0.3, 0.8)
|
||||
sp.velocity = Vector3.ZERO
|
||||
main._process(0.05)
|
||||
p(main._fokus == spur and ui()._hinweis.text.contains("Spur"),
|
||||
"Vor der Spur zeigt der Hinweis sie an: %s" % ui()._hinweis.text)
|
||||
main._interagieren()
|
||||
p(Game.spur_bekannt(id, u) and Game.ursache_bekannt(id, u),
|
||||
"E liest die Spur: die Ursache '%s' steht im Journal" % Ursachen.LISTE[u]["name"])
|
||||
var verdaechtig := 0
|
||||
for nr in range(Game.slots(id).size()):
|
||||
if Game.slot_zustand(id, nr) == Game.Stunde.VERDAECHTIG:
|
||||
verdaechtig += 1
|
||||
p(verdaechtig_vorher == 0 and verdaechtig >= 1,
|
||||
"Die Stunden dieser Ursache sind jetzt verdächtig (%d), gesehen sind sie nicht" % verdaechtig)
|
||||
main._process(0.05)
|
||||
ui()._ablauf_aktualisieren()
|
||||
p(ui()._ablauf_titel.text.contains("verdächtig"), "Die Zeitleiste sagt es: %s" % ui()._ablauf_titel.text)
|
||||
p(not Game.spur_lesen(id, u), "Noch einmal lesen bringt nichts Neues")
|
||||
Game.spuren.erase(id)
|
||||
Game.active_mission = ""
|
||||
|
||||
|
||||
## Wer mit Mission zur Erde reist, landet vor der Tür seines Ziels; ein Knopf
|
||||
## bringt ihn jederzeit dorthin.
|
||||
func test_ankunft() -> void:
|
||||
abschnitt("Ankunft beim Ziel")
|
||||
var ziel: Bewohner = welt().humans[0]
|
||||
var id := str(ziel.data["id"])
|
||||
Game.active_mission = id
|
||||
main._traumwelt()
|
||||
await get_tree().process_frame
|
||||
main._erde()
|
||||
await get_tree().process_frame
|
||||
var nah: float = _xz(main.player.global_position).distance_to(_xz(ziel.global_position))
|
||||
var tuer := Orte.door(HumanDB.PLACES, ziel.place) if HumanDB.PLACES.has(ziel.place) \
|
||||
and float(HumanDB.PLACES[ziel.place]["size"][0]) > 0.0 else ziel.global_position
|
||||
var vor_tuer: float = _xz(main.player.global_position).distance_to(_xz(tuer))
|
||||
p(minf(nah, vor_tuer) < 4.0 and main.player.global_position.y > -0.5,
|
||||
"Mit Mission landet man vor der Tür von %s (%.1f m)" % [ziel.data["name"], minf(nah, vor_tuer)])
|
||||
var andere: Bewohner = welt().humans[3]
|
||||
main.hinfahren(str(andere.data["id"]))
|
||||
var d2: float = _xz(main.player.global_position).distance_to(_xz(andere.global_position))
|
||||
p(d2 < 12.0 and main.auf_erde, "„Hinfahren“ setzt einen zu %s (%.1f m)" % [andere.data["name"], d2])
|
||||
main._traumwelt()
|
||||
await get_tree().process_frame
|
||||
main.hinfahren(id)
|
||||
p(main.auf_erde, "Aus dem Traum heraus geht es ebenfalls, über das Tor")
|
||||
Game.active_mission = ""
|
||||
|
||||
|
||||
## Die Akte: ein Blatt je Mensch mit Person, Traum, Befund, Tag und Notizen.
|
||||
func test_akte() -> void:
|
||||
abschnitt("Die Akte")
|
||||
var ziel: Bewohner = welt().humans[0]
|
||||
var id := str(ziel.data["id"])
|
||||
var h := HumanDB.by_id(id)
|
||||
Game.active_mission = id
|
||||
Game.progress[id] = Game.progress.get(id, 0.0)
|
||||
Game.spuren.erase(id)
|
||||
Game.notizen.erase(id)
|
||||
ui().journal_panel("missionen")
|
||||
p(_knopf_da(str(h["name"])), "Im Journal liegt eine Akte je Mensch mit Mission")
|
||||
p(_druecke(str(h["name"])) and ui()._kind == "akte", "Sie lässt sich aufschlagen")
|
||||
var text := _panel_text()
|
||||
p(text.contains(str(h["name"])) and text.contains(str(h["traum"])) and text.contains(str(h["problem"])),
|
||||
"Oben stehen Person, Traum und was im Weg steht")
|
||||
p(text.contains("BEFUND") and text.contains("TAG") and text.contains("NOTIZEN"),
|
||||
"Darunter Befund, Tag und Notizen")
|
||||
p(text.contains("noch keine") and text.contains("noch nichts gefunden"),
|
||||
"Ohne Spuren und Beobachtungen ist der Befund leer")
|
||||
p(_knopf_da("Hinfahren"), "Ein Knopf bringt einen vor die Tür")
|
||||
var u := str((h["ursachen"] as Array)[0])
|
||||
Game.spur_lesen(id, u)
|
||||
ui().akte_panel(id)
|
||||
text = _panel_text()
|
||||
p(text.contains(str(Ursachen.LISTE[u]["name"])) and text.contains("Spur gelesen"),
|
||||
"Eine gelesene Spur steht als Befund: %s" % Ursachen.LISTE[u]["name"])
|
||||
p(text.contains("Uhr —") and text.contains("Spur:"), "Und als Notiz mit Uhrzeit")
|
||||
ui().schliessen()
|
||||
main._traumwelt()
|
||||
await get_tree().process_frame
|
||||
ui().akte_panel(id)
|
||||
p(_druecke("Hinfahren") and main.auf_erde and not ui().offen,
|
||||
"„Hinfahren“ führt aus dem Traum vor die Tür von %s" % h["name"])
|
||||
Game.active_mission = ""
|
||||
Game.spuren.erase(id)
|
||||
Game.notizen.erase(id)
|
||||
|
||||
|
||||
## Passanten beleben die Straßen: ohne Namen, ohne Schild, ohne Mission.
|
||||
func test_passanten() -> void:
|
||||
abschnitt("Passanten beleben die Straßen")
|
||||
if not main.auf_erde:
|
||||
main._erde()
|
||||
await get_tree().process_frame
|
||||
p(welt().passanten.size() == HumanDB.PASSANTEN and welt().humans.size() == HumanDB.LIST.size(),
|
||||
"%d Passanten neben den %d Menschen mit Geschichte" % [welt().passanten.size(), welt().humans.size()])
|
||||
var unterwegs := 0
|
||||
var proben := 0
|
||||
for i in range(1200):
|
||||
schritte(1, 0.05, 9.0 + float(i) * 0.004)
|
||||
if i % 100 == 99:
|
||||
proben += 1
|
||||
for pz in welt().passanten:
|
||||
if not pz._path.is_empty():
|
||||
unterwegs += 1
|
||||
p(unterwegs >= proben, "Zu jeder Zeit ist im Schnitt mindestens einer unterwegs (%d in %d Proben)" %
|
||||
[unterwegs, proben])
|
||||
var schilder := 0
|
||||
for pz in welt().passanten:
|
||||
if (pz._label as Label3D).visible:
|
||||
schilder += 1
|
||||
p(schilder == 0, "Passanten tragen kein Schild")
|
||||
var pz0: Bewohner = welt().passanten[0]
|
||||
main.player.global_position = pz0.global_position + Vector3(0, 0.3, 0)
|
||||
main._process(0.05)
|
||||
p(main._fokus_mensch != pz0, "Und kommen nicht in den Fokus")
|
||||
|
||||
|
||||
## Die Traumwesen helfen: ein Bewohner flüstert, was in der Wohnung des
|
||||
## Menschen liegt, um den es gerade geht.
|
||||
func test_wesen_helfen() -> void:
|
||||
abschnitt("Die Traumwesen helfen")
|
||||
var merker: Array = Game.completed.duplicate()
|
||||
Game.completed.clear()
|
||||
Game.completed.append("mensch_1")
|
||||
Game.welt_erzeugen()
|
||||
main._traumwelt()
|
||||
await get_tree().process_frame
|
||||
p(welt().bewohner.size() == 1, "Mit einem erfüllten Traum wohnt ein Wesen hier")
|
||||
var wesen: Bewohner = welt().bewohner[0]
|
||||
p(str(wesen.data["rolle"]).contains("Traum"), "Es trägt, aus wessen Traum es kam: %s" % wesen.data["rolle"])
|
||||
var marke: Node3D = null
|
||||
for n in get_tree().get_nodes_in_group("interactable"):
|
||||
if n.get_meta("kind", "") == "wesen":
|
||||
marke = n
|
||||
p(marke != null, "Man kann es ansprechen")
|
||||
Game.active_mission = "mensch_0"
|
||||
Game.spuren.erase("mensch_0")
|
||||
main.player.set_mode(Player.Mode.TRAUM)
|
||||
main.player.global_position = wesen.global_position + Vector3(0.5, 0.3, 0.5)
|
||||
main.player.velocity = Vector3.ZERO
|
||||
main._process(0.05)
|
||||
p(main._fokus == marke and ui()._hinweis.text.contains("zuhören"), "Davor steht: zuhören")
|
||||
main._interagieren()
|
||||
var bekannt := 0
|
||||
for u in Game.ursachen_von("mensch_0"):
|
||||
if Game.spur_bekannt("mensch_0", str(u)):
|
||||
bekannt += 1
|
||||
p(bekannt == 1, "Es flüstert eine Spur des Menschen, um den es geht (%d bekannt)" % bekannt)
|
||||
Game.spuren.erase("mensch_0")
|
||||
Game.active_mission = ""
|
||||
Game.completed.assign(merker)
|
||||
Game.welt_erzeugen()
|
||||
main._erde() # die folgenden Abschnitte spielen auf der Erde
|
||||
await get_tree().process_frame
|
||||
|
||||
|
||||
func test_tagesablauf() -> void:
|
||||
abschnitt("Tagesablauf der Menschen")
|
||||
var mensch: Bewohner = null
|
||||
@@ -734,7 +986,10 @@ func test_tagesablauf() -> void:
|
||||
mensch = m
|
||||
p(mensch != null, "Der erste Mensch lebt auf der Erde: %s" % mensch.data["name"])
|
||||
|
||||
schritte(30, 0.05, 2.0)
|
||||
for i in range(3000): # die Welt bleibt bestehen: er kann weit weg sein
|
||||
schritte(1, 0.05, 2.0)
|
||||
if mensch.place == str(mensch.data["heim"]) and mensch._path.is_empty():
|
||||
break
|
||||
p(mensch.activity == "schlaeft", "Um 02:00 schläft er (%s)" % mensch.activity)
|
||||
p(_xz(mensch.global_position).distance_to(
|
||||
_xz(Orte.center(HumanDB.PLACES, str(mensch.data["heim"])))) < 5.0,
|
||||
@@ -791,12 +1046,11 @@ func test_tagesablauf() -> void:
|
||||
|
||||
func test_wege_machbar() -> void:
|
||||
abschnitt("Wege passen in die Zeitfenster")
|
||||
_wege_pruefen("Erde", HumanDB.PLACES, HumanDB.LIST, HumanDB.NETZ, HumanDB.ANKER)
|
||||
_wege_pruefen("Traumwelt", DreamDB.PLACES, DreamDB.LIST, DreamDB.NETZ, DreamDB.ANKER)
|
||||
_wege_pruefen("Erde", HumanDB.PLACES, HumanDB.LIST, main.erde.nav_map)
|
||||
_wege_pruefen("Traumwelt", DreamDB.PLACES, DreamDB.LIST, main.traum.nav_map)
|
||||
|
||||
|
||||
func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array,
|
||||
netz: Netz = null, anker: Dictionary = {}) -> void:
|
||||
func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array, karte: RID) -> void:
|
||||
var sek_pro_stunde := Game.DAY_SECONDS / 24.0
|
||||
var alles_ok := true
|
||||
var engste := 999.0
|
||||
@@ -808,8 +1062,7 @@ func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array,
|
||||
var nach: String = String(r[i][1])
|
||||
if von == nach:
|
||||
continue
|
||||
var dauer := _weg_laenge(places, von, nach, m.get("etage", {}), netz, anker) \
|
||||
/ Bewohner.GEHTEMPO
|
||||
var dauer := _weg_laenge(places, von, nach, karte) / Bewohner.GEHTEMPO
|
||||
var slot_h: float = (float(r[i + 1][0]) - float(r[i][0])) if i + 1 < r.size() \
|
||||
else (24.0 - float(r[i][0]))
|
||||
var fenster := slot_h * sek_pro_stunde
|
||||
@@ -824,21 +1077,28 @@ func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array,
|
||||
p(alles_ok, "%s — jeder Weg passt, engster Fall: %s" % [welt_name, engste_text])
|
||||
|
||||
|
||||
func _weg_laenge(places: Dictionary, von: String, nach: String, etagen: Dictionary,
|
||||
netz: Netz = null, anker: Dictionary = {}) -> float:
|
||||
var pts: Array = Orte.route_voll(places, von, nach, Vector3.ZERO, etagen, netz, anker)
|
||||
## Weglänge über das Navigationsnetz der Welt -- so, wie die Menschen wirklich laufen.
|
||||
func _weg_laenge(places: Dictionary, von: String, nach: String, karte: RID) -> float:
|
||||
var a := Orte.center(places, von)
|
||||
var b := Orte.center(places, nach)
|
||||
# freie Plätze: neben der Mitte stehen, wie die Bewohner auch (in der Mitte steht der Traumbaum)
|
||||
if float(places[von]["size"][0]) <= 0.0:
|
||||
a += Vector3(2.2, 0, 0)
|
||||
if float(places[nach]["size"][0]) <= 0.0:
|
||||
b += Vector3(2.2, 0, 0)
|
||||
var pts := NavigationServer3D.map_get_path(karte, a, b, true)
|
||||
if pts.size() < 2:
|
||||
return 9999.0 # kein Weg: fällt als "passt nicht" auf
|
||||
var l := 0.0
|
||||
var pos := Orte.center(places, von) + Vector3(0, Orte.hoehe(int(etagen.get(von, 0))), 0)
|
||||
for pt in pts:
|
||||
l += pos.distance_to(pt)
|
||||
pos = pt
|
||||
for i in range(1, pts.size()):
|
||||
l += pts[i - 1].distance_to(pts[i])
|
||||
return l
|
||||
|
||||
|
||||
func test_geist() -> void:
|
||||
abschnitt("Geist-Bewegung")
|
||||
p(main.player.mode == Player.Mode.GEIST, "Spieler ist auf der Erde ein Geist")
|
||||
p(main.player.collision_mask == 1, "Der Geist stößt an Wände wie ein Mensch")
|
||||
p(main.player.collision_mask != 0, "Der Geist stößt an Wände wie ein Mensch")
|
||||
main.player.global_position = Vector3(0, 8.0, 0)
|
||||
main.player.velocity = Vector3(6, 0, 0)
|
||||
for i in range(60):
|
||||
@@ -848,9 +1108,14 @@ func test_geist() -> void:
|
||||
"Die Schwerkraft holt ihn auf den Boden (y = %.2f)" % main.player.global_position.y)
|
||||
|
||||
var lena: Bewohner = welt().humans[0]
|
||||
for i in range(1500): # erst, wenn sie irgendwo angekommen ist
|
||||
if lena._path.is_empty():
|
||||
break
|
||||
schritte(1)
|
||||
var davor: Vector3 = lena.global_position
|
||||
var uhr_geist := Game.time_h
|
||||
main.player.global_position = lena.global_position + Vector3(0, 1, 0.5)
|
||||
schritte(10)
|
||||
schritte(10, 0.05, uhr_geist)
|
||||
p(lena.global_position.distance_to(davor) < 2.0, "Menschen nehmen den Geist nicht wahr")
|
||||
|
||||
|
||||
@@ -954,7 +1219,7 @@ func _xz(v: Vector3) -> Vector2:
|
||||
|
||||
|
||||
func test_kamera_und_etagen() -> void:
|
||||
abschnitt("Feste Kamera, kleine Menschen, Stadt mit Stockwerken")
|
||||
abschnitt("Feste Kamera, kleine Menschen, geschlossene Häuser")
|
||||
ui().schliessen()
|
||||
var sp: Player = main.player
|
||||
sp.set_mode(Player.Mode.GEIST)
|
||||
@@ -994,40 +1259,23 @@ func test_kamera_und_etagen() -> void:
|
||||
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)
|
||||
# Geschlossene Häuser: einstöckig, ein Raum mit Tür
|
||||
var eng := 0
|
||||
for key in HumanDB.PLACES:
|
||||
var sz: Array = HumanDB.PLACES[key]["size"]
|
||||
if float(sz[0]) > 0.0 and minf(float(sz[0]), float(sz[1])) < Orte.TREPPE_TIEFE + 2.0:
|
||||
if float(sz[0]) > 0.0 and minf(float(sz[0]), float(sz[1])) < 6.0:
|
||||
eng += 1
|
||||
p(eng == 0, "Jedes Gebäude hat Platz für Treppenhaus und Decke (%d zu schmal)" % eng)
|
||||
p(eng == 0, "Jedes Gebäude ist breit genug für Tür und Möbel (%d zu schmal)" % eng)
|
||||
for b in welt()._bauten:
|
||||
for mi in Prims._meshes_von(b):
|
||||
var bm := (mi as MeshInstance3D).mesh as BoxMesh
|
||||
if bm != null and (bm.size.x <= 0.0 or bm.size.y <= 0.0 or bm.size.z <= 0.0):
|
||||
eng += 1
|
||||
p(eng == 0, "Keine Platte mit negativer Größe")
|
||||
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 oben: Bewohner = welt().humans[0]
|
||||
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):
|
||||
@@ -1035,80 +1283,34 @@ func test_kamera_und_etagen() -> void:
|
||||
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)
|
||||
p(zuhause and absf(oben.global_position.y) < 0.3,
|
||||
"Nachts liegt %s zu Hause, ebenerdig (y = %.1f)" % [oben.data["name"], oben.global_position.y])
|
||||
|
||||
# Etagenschnitt
|
||||
# Schnitt: drinnen verschwinden Dach und die Wände zur Kamera
|
||||
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)
|
||||
p(not bau.is_empty() and (bau["teile"] as Array).size() == 1 and (bau["waende"] as Array).size() >= 4,
|
||||
"Sein Haus steht mit Dach und Wänden da")
|
||||
sp.global_position = c + Vector3(0, 0.3, 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]:
|
||||
for w in bau["waende"]:
|
||||
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(),
|
||||
p(not (bau["teile"][0] as Node3D).visible, "Steht der Geist im Haus, verschwindet das Dach")
|
||||
p(wand_weg >= 2 and wand_weg < (bau["waende"] 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()])
|
||||
var stufen: Array = Orte.treppen_lauf(HumanDB.PLACES, heim, 0)
|
||||
sp.global_position = (stufen[0] as Vector3) + Vector3(0, 0.3, 0)
|
||||
sp.velocity = Vector3.ZERO
|
||||
var lauf: Vector3 = stufen[1] - stufen[0]
|
||||
lauf.y = 0.0
|
||||
sp.yaw = atan2(-lauf.x, -lauf.z)
|
||||
Input.action_press("vor")
|
||||
var hoechste := 0.0
|
||||
for i in range(70):
|
||||
await get_tree().physics_frame
|
||||
hoechste = maxf(hoechste, sp.global_position.y)
|
||||
Input.action_release("vor")
|
||||
p(hoechste > Orte.ETAGE - 0.3,
|
||||
"Über die Treppe kommt der Geist in den ersten Stock (y = %.1f)" % hoechste)
|
||||
sp.global_position = t1 + Vector3(0, 0.3, 0)
|
||||
sp.velocity = Vector3.ZERO
|
||||
[wand_weg, (bau["waende"] as Array).size()])
|
||||
sp.global_position = c + Vector3(float(g[0]) + 4.0, 0.3, 0)
|
||||
await get_tree().physics_frame
|
||||
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]:
|
||||
var alles := (bau["teile"][0] as Node3D).visible
|
||||
for w in bau["waende"]:
|
||||
if not (w as Node3D).visible:
|
||||
alles = false
|
||||
p(alles, "Draußen steht das Haus wieder ganz")
|
||||
@@ -1124,6 +1326,8 @@ func test_kamera_und_etagen() -> void:
|
||||
p(sp.folgt == null, "Noch ein Klick löst ihn wieder")
|
||||
|
||||
# Anlauf: von weit weg zu Fuß über die Straße bis in seine Wohnung
|
||||
for m in welt().humans:
|
||||
m.nav_fehl = 0 # frühere Tests haben Leute versetzt
|
||||
var daheim := false
|
||||
for i in range(3000):
|
||||
schritte(1, 0.05, 2.0)
|
||||
@@ -1134,9 +1338,11 @@ func test_kamera_und_etagen() -> void:
|
||||
sp.velocity = Vector3.ZERO
|
||||
sp.spruenge = 0
|
||||
sp.anhaengen(oben)
|
||||
p(daheim and sp._anlauf.size() >= 4 and (sp._anlauf.back() as Vector3).y > Orte.ETAGE - 0.1,
|
||||
"Von weit weg plant der Geist den Weg über die Straße in seine Etage (%d Punkte)" %
|
||||
sp._anlauf.size())
|
||||
await get_tree().physics_frame
|
||||
await get_tree().physics_frame
|
||||
p(daheim and sp._leine_pfad.size() >= 4,
|
||||
"Von weit weg plant das Navigationsnetz den Weg bis in seine Wohnung (%d Punkte)" %
|
||||
sp._leine_pfad.size())
|
||||
var angekommen := false
|
||||
for i in range(2400):
|
||||
await get_tree().physics_frame
|
||||
@@ -1156,9 +1362,82 @@ func test_kamera_und_etagen() -> void:
|
||||
p((oben._label as Label3D).visible and fremde_schilder == 0,
|
||||
"Nur der Verfolgte trägt ein Schild, die anderen nicht (%d fremde)" % fremde_schilder)
|
||||
sp.folgt = null
|
||||
var ohne_netz := 0
|
||||
for m in welt().humans:
|
||||
ohne_netz += int(m.nav_fehl)
|
||||
p(ohne_netz == 0, "Alle Wege der Menschen kamen vom Navigationsnetz (%d Rückfälle)" % ohne_netz)
|
||||
Game.time_h = uhr
|
||||
|
||||
|
||||
## Wie ein Spieler, der einfach losläuft: 90 Sekunden kreuz und quer durch die
|
||||
## Stadt, bei Wand Richtung wechseln. Dabei darf er nie hängen bleiben, nie
|
||||
## unter den Boden und nie über die Dächer geraten.
|
||||
func test_zufallslauf() -> void:
|
||||
abschnitt("Freies Herumlaufen: nie eingeklemmt, nie unter dem Boden")
|
||||
if not main.auf_erde:
|
||||
main._erde()
|
||||
await get_tree().process_frame
|
||||
ui().schliessen()
|
||||
var sp: Player = main.player
|
||||
sp.set_mode(Player.Mode.GEIST)
|
||||
sp.look_enabled = true
|
||||
sp.folgt = null
|
||||
sp.spruenge = 0
|
||||
sp.global_position = Vector3(0, 0.3, 12.0)
|
||||
sp.velocity = Vector3.ZERO
|
||||
var rng := RandomNumberGenerator.new()
|
||||
rng.seed = 4711
|
||||
var richtungen := [["vor"], ["vor", "rechts"], ["rechts"], ["zurueck", "rechts"],
|
||||
["zurueck"], ["zurueck", "links"], ["links"], ["vor", "links"]]
|
||||
var kurs := 0
|
||||
var letzte := sp.global_position
|
||||
var tiefster := 0.0
|
||||
var hoechster := 0.0
|
||||
var still := 0
|
||||
var laengste_still := 0
|
||||
var gesamt := 0.0
|
||||
for runde in range(150):
|
||||
for t in richtungen[kurs]:
|
||||
Input.action_press(t)
|
||||
var gemeldet := false
|
||||
for i in range(36):
|
||||
await get_tree().physics_frame
|
||||
if sp.global_position.y < -0.2 and not gemeldet:
|
||||
gemeldet = true
|
||||
var k := sp.get_last_slide_collision()
|
||||
var txt := "-"
|
||||
if k != null and k.get_collider() != null:
|
||||
txt = str((k.get_collider() as Node).get_parent().global_position)
|
||||
print(" [unter dem Boden] Runde %d, %s, Kollision bei %s" % [runde, str(sp.global_position), txt])
|
||||
for t in richtungen[kurs]:
|
||||
Input.action_release(t)
|
||||
var bewegt := _xz(sp.global_position).distance_to(_xz(letzte))
|
||||
if bewegt < 0.3 and still >= 2:
|
||||
print(" [steht] Runde %d bei %s, Kurs %d" % [runde, str(sp.global_position), kurs])
|
||||
gesamt += bewegt
|
||||
if bewegt < 0.3:
|
||||
still += 1
|
||||
laengste_still = maxi(laengste_still, still)
|
||||
kurs = (kurs + 3 + rng.randi_range(0, 2)) % 8 # umdrehen, leicht versetzt
|
||||
else:
|
||||
still = 0
|
||||
if rng.randf() < 0.2:
|
||||
kurs = rng.randi_range(0, 7)
|
||||
letzte = sp.global_position
|
||||
tiefster = minf(tiefster, sp.global_position.y)
|
||||
hoechster = maxf(hoechster, sp.global_position.y)
|
||||
p(gesamt > 200.0, "Er kommt herum (%.0f m in 90 s)" % gesamt)
|
||||
p(laengste_still <= 3, "Nie länger als drei Züge ohne Bewegung (längste Pause %d)" % laengste_still)
|
||||
p(tiefster > -0.5, "Nie unter dem Boden (tiefster y %.2f)" % tiefster)
|
||||
p(hoechster < 6.0, "Nie über den Dächern (höchster y %.1f)" % hoechster)
|
||||
p(sp.spruenge == 0, "Nie eingeklemmt -- die Stadt musste ihn nicht befreien (%d)" % sp.spruenge)
|
||||
var schwebend := 0
|
||||
for m in welt().humans:
|
||||
if m._path.is_empty() and m.global_position.y > 0.4:
|
||||
schwebend += 1
|
||||
p(schwebend == 0, "Kein stehender Mensch schwebt über dem Boden (%d)" % schwebend)
|
||||
|
||||
|
||||
## 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:
|
||||
@@ -1671,7 +1950,9 @@ func test_eigene_menschen() -> void:
|
||||
p(stumm.is_empty(), "Bei allen %d Menschen zeigt sich jede Ursache im Tag%s" %
|
||||
[HumanDB.LIST.size(), "" if stumm.is_empty() else " — nicht: " + ", ".join(stumm)])
|
||||
p(Game.machbar(id) or true, "Bereit fürs Brett")
|
||||
_wege_pruefen("Stadt mit eigenem Menschen", HumanDB.PLACES, HumanDB.LIST)
|
||||
main._erde()
|
||||
await get_tree().process_frame
|
||||
_wege_pruefen("Stadt mit eigenem Menschen", HumanDB.PLACES, HumanDB.LIST, main.erde.nav_map)
|
||||
|
||||
# --- Die Wörter verändern, wie er sich verhält
|
||||
p(Game.verhalten(id, "abwehr") >= 1, "Stur: Abwehr %d" % Game.verhalten(id, "abwehr"))
|
||||
@@ -2238,10 +2519,26 @@ func _dorf_platz_pruefen() -> void:
|
||||
for i in range(stufen):
|
||||
Game.completed.append("stufe_%d" % i)
|
||||
Game.welt_erzeugen()
|
||||
_wege_pruefen("Traumdorf mit %d Häusern" % stufen, DreamDB.PLACES, DreamDB.LIST,
|
||||
DreamDB.NETZ, DreamDB.ANKER)
|
||||
main._traumwelt()
|
||||
await get_tree().process_frame
|
||||
_wege_pruefen("Traumdorf mit %d Häusern" % stufen, DreamDB.PLACES, DreamDB.LIST, main.traum.nav_map)
|
||||
# Jede Haustür muss zu Fuß erreichbar sein -- keine Klippe vor der Tür
|
||||
var platz := Orte.center(DreamDB.PLACES, "lichtung") + Vector3(2.2, 0, 0)
|
||||
var unerreichbar: Array = []
|
||||
for key in DreamDB.PLACES:
|
||||
if not str(key).begins_with("haus_"):
|
||||
continue
|
||||
var t := Orte.door(DreamDB.PLACES, key)
|
||||
t.y = DreamDB.hoehe_bei(t.x, t.z)
|
||||
var pf := NavigationServer3D.map_get_path(main.traum.nav_map, platz, t, true)
|
||||
if pf.is_empty() or pf[pf.size() - 1].distance_to(t) > 1.5:
|
||||
unerreichbar.append("%s (Terrasse %.1f)" % [key, DreamDB.terrasse(key)])
|
||||
p(unerreichbar.is_empty(), "Bei %d Häusern ist jede Haustür vom Dorfplatz aus zu Fuß erreichbar%s" %
|
||||
[stufen, "" if unerreichbar.is_empty() else " — nicht: " + ", ".join(unerreichbar)])
|
||||
main._erde()
|
||||
await get_tree().process_frame
|
||||
_wege_pruefen("Stadt mit %d Menschen" % HumanDB.LIST.size(), HumanDB.PLACES,
|
||||
HumanDB.LIST, HumanDB.NETZ, HumanDB.ANKER)
|
||||
HumanDB.LIST, main.erde.nav_map)
|
||||
Game.completed.assign(merker)
|
||||
Game.welt_erzeugen()
|
||||
|
||||
@@ -2364,8 +2661,8 @@ func test_steuerung() -> void:
|
||||
var klotz: Dictionary = {}
|
||||
var richtung := Vector3.ZERO
|
||||
for b in welt()._bauten:
|
||||
if not bool(b.get("voll", false)):
|
||||
continue
|
||||
if not (b["waende"] as Array).is_empty():
|
||||
continue # ein Haus mit Wänden ist kein Klotz
|
||||
for pz in HumanDB.PARZELLEN:
|
||||
if (pz["pos"] as Vector3).distance_to(b["c"]) < 0.1:
|
||||
klotz = b
|
||||
@@ -2697,7 +2994,8 @@ func test_dorf_organisch() -> void:
|
||||
sektoren_12 = sekt.size()
|
||||
if stufe == stufens_probe():
|
||||
var flaeche := PI * pow(Game.insel_radius(), 2.0)
|
||||
if flaeche / float(stufe) > 2200.0:
|
||||
# Terrassen verschiedener Höhe brauchen Böschungen dazwischen: das kostet Fläche
|
||||
if flaeche / float(stufe) > 3300.0:
|
||||
dichte = "%.0f m² je Haus" % (flaeche / float(stufe))
|
||||
|
||||
p(ueberlappung == "", "Über %d Ausbaustufen steht kein Gebäude im anderen%s" %
|
||||
|
||||
Reference in New Issue
Block a user