update
This commit is contained in:
@@ -1,24 +1,27 @@
|
|||||||
78098b89c88a74ebe8c20117b8754620
|
78098b89c88a74ebe8c20117b8754620
|
||||||
::res://::1787996624
|
::res://::1788113016
|
||||||
anforderungen.md::TextFile::-1::1788038502::0::1::::<><><>0<>0<><>::
|
anforderungen.md::TextFile::-1::1788165575::0::1::::<><><>0<>0<><>::
|
||||||
README.md::TextFile::-1::1788038596::0::1::::<><><>0<>0<><>::
|
README.md::TextFile::-1::1788165599::0::1::::<><><>0<>0<><>::
|
||||||
::res://scenes/::1787989294
|
::res://scenes/::1787989294
|
||||||
main.tscn::PackedScene::-1::1787989294::0::1::::<><><>0<>0<><>::res://scripts/main.gd
|
main.tscn::PackedScene::-1::1787989294::0::1::::<><><>0<>0<><>::res://scripts/main.gd
|
||||||
::res://scripts/::1788039082
|
::res://scripts/::1788163668
|
||||||
bewohner.gd::GDScript::2125278938812678811::1788039082::0::1::::Bewohner<>Node3D<><>0<>0<><>::
|
bewohner.gd::GDScript::2125278938812678811::1788165374::0::1::::Bewohner<>Node3D<><>0<>0<><>::
|
||||||
dream_db.gd::GDScript::4217518896355437099::1788039046::0::1::::DreamDB<>RefCounted<><>0<>0<><>::
|
dream_db.gd::GDScript::4217518896355437099::1788164505::0::1::::DreamDB<>RefCounted<><>0<>0<><>::
|
||||||
dream_world.gd::GDScript::8279158278942867258::1788039140::0::1::::DreamWorld<>Node3D<><>0<>0<><>::
|
dream_world.gd::GDScript::8279158278942867258::1788164873::0::1::::DreamWorld<>Node3D<><>0<>0<><>::
|
||||||
earth_world.gd::GDScript::6086818627272428934::1788039160::0::1::::EarthWorld<>Node3D<><>0<>0<><>::
|
earth_world.gd::GDScript::6086818627272428934::1788165412::0::1::::EarthWorld<>Node3D<><>0<>0<><>::
|
||||||
game.gd::GDScript::3600922541500868814::1788038470::0::1::::<>Node<><>0<>0<><>::
|
game.gd::GDScript::3600922541500868814::1788165125::0::1::::<>Node<><>0<>0<><>::
|
||||||
human_db.gd::GDScript::412604892032291296::1788039057::0::1::::HumanDB<>RefCounted<><>0<>0<><>::
|
human_db.gd::GDScript::412604892032291296::1788165374::0::1::::HumanDB<>RefCounted<><>0<>0<><>::
|
||||||
main.gd::GDScript::1504009331368977934::1788039160::0::1::::<>Node3D<><>0<>0<><>::
|
main.gd::GDScript::1504009331368977934::1788121650::0::1::::<>Node3D<><>0<>0<><>::
|
||||||
orte.gd::GDScript::5536340784294498671::1788039046::0::1::::Orte<>RefCounted<><>0<>0<><>::
|
netz.gd::GDScript::8422943321868648339::1788163667::0::1::::Netz<>RefCounted<><>0<>0<><>::
|
||||||
player.gd::GDScript::8724108548430371398::1787990898::0::1::::Player<>CharacterBody3D<><>0<>0<><>::
|
orte.gd::GDScript::5536340784294498671::1788163839::0::1::::Orte<>RefCounted<><>0<>0<><>::
|
||||||
prims.gd::GDScript::322632462553361584::1787990855::0::1::::Prims<>RefCounted<><>0<>0<><>::
|
player.gd::GDScript::8724108548430371398::1788039718::0::1::::Player<>CharacterBody3D<><>0<>0<><>::
|
||||||
technique_db.gd::GDScript::4543286561661639701::1788039160::0::1::::TechDB<>RefCounted<><>0<>0<><>::
|
prims.gd::GDScript::322632462553361584::1788039797::0::1::::Prims<>RefCounted<><>0<>0<><>::
|
||||||
ui.gd::GDScript::843471304695966286::1788039160::0::1::::UI<>CanvasLayer<><>0<>0<><>::
|
technique_db.gd::GDScript::4543286561661639701::1788120975::0::1::::TechDB<>RefCounted<><>0<>0<><>::
|
||||||
::res://tests/::1787990698
|
ui.gd::GDScript::843471304695966286::1788122559::0::1::::UI<>CanvasLayer<><>0<>0<><>::
|
||||||
shots.gd::GDScript::5999651346126871724::1788038553::0::1::::<>Node<><>0<>0<><>::
|
::res://tests/::1788164088
|
||||||
|
probe.gd::GDScript::5788011518062194748::1788165350::0::1::::<>Node<><>0<>0<><>::
|
||||||
|
probe.tscn::PackedScene::-1::1788163874::0::1::::<><><>0<>0<><>::res://tests/probe.gd
|
||||||
|
shots.gd::GDScript::5999651346126871724::1788118441::0::1::::<>Node<><>0<>0<><>::
|
||||||
shots.tscn::PackedScene::-1::1787990528::0::1::::<><><>0<>0<><>::res://tests/shots.gd
|
shots.tscn::PackedScene::-1::1787990528::0::1::::<><><>0<>0<><>::res://tests/shots.gd
|
||||||
verify.gd::GDScript::4392606218392155269::1788038533::0::1::::<>Node<><>0<>0<><>::
|
verify.gd::GDScript::4392606218392155269::1788165669::0::1::::<>Node<><>0<>0<><>::
|
||||||
verify.tscn::PackedScene::-1::1787989983::0::1::::<><><>0<>0<><>::res://tests/verify.gd
|
verify.tscn::PackedScene::-1::1787989983::0::1::::<><><>0<>0<><>::res://tests/verify.gd
|
||||||
|
|||||||
@@ -40,6 +40,14 @@ list=[{
|
|||||||
"path": "res://scripts/human_db.gd"
|
"path": "res://scripts/human_db.gd"
|
||||||
}, {
|
}, {
|
||||||
"base": &"RefCounted",
|
"base": &"RefCounted",
|
||||||
|
"class": &"Netz",
|
||||||
|
"icon": "",
|
||||||
|
"is_abstract": false,
|
||||||
|
"is_tool": false,
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://scripts/netz.gd"
|
||||||
|
}, {
|
||||||
|
"base": &"RefCounted",
|
||||||
"class": &"Orte",
|
"class": &"Orte",
|
||||||
"icon": "",
|
"icon": "",
|
||||||
"is_abstract": false,
|
"is_abstract": false,
|
||||||
|
|||||||
Binary file not shown.
114
README.md
114
README.md
@@ -11,7 +11,7 @@ sie allein nicht bewegt bekommen. Du wirst zu ihnen geschickt.
|
|||||||
godot --path .
|
godot --path .
|
||||||
|
|
||||||
Steuerung: **WASD** bewegen · **Maus** umsehen · **E** interagieren ·
|
Steuerung: **WASD** bewegen · **Maus** umsehen · **E** interagieren ·
|
||||||
**J** Journal · **Esc** Maus freigeben.
|
**J** Journal · **1/2/3** Tempo · **Esc** Maus freigeben.
|
||||||
In der Traumwelt springt **Leertaste**; auf der Erde steigt sie und **Umschalt**
|
In der Traumwelt springt **Leertaste**; auf der Erde steigt sie und **Umschalt**
|
||||||
lässt sinken.
|
lässt sinken.
|
||||||
|
|
||||||
@@ -21,7 +21,8 @@ lässt sinken.
|
|||||||
|
|
||||||
Das Journal führt Buch — es lehrt und vergibt nichts.
|
Das Journal führt Buch — es lehrt und vergibt nichts.
|
||||||
|
|
||||||
* **Techniken** — was du gelernt hast, mit Stufe und Erfahrung.
|
* **Techniken** — was du gelernt hast, mit Stufe und Erfahrung. Besser werden
|
||||||
|
sie nur draußen.
|
||||||
* **Missionen** — die Menschen, für die du dich entschieden hast, jeweils mit
|
* **Missionen** — die Menschen, für die du dich entschieden hast, jeweils mit
|
||||||
ihrem **Regelwerk**.
|
ihrem **Regelwerk**.
|
||||||
|
|
||||||
@@ -29,18 +30,29 @@ Das Journal führt Buch — es lehrt und vergibt nichts.
|
|||||||
|
|
||||||
Jede Tätigkeit im Tag eines Menschen kann eine Technik auslösen: *wenn Lena
|
Jede Tätigkeit im Tag eines Menschen kann eine Technik auslösen: *wenn Lena
|
||||||
zögert, dann Pomodoro*. Wechselt sie in die Tätigkeit, greift die Regel von
|
zögert, dann Pomodoro*. Wechselt sie in die Tätigkeit, greift die Regel von
|
||||||
allein, solange du auf der Erde bist. Neben jeder Wahl steht die Erfolgschance
|
allein, solange du auf der Erde bist.
|
||||||
für genau diesen Moment — du siehst also vorher, ob eine Regel taugt.
|
|
||||||
|
|
||||||
Jede Technik hält nur einen Posten — sie an einen neuen Moment zu hängen nimmt
|
**Ob eine Regel taugt, sagt dir niemand.** Keine Prozentzahl, kein „passt zum
|
||||||
sie vom alten weg. Mehr Momente abzudecken heißt also, mehr Techniken zu
|
Thema", keine Liste guter Momente. Du bekommst nur die Technik selbst: was sie
|
||||||
kennen, und die gibt es nur auf der gewachsenen Insel.
|
tut, wann sie passt, warum sie hilft. Und den Menschen: seinen Traum und das,
|
||||||
|
was im Weg steht. Beides zusammenzubringen ist das Spiel.
|
||||||
|
|
||||||
|
Eine Technik darf an beliebig vielen Tätigkeiten hängen. Sie überall
|
||||||
|
hinzuhängen ist trotzdem falsch: im falschen Moment greift sie kaum und wirft
|
||||||
|
den Traum um 14 % zurück. Nach jedem Einsatz braucht sie außerdem 30 Sekunden
|
||||||
|
Ruhe — ein Fehlgriff verbrennt also den Platz, den ein guter Moment gehabt
|
||||||
|
hätte. Mehr Momente sauber abzudecken heißt, mehr Techniken zu kennen, und die
|
||||||
|
gibt es nur auf der gewachsenen Insel.
|
||||||
|
|
||||||
So laufen die zwei Schleifen:
|
So laufen die zwei Schleifen:
|
||||||
|
|
||||||
Techniken lernen → Regelwerk anpassen → Probleme lösen
|
Techniken lernen → Regelwerk anpassen → Probleme lösen
|
||||||
Träume erfüllen → Dorf wächst → neue Wege entdecken
|
Träume erfüllen → Dorf wächst → neue Wege entdecken
|
||||||
|
|
||||||
|
Eine Regel greift immer wieder, solange der Moment anhält — im Takt des
|
||||||
|
Cooldowns. Wie viel ein Tag einbringt, hängt also daran, wie lange ein Mensch
|
||||||
|
in der passenden Tätigkeit bleibt.
|
||||||
|
|
||||||
Ein erfüllter Traum bringt ein Wesen mit seinem Haus ins Dorf, und in diesem
|
Ein erfüllter Traum bringt ein Wesen mit seinem Haus ins Dorf, und in diesem
|
||||||
Haus wird ein neuer Weg gelehrt. Zu Beginn kennt das Dorf nur einen.
|
Haus wird ein neuer Weg gelehrt. Zu Beginn kennt das Dorf nur einen.
|
||||||
|
|
||||||
@@ -48,42 +60,74 @@ Schlafende sind unerreichbar, dieselbe Technik wirkt bei Wiederholung
|
|||||||
schwächer, ein Fehlversuch macht kurz dicht. Von Hand geht es weiter: nah
|
schwächer, ein Fehlversuch macht kurz dicht. Von Hand geht es weiter: nah
|
||||||
herangehen und **E** wirkt sofort.
|
herangehen und **E** wirkt sofort.
|
||||||
|
|
||||||
**Traumwelt** — eine schwebende Insel mit einem Dorf, das erzeugt wird
|
**Traumwelt** — eine schwebende Insel mit einem Dorf, das wächst
|
||||||
|
|
||||||
* Fest sind nur drei Dinge: der **Hörsaal der Wege**, der **Dorfplatz** und das
|
* Fest sind nur drei Dinge: der **Hörsaal der Wege**, der **Dorfplatz** und das
|
||||||
**Tor zur Erde**. Alles andere entsteht beim Wachsen.
|
**Tor zur Erde**. Alles andere entsteht beim Wachsen.
|
||||||
* **Die Insel beginnt kahl.** Ein Gebäude, kein Bewohner, ein einziger
|
* **Die Insel beginnt kahl.** Ein Gebäude, kein Bewohner, ein einziger
|
||||||
gelehrter Weg.
|
gelehrter Weg — und den bringt dein Wesen schon mit. Am Brett hängt also vom
|
||||||
* Jeder erfüllte Traum bringt einen Bewohner mit seinem Haus — Lage, Größe,
|
ersten Moment an eine Mission.
|
||||||
Farbe, Türrichtung, Einrichtung und Tagesablauf werden erzeugt, ringförmig
|
* Jeder erfüllte Traum bringt einen Bewohner mit seinem Haus. **In jedem Haus
|
||||||
nach außen. **In jedem Haus wird ein Weg gelehrt, den es vorher nirgends
|
wird ein Weg gelehrt, den es vorher nirgends gab.**
|
||||||
gab.** Ein Mensch, ein Haus, eine Technik.
|
* Das Haus setzt sich nicht auf einen Ring, sondern **sucht sich seinen Platz**:
|
||||||
* Die Insel dehnt sich mit, damit jedes Haus festen Grund hat. Das Dorf hört
|
gewertet werden Nachbarschaft, Nähe zu einem Weg, Nähe zum Dorfplatz und wie
|
||||||
nicht auf zu wachsen: die Kleinstadt schickt immer neue Menschen.
|
leer die Himmelsrichtung noch ist. Der beste von 64 Probepunkten gewinnt.
|
||||||
|
* Der Weg dorthin **dockt an der nächsten Stelle des bestehenden Netzes an** —
|
||||||
|
nicht an der Dorfmitte. Kreuzt die Gerade ein Haus, sucht ein Raster einen Weg
|
||||||
|
darum herum. Liegt ein Knoten nah, ist aber im Netz weit weg, kommt eine
|
||||||
|
Abkürzung dazu. Daraus entstehen Gabeln, Schleifen und krumme Gassen.
|
||||||
|
* Die Insel dehnt sich mit, Bäume füllen nach Poisson-Regel die freie Fläche.
|
||||||
|
Vorbild: Emilien et al., *Procedural Generation of Villages* (2012).
|
||||||
* **Schwarzes Brett** am Dorfplatz — hier hängen die Menschen aus, die nicht
|
* **Schwarzes Brett** am Dorfplatz — hier hängen die Menschen aus, die nicht
|
||||||
weiterkommen. Mission annehmen.
|
weiterkommen. **Ausgehängt wird nur, wem du mit dem, was du kannst, auch
|
||||||
|
helfen kannst:** mindestens eine gelernte Technik muss ihr Thema treffen und
|
||||||
|
in ihrem Tag einen Moment dafür finden. Die anderen bleiben verdeckt, bis du
|
||||||
|
den passenden Weg gelernt hast.
|
||||||
|
|
||||||
**Erde** — eine Kleinstadt, ein Tag dauert 6 Minuten
|
**Tempo** — **1**, **2**, **3**
|
||||||
|
|
||||||
|
Drei Stufen wie in Die Sims. Die Stufe ist nicht der Faktor:
|
||||||
|
|
||||||
|
Stufe 1 1× ein Tag in 6:00 Minuten
|
||||||
|
Stufe 2 3× ein Tag in 2:00 Minuten
|
||||||
|
Stufe 3 7× ein Tag in 0:51 Minuten
|
||||||
|
|
||||||
|
Über der Nacht des Menschen, dem du gerade hilfst, springt das Spiel von allein
|
||||||
|
auf Stufe 3 und fällt beim Aufwachen auf deine zurück — Schlafende erreicht
|
||||||
|
ohnehin keine Technik. Beschleunigt wird die ganze Simulation samt Cooldown und
|
||||||
|
Abwehr. Am Ausgang ändert sich dadurch nichts, nur an der Wartezeit.
|
||||||
|
|
||||||
|
**Erde** — ein Stadtbezirk nach japanischem Vorbild
|
||||||
|
|
||||||
|
* **Arterien** in einem groben, leicht versetzten Raster spannen Blöcke auf.
|
||||||
|
Am Blockrand stehen die Häuser direkt an der Straße, ohne Vorgarten. Ins
|
||||||
|
Blockinnere führt eine schmale Sackgasse — *roji* —, an der die hinteren
|
||||||
|
Parzellen hängen. Der mittlere Block bleibt Park.
|
||||||
|
* **Hier wächst nichts.** Alle 16 Menschen sind von Anfang an da und leben
|
||||||
|
ihren Tag: schlafen, arbeiten, grübeln, essen, spazieren. Wer wo wohnt,
|
||||||
|
arbeitet in der Nähe; das Café liegt in der Mitte.
|
||||||
* Du bist ein unsichtbarer Geist, schwebst und gehst durch Wände.
|
* Du bist ein unsichtbarer Geist, schwebst und gehst durch Wände.
|
||||||
* Vier Menschen leben ihren Tagesablauf: schlafen, arbeiten, grübeln, essen,
|
* Nah herangehen, **E** drücken, Technik wählen. Das Panel nennt für jede
|
||||||
spazieren.
|
Technik nur, ob Thema und Moment passen — keine Zahl.
|
||||||
* Nah herangehen, **E** drücken, Technik wählen. Das Panel zeigt für jede
|
|
||||||
Technik die Erfolgschance in genau dieser Situation.
|
|
||||||
* Die Säule im Park bringt dich zurück in die Traumwelt.
|
* Die Säule im Park bringt dich zurück in die Traumwelt.
|
||||||
|
|
||||||
## Erfolgschance
|
## Erfolgschance
|
||||||
|
|
||||||
8 % + 11 % je Fertigkeitsstufe
|
5 % + 9 % je Fertigkeitsstufe
|
||||||
+ 25 % wenn die Technik das Thema des Menschen trifft
|
+ 20 % wenn die Technik das Thema des Menschen trifft
|
||||||
+ 22 % wenn der Moment passt (z. B. Box-Atmung, während jemand angespannt ist)
|
+ 35 % wenn der Moment passt (z. B. Box-Atmung, während jemand angespannt ist)
|
||||||
− 30 % wenn der Mensch gerade abblockt
|
− 30 % wenn der Mensch gerade abblockt
|
||||||
begrenzt auf 5 … 95 %
|
begrenzt auf 5 … 95 %
|
||||||
|
|
||||||
Schlafende sind nicht erreichbar. Erfolg bringt Fortschritt und 2 EP, ein
|
Schlafende sind nicht erreichbar. Erfolg bringt Fortschritt und 2 EP, ein
|
||||||
Fehlversuch 1 EP und eine kurze Sperre. Dieselbe Technik wirkt bei Wiederholung
|
Fehlversuch 1 EP, −14 % Fortschritt und 18 Sekunden Abwehr. Jeder Einsatz sperrt
|
||||||
schwächer — es lohnt sich, mehrere zu lernen. 100 % erfüllen den Traum und
|
die Technik für 30 Sekunden. Dieselbe Technik wirkt bei Wiederholung schwächer —
|
||||||
bringen 3 Einsicht zurück.
|
es lohnt sich, mehrere zu lernen. 100 % erfüllen den Traum.
|
||||||
|
|
||||||
|
**Eine Technik wird nur besser, indem du sie anwendest.** Der Lehrort gibt sie
|
||||||
|
dir, mehr nicht: kein Üben, kein Trainieren, keine Währung, die man dagegen
|
||||||
|
eintauschen könnte. Wer eine Technik auf Stufe 5 will, muss sie draußen bei
|
||||||
|
Menschen einsetzen — auch Fehlversuche zählen.
|
||||||
|
|
||||||
## Aufbau
|
## Aufbau
|
||||||
|
|
||||||
@@ -91,8 +135,9 @@ bringen 3 Einsicht zurück.
|
|||||||
scenes/main.tscn Einstieg
|
scenes/main.tscn Einstieg
|
||||||
scripts/game.gd Zustand, Regelwerk, wachsende Welt, Uhr (Autoload)
|
scripts/game.gd Zustand, Regelwerk, wachsende Welt, Uhr (Autoload)
|
||||||
scripts/technique_db.gd die zwölf Techniken mit ihren Hinweisen
|
scripts/technique_db.gd die zwölf Techniken mit ihren Hinweisen
|
||||||
scripts/human_db.gd Kleinstadt, erzeugte Wohnhäuser und Menschen
|
scripts/human_db.gd Bezirk: Arterien, Blöcke, roji, Parzellen, Menschen
|
||||||
scripts/dream_db.gd Traumdorf: erzeugt Häuser, Bewohner und Lehrorte
|
scripts/dream_db.gd Traumdorf: wachsende Häuser, Wege, Bewohner, Lehrorte
|
||||||
|
scripts/netz.gd Wegenetz: Knoten, Kanten, Andocken, kürzester Weg
|
||||||
scripts/orte.gd Orte, Türen, Wege, Tätigkeiten (beide Welten)
|
scripts/orte.gd Orte, Türen, Wege, Tätigkeiten (beide Welten)
|
||||||
scripts/bewohner.gd Bewohner mit Tagesablauf, löst Regeln aus
|
scripts/bewohner.gd Bewohner mit Tagesablauf, löst Regeln aus
|
||||||
scripts/main.gd Weltenwechsel, Fokus, Interaktion
|
scripts/main.gd Weltenwechsel, Fokus, Interaktion
|
||||||
@@ -108,15 +153,18 @@ bringen 3 Einsicht zurück.
|
|||||||
godot --path . tests/shots.tscn # 28 Screenshots nach user://shots/
|
godot --path . tests/shots.tscn # 28 Screenshots nach user://shots/
|
||||||
|
|
||||||
`verify` fährt das echte Spiel: leeres Traumdorf, einziehende Wesen mit ihren
|
`verify` fährt das echte Spiel: leeres Traumdorf, einziehende Wesen mit ihren
|
||||||
Tagesabläufen,
|
Tagesabläufen, gewachsenes Dorf und erzeugter Bezirk,
|
||||||
Häuser betreten, Journal in beiden Welten, im Hörsaal lernen und vertiefen,
|
Häuser betreten, Journal in beiden Welten, im Hörsaal lernen,
|
||||||
Verbessern allein durchs Anwenden, Mission am Schwarzen Brett annehmen,
|
Verbessern allein durchs Anwenden, Mission am Schwarzen Brett annehmen,
|
||||||
Regelwerk bauen und einen Tag unbeaufsichtigt laufen lassen, zur Erde reisen,
|
Regelwerk bauen und einen Tag unbeaufsichtigt laufen lassen, zur Erde reisen,
|
||||||
Tagesabläufe über einen ganzen Tag, Erfolgschancen, Mission abschließen,
|
Tagesabläufe über einen ganzen Tag, Wirkung der Technikwahl, Cooldown,
|
||||||
|
verdeckte Erfolgschance, keine Passungshinweise, Mehrfachbelegung von Regeln,
|
||||||
|
Tempo und Nachtsprung,
|
||||||
|
nur lösbare Aushänge, Mission abschließen,
|
||||||
Insel wachsen lassen, Tastensteuerung. Beendet sich mit Code 1, wenn etwas
|
Insel wachsen lassen, Tastensteuerung. Beendet sich mit Code 1, wenn etwas
|
||||||
fehlt.
|
fehlt.
|
||||||
|
|
||||||
Drei Prüfungen messen statt zu behaupten. Über 24 Ausbaustufen darf sich kein
|
Mehrere Prüfungen messen statt zu behaupten. Über 24 Ausbaustufen darf sich kein
|
||||||
erzeugtes Gebäude mit einem anderen berühren und keines über den Inselrand
|
erzeugtes Gebäude mit einem anderen berühren und keines über den Inselrand
|
||||||
ragen; jeder Weg eines Bewohners muss in sein Zeitfenster passen — auch in
|
ragen; jeder Weg eines Bewohners muss in sein Zeitfenster passen — auch in
|
||||||
einem Dorf mit 24 Häusern und einer Stadt mit 27 Menschen. Und der erste Traum
|
einem Dorf mit 24 Häusern und einer Stadt mit 27 Menschen. Und der erste Traum
|
||||||
|
|||||||
@@ -9,7 +9,15 @@ Loops
|
|||||||
Funktionen
|
Funktionen
|
||||||
- Bewegung: Bewegen, Springen, Geist
|
- Bewegung: Bewegen, Springen, Geist
|
||||||
- Journal: Gelernte Techniken, Akzetierte Missionen
|
- Journal: Gelernte Techniken, Akzetierte Missionen
|
||||||
- Techniken: Lernen, Verbessern
|
- Techniken: Lernen am Lehrort, Verbessern durch Anwenden
|
||||||
- Missionen: Pipeline
|
- Missionen: Pipeline, nur lösbare werden angeboten
|
||||||
|
- Techniken: Cooldown, Fehlschlag kostet Fortschritt
|
||||||
|
- Erfolgschance und Passung bleiben verdeckt
|
||||||
|
- Eine Technik darf an mehreren Tätigkeiten hängen
|
||||||
- Menschen: Tagesablauf
|
- Menschen: Tagesablauf
|
||||||
- Prozedurale Generierung: Dorf, Erde
|
- Tempo: Stufe 1, 2, 3 wie in Die Sims (1×, 3×, 7×)
|
||||||
|
- Im Schlaf des Menschen automatisch Stufe 3
|
||||||
|
- Prozedurale Generierung
|
||||||
|
- Dorf: wächst Haus für Haus, verwinkelte Wege, grün und gemütlich
|
||||||
|
- Erde: Stadtbezirk nach japanischem Vorbild, Arterien und roji
|
||||||
|
- Auf der Erde sind alle Menschen von Anfang an da
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class_name Bewohner
|
|||||||
## Bewohner einer Welt: läuft seinen Tagesablauf ab. Auf der Erde kann er von
|
## Bewohner einer Welt: läuft seinen Tagesablauf ab. Auf der Erde kann er von
|
||||||
## einer Technik erreicht werden -- oder eben nicht.
|
## einer Technik erreicht werden -- oder eben nicht.
|
||||||
|
|
||||||
const GEHTEMPO := 6.0
|
const GEHTEMPO := 8.0
|
||||||
|
|
||||||
var data: Dictionary
|
var data: Dictionary
|
||||||
var idx: int = 0
|
var idx: int = 0
|
||||||
@@ -11,6 +11,8 @@ var place: String = ""
|
|||||||
var activity: String = ""
|
var activity: String = ""
|
||||||
var resist_left: float = 0.0
|
var resist_left: float = 0.0
|
||||||
var places: Dictionary = {}
|
var places: Dictionary = {}
|
||||||
|
var netz: Netz = null
|
||||||
|
var anker: Dictionary = {}
|
||||||
var mit_mission := false
|
var mit_mission := false
|
||||||
|
|
||||||
var _visual: Node3D
|
var _visual: Node3D
|
||||||
@@ -21,14 +23,18 @@ var _aura: MeshInstance3D
|
|||||||
var _strahl: MeshInstance3D
|
var _strahl: MeshInstance3D
|
||||||
var _path: Array = []
|
var _path: Array = []
|
||||||
var _letzte_aktivitaet: String = ""
|
var _letzte_aktivitaet: String = ""
|
||||||
|
var _seit_regel: float = 0.0
|
||||||
var _bob: float = 0.0
|
var _bob: float = 0.0
|
||||||
var _offset: Vector3
|
var _offset: Vector3
|
||||||
|
|
||||||
|
|
||||||
func setup(d: Dictionary, index: int, orte: Dictionary) -> void:
|
func setup(d: Dictionary, index: int, orte: Dictionary,
|
||||||
|
wegenetz: Netz = null, wegeanker: Dictionary = {}) -> void:
|
||||||
data = d
|
data = d
|
||||||
idx = index
|
idx = index
|
||||||
places = orte
|
places = orte
|
||||||
|
netz = wegenetz
|
||||||
|
anker = wegeanker
|
||||||
mit_mission = bool(d.get("mission", false))
|
mit_mission = bool(d.get("mission", false))
|
||||||
name = "Bewohner_" + str(d["id"])
|
name = "Bewohner_" + str(d["id"])
|
||||||
var winkel := float(index) * TAU / 4.0
|
var winkel := float(index) * TAU / 4.0
|
||||||
@@ -73,13 +79,14 @@ func _routine_at(h: float) -> Array:
|
|||||||
return [cur[1], cur[2]]
|
return [cur[1], cur[2]]
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(roh: float) -> void:
|
||||||
|
var delta := roh * Game.zeit_faktor()
|
||||||
resist_left = maxf(0.0, resist_left - delta)
|
resist_left = maxf(0.0, resist_left - delta)
|
||||||
|
|
||||||
var soll := _routine_at(Game.time_h)
|
var soll := _routine_at(Game.time_h)
|
||||||
if soll[0] != place:
|
if soll[0] != place:
|
||||||
var war_unterwegs := not _path.is_empty()
|
var war_unterwegs := not _path.is_empty()
|
||||||
_path = Orte.route(places, place, soll[0], _offset)
|
_path = Orte.route(places, place, soll[0], _offset, netz, anker)
|
||||||
if war_unterwegs and _path.size() > 1:
|
if war_unterwegs and _path.size() > 1:
|
||||||
_path.remove_at(0) # steht schon draußen, alte Tür überspringen
|
_path.remove_at(0) # steht schon draußen, alte Tür überspringen
|
||||||
place = soll[0]
|
place = soll[0]
|
||||||
@@ -110,8 +117,15 @@ func _process(delta: float) -> void:
|
|||||||
_visual.rotation.z = lerp_angle(_visual.rotation.z, PI * 0.5 if activity == "schlaeft" else 0.0,
|
_visual.rotation.z = lerp_angle(_visual.rotation.z, PI * 0.5 if activity == "schlaeft" else 0.0,
|
||||||
1.0 - pow(0.01, delta))
|
1.0 - pow(0.01, delta))
|
||||||
|
|
||||||
|
# Solange der Moment anhält, greift die Regel erneut, sobald ihre Technik
|
||||||
|
# wieder frei ist. Der Cooldown gibt damit den Takt vor.
|
||||||
|
_seit_regel += delta
|
||||||
if activity != _letzte_aktivitaet:
|
if activity != _letzte_aktivitaet:
|
||||||
_letzte_aktivitaet = activity
|
_letzte_aktivitaet = activity
|
||||||
|
_seit_regel = 0.0
|
||||||
|
_regel_pruefen()
|
||||||
|
elif _seit_regel >= 1.0:
|
||||||
|
_seit_regel = 0.0
|
||||||
_regel_pruefen()
|
_regel_pruefen()
|
||||||
|
|
||||||
var erledigt: bool = mit_mission and Game.completed.has(data["id"])
|
var erledigt: bool = mit_mission and Game.completed.has(data["id"])
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
class_name DreamDB
|
class_name DreamDB
|
||||||
## Die Traumwelt: ein Dorf, das mit jedem erfüllten Traum wächst.
|
## Die Traumwelt: ein Dorf, das mit jedem erfüllten Traum wächst.
|
||||||
## Fest sind nur Hörsaal, Dorfplatz und Tor zur Erde. Jedes Haus und sein
|
## Fest sind nur Hörsaal, Dorfplatz und Tor zur Erde. Jedes weitere Haus sucht
|
||||||
## Bewohner werden erzeugt — der Reihe nach, immer weiter nach außen.
|
## sich seinen Platz selbst: es wird dort gebaut, wo es Nachbarn, einen Weg und
|
||||||
|
## den Dorfplatz in erträglicher Nähe hat. Der Weg dorthin dockt an der
|
||||||
|
## nächsten Stelle des bestehenden Netzes an. Weil Wiederverwendung gratis ist,
|
||||||
|
## gabeln sich die Wege und werden verwinkelt, statt sternförmig zu laufen.
|
||||||
|
## Vorbild: Emilien et al., "Procedural Generation of Villages" (2012).
|
||||||
|
|
||||||
const HAEUSER_JE_RING := 6
|
const PLATZ_FREI := 22.0 # Radius um den Dorfplatz, der leer bleibt
|
||||||
const RING_ERSTER := 30.0
|
const HAUS_ABSTAND := 4.0 # Mindestluft zwischen zwei Gebäuden
|
||||||
const RING_ABSTAND := 24.0
|
const WEG_ABSTAND := 3.0 # Mindestluft zwischen Haus und Weg
|
||||||
|
const KANDIDATEN := 64 # Probepunkte je Haus
|
||||||
## Häuser sitzen auf Achtelrichtungen. Die Achsen 0 (Hörsaal) und 4 (Tor)
|
const SPINE_SCHRITT := 8.0 # Abstand der Knoten auf der Hauptachse
|
||||||
## bleiben frei, damit der Weg vom Dorfplatz zu beiden offen bleibt.
|
|
||||||
const SLOTS := [1, 2, 3, 5, 6, 7]
|
|
||||||
|
|
||||||
static var FEST: Dictionary = {
|
static var FEST: Dictionary = {
|
||||||
"hoersaal": {
|
"hoersaal": {
|
||||||
@@ -29,6 +31,8 @@ static var FEST: Dictionary = {
|
|||||||
## Wird von erzeuge() gefüllt und wächst mit dem Dorf.
|
## Wird von erzeuge() gefüllt und wächst mit dem Dorf.
|
||||||
static var PLACES: Dictionary = FEST.duplicate(true)
|
static var PLACES: Dictionary = FEST.duplicate(true)
|
||||||
static var LIST: Array = []
|
static var LIST: Array = []
|
||||||
|
static var NETZ: Netz = Netz.new()
|
||||||
|
static var ANKER: Dictionary = {}
|
||||||
|
|
||||||
static var FESTE_NAMEN: Array = ["Ailen", "Morr", "Ivo", "Sela"]
|
static var FESTE_NAMEN: Array = ["Ailen", "Morr", "Ivo", "Sela"]
|
||||||
static var FESTE_ROLLEN: Array = ["Traumweberin", "Erinnerungssammler", "Bote",
|
static var FESTE_ROLLEN: Array = ["Traumweberin", "Erinnerungssammler", "Bote",
|
||||||
@@ -56,47 +60,346 @@ static func haus_key(i: int) -> String:
|
|||||||
return "haus_%d" % i
|
return "haus_%d" % i
|
||||||
|
|
||||||
|
|
||||||
## Baut Orte und Bewohner für ein Dorf mit so vielen Häusern neu auf.
|
## Baut Orte, Wegenetz und Bewohner für ein Dorf mit so vielen Häusern neu auf.
|
||||||
|
## Deterministisch: Haus i landet immer am selben Fleck.
|
||||||
static func erzeuge(haeuser: int) -> void:
|
static func erzeuge(haeuser: int) -> void:
|
||||||
PLACES = FEST.duplicate(true)
|
PLACES = FEST.duplicate(true)
|
||||||
LIST = []
|
LIST = []
|
||||||
|
NETZ = Netz.new()
|
||||||
|
ANKER = {}
|
||||||
|
_hauptachse()
|
||||||
for i in range(maxi(haeuser, 0)):
|
for i in range(maxi(haeuser, 0)):
|
||||||
PLACES[haus_key(i)] = _haus(i)
|
var haus := _platz_suchen(i)
|
||||||
|
PLACES[haus_key(i)] = haus
|
||||||
|
_anschliessen(haus_key(i))
|
||||||
LIST.append(_bewohner(i))
|
LIST.append(_bewohner(i))
|
||||||
|
|
||||||
|
|
||||||
## Wie weit reicht das Dorf hinaus?
|
## Die feste Achse Hörsaal – Dorfplatz – Tor. Sie ist von Anfang an da und
|
||||||
static func aeusserster_ring(haeuser: int) -> float:
|
## trägt genug Knoten, damit Häuser überall andocken können.
|
||||||
if haeuser <= 0:
|
static func _hauptachse() -> void:
|
||||||
return 0.0
|
var punkte: Array[Vector3] = []
|
||||||
var ring := (haeuser - 1) / HAEUSER_JE_RING
|
var a := Orte.door(PLACES, "hoersaal")
|
||||||
return RING_ERSTER + float(ring) * RING_ABSTAND
|
var m := Orte.center(PLACES, "lichtung")
|
||||||
|
var b := Orte.door(PLACES, "tor")
|
||||||
|
for paar in [[a, m], [m, b]]:
|
||||||
|
var von: Vector3 = paar[0]
|
||||||
|
var nach: Vector3 = paar[1]
|
||||||
|
var n := maxi(1, int(round(von.distance_to(nach) / SPINE_SCHRITT)))
|
||||||
|
for k in range(n + 1):
|
||||||
|
var p := von.lerp(nach, float(k) / float(n))
|
||||||
|
if punkte.is_empty() or punkte[punkte.size() - 1].distance_to(p) > 0.6:
|
||||||
|
punkte.append(p)
|
||||||
|
var vorher := -1
|
||||||
|
for p in punkte:
|
||||||
|
var idx := NETZ.fuege_knoten(p)
|
||||||
|
if vorher >= 0:
|
||||||
|
NETZ.verbinde(vorher, idx)
|
||||||
|
vorher = idx
|
||||||
|
ANKER["hoersaal"] = NETZ.fuege_knoten(a)
|
||||||
|
ANKER["lichtung"] = NETZ.fuege_knoten(m)
|
||||||
|
ANKER["tor"] = NETZ.fuege_knoten(b)
|
||||||
|
|
||||||
|
|
||||||
static func _haus(i: int) -> Dictionary:
|
# ------------------------------------------------------------ Platzsuche
|
||||||
var ring := i / HAEUSER_JE_RING
|
|
||||||
var winkel := float(SLOTS[i % HAEUSER_JE_RING]) * TAU / 8.0
|
## Wählt unter vielen Probepunkten den mit dem höchsten Interesse.
|
||||||
var r := RING_ERSTER + float(ring) * RING_ABSTAND
|
static func _platz_suchen(i: int) -> Dictionary:
|
||||||
var x := sin(winkel) * r
|
var rng := RandomNumberGenerator.new()
|
||||||
var z := cos(winkel) * r
|
rng.seed = 4711 + i * 7919
|
||||||
var breite := 12.0 + float(i % 2) * 2.0
|
var breite := 12.0 + float(i % 2) * 2.0
|
||||||
var tiefe := 10.0 + float(i % 3)
|
var tiefe := 10.0 + float(i % 3)
|
||||||
|
var groesse := Vector2(breite, tiefe)
|
||||||
|
# Die Suchfläche wächst mit der Zahl der Häuser, nicht mit dem weitesten
|
||||||
|
# Ausreißer. So bleibt die Dichte gleich, statt zu zerfasern.
|
||||||
|
var aussen := maxf(_wachstumsradius() + 8.0,
|
||||||
|
sqrt(PLATZ_FREI * PLATZ_FREI + 240.0 * float(i + 1)))
|
||||||
|
|
||||||
|
var bester := Vector3.ZERO
|
||||||
|
var bestes := -1.0
|
||||||
|
for k in range(KANDIDATEN):
|
||||||
|
var winkel := rng.randf() * TAU
|
||||||
|
var r := PLATZ_FREI + rng.randf() * maxf(6.0, aussen - PLATZ_FREI)
|
||||||
|
var p := Vector3(cos(winkel) * r, 0.0, sin(winkel) * r)
|
||||||
|
if not _platz_frei(p, groesse):
|
||||||
|
continue
|
||||||
|
var wert := _interesse(p)
|
||||||
|
if wert > bestes:
|
||||||
|
bestes = wert
|
||||||
|
bester = p
|
||||||
|
if bestes < 0.0:
|
||||||
|
# Notnagel: irgendwo weit außen, damit das Dorf nie steckenbleibt
|
||||||
|
bester = _notplatz(i, groesse)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"name": "%s Haus" % _besitz(_name(i)),
|
"name": "%s Haus" % _besitz(_name(i)),
|
||||||
"pos": [x, z],
|
"pos": [bester.x, bester.z],
|
||||||
"size": [breite, tiefe],
|
"size": [breite, tiefe],
|
||||||
"dir": _tuer_zur_mitte(x, z),
|
"dir": _tuer_zum_netz(bester, groesse),
|
||||||
"color": Color.from_hsv(fmod(0.60 + float(i) * 0.137, 1.0), 0.34, 0.86),
|
"color": Color.from_hsv(fmod(0.60 + float(i) * 0.137, 1.0), 0.34, 0.86),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
## Die Tür zeigt zur Dorfmitte — auf die Seite, die ihr am nächsten liegt.
|
## Wie weit reichen die Häuser? Danach richtet sich, wo neu gesucht wird --
|
||||||
static func _tuer_zur_mitte(x: float, z: float) -> String:
|
## nicht nach Hörsaal und Tor, sonst zerfasert das Dorf sofort.
|
||||||
if absf(x) > absf(z):
|
static func _wachstumsradius() -> float:
|
||||||
return "left" if x > 0.0 else "right"
|
var r := PLATZ_FREI + 8.0
|
||||||
return "forward" if z > 0.0 else "back"
|
for key in PLACES:
|
||||||
|
if not key.begins_with("haus_"):
|
||||||
|
continue
|
||||||
|
var s: Array = PLACES[key]["size"]
|
||||||
|
var c := Orte.center(PLACES, key)
|
||||||
|
r = maxf(r, Vector2(absf(c.x) + float(s[0]) * 0.5,
|
||||||
|
absf(c.z) + float(s[1]) * 0.5).length())
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
static func _notplatz(i: int, groesse: Vector2) -> Vector3:
|
||||||
|
var r := aussenradius() + 20.0
|
||||||
|
for k in range(72):
|
||||||
|
var a := TAU * float((i * 29 + k) % 72) / 72.0
|
||||||
|
var p := Vector3(cos(a) * r, 0.0, sin(a) * r)
|
||||||
|
if _platz_frei(p, groesse):
|
||||||
|
return p
|
||||||
|
return Vector3(0, 0, r)
|
||||||
|
|
||||||
|
|
||||||
|
## Harte Bedingungen: nicht auf dem Dorfplatz, nicht in einem anderen Gebäude,
|
||||||
|
## nicht auf einem Weg.
|
||||||
|
static func _platz_frei(p: Vector3, groesse: Vector2) -> bool:
|
||||||
|
if p.length() - maxf(groesse.x, groesse.y) * 0.5 < PLATZ_FREI:
|
||||||
|
return false
|
||||||
|
for key in PLACES:
|
||||||
|
var s: Array = PLACES[key]["size"]
|
||||||
|
if float(s[0]) <= 0.0:
|
||||||
|
continue
|
||||||
|
var c := Orte.center(PLACES, key)
|
||||||
|
var dx: float = absf(p.x - c.x) - (groesse.x + float(s[0])) * 0.5
|
||||||
|
var dz: float = absf(p.z - c.z) - (groesse.y + float(s[1])) * 0.5
|
||||||
|
if dx < HAUS_ABSTAND and dz < HAUS_ABSTAND:
|
||||||
|
return false
|
||||||
|
for k in NETZ.kanten:
|
||||||
|
if Netz.trifft_rechteck(NETZ.knoten[k[0]], NETZ.knoten[k[1]], p, groesse, WEG_ABSTAND):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Wie gut liegt dieser Punkt? Nachbarschaft, Weganschluss, Nähe zum Platz.
|
||||||
|
static func _interesse(p: Vector3) -> float:
|
||||||
|
var sozial := 0.0
|
||||||
|
var n := 0
|
||||||
|
for key in PLACES:
|
||||||
|
if not key.begins_with("haus_"):
|
||||||
|
continue
|
||||||
|
var d := Orte.center(PLACES, key).distance_to(p)
|
||||||
|
sozial += _glocke(d, 16.0, 21.0, 46.0)
|
||||||
|
n += 1
|
||||||
|
if n > 0:
|
||||||
|
sozial = sozial / sqrt(float(n))
|
||||||
|
|
||||||
|
var an := NETZ.andocken(p)
|
||||||
|
var weg := _glocke(float(an["dist"]), 3.0, 9.0, 34.0)
|
||||||
|
var platz := _glocke(p.length(), PLATZ_FREI - 4.0, PLATZ_FREI + 6.0, 110.0)
|
||||||
|
return 1.0 * sozial + 1.3 * weg + 0.5 * platz + 0.8 * _freiraum(p)
|
||||||
|
|
||||||
|
|
||||||
|
## Richtungen, in denen schon viel steht, sind weniger interessant. Ohne das
|
||||||
|
## wächst das Dorf einseitig davon und die halbe Insel bleibt leer.
|
||||||
|
static func _freiraum(p: Vector3) -> float:
|
||||||
|
var w := atan2(p.z, p.x)
|
||||||
|
var nah := 0.0
|
||||||
|
var alle := 0.0
|
||||||
|
for key in PLACES:
|
||||||
|
if not key.begins_with("haus_"):
|
||||||
|
continue
|
||||||
|
var c := Orte.center(PLACES, key)
|
||||||
|
alle += 1.0
|
||||||
|
if absf(angle_difference(atan2(c.z, c.x), w)) < PI / 3.0:
|
||||||
|
nah += 1.0
|
||||||
|
if alle < 1.0:
|
||||||
|
return 1.0
|
||||||
|
return clampf(1.0 - 3.0 * nah / alle, 0.0, 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
## Dreiecksglocke: 0 unter lo, 1 bei spitze, 0 über hi.
|
||||||
|
static func _glocke(d: float, lo: float, spitze: float, hi: float) -> float:
|
||||||
|
if d <= lo or d >= hi:
|
||||||
|
return 0.0
|
||||||
|
if d < spitze:
|
||||||
|
return (d - lo) / maxf(0.001, spitze - lo)
|
||||||
|
return (hi - d) / maxf(0.001, hi - spitze)
|
||||||
|
|
||||||
|
|
||||||
|
## Die Tür zeigt auf die Seite, die dem Wegenetz am nächsten liegt.
|
||||||
|
static func _tuer_zum_netz(p: Vector3, groesse: Vector2) -> String:
|
||||||
|
var ziel: Vector3 = NETZ.andocken(p)["punkt"]
|
||||||
|
var d := ziel - p
|
||||||
|
if absf(d.x) * groesse.y > absf(d.z) * groesse.x:
|
||||||
|
return "left" if d.x < 0.0 else "right"
|
||||||
|
return "forward" if d.z < 0.0 else "back"
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------ Weganschluss
|
||||||
|
|
||||||
|
## Verbindet ein neues Haus mit dem Netz. Der Weg geht um Häuser herum, statt
|
||||||
|
## durch sie hindurch. Weil er am nächsten Punkt des Netzes andockt und nicht
|
||||||
|
## in der Dorfmitte, gabeln sich die Wege und werden verwinkelt.
|
||||||
|
static func _anschliessen(key: String) -> void:
|
||||||
|
var tuer := Orte.door(PLACES, key)
|
||||||
|
var an := NETZ.andocken(tuer)
|
||||||
|
var von: Vector3 = an["punkt"]
|
||||||
|
var linie := _route_um_haeuser(tuer, von, key)
|
||||||
|
var vorher := -1
|
||||||
|
for i in range(linie.size()):
|
||||||
|
var idx := NETZ.fuege_knoten(linie[i])
|
||||||
|
if vorher >= 0:
|
||||||
|
NETZ.verbinde(vorher, idx)
|
||||||
|
vorher = idx
|
||||||
|
if i == 0:
|
||||||
|
ANKER[key] = idx
|
||||||
|
if vorher >= 0:
|
||||||
|
NETZ.verbinde(vorher, int(an["knoten"]))
|
||||||
|
if not ANKER.has(key):
|
||||||
|
ANKER[key] = NETZ.fuege_knoten(tuer)
|
||||||
|
_schleife(int(ANKER[key]), key)
|
||||||
|
|
||||||
|
|
||||||
|
## Weg von a nach b, der kein Haus schneidet. Frei? Dann die Gerade. Sonst
|
||||||
|
## Dijkstra über ein grobes Raster, danach auf wenige Knicke geglättet.
|
||||||
|
static func _route_um_haeuser(a: Vector3, b: Vector3, ausser: String) -> Array:
|
||||||
|
if _weg_frei(a, b, ausser):
|
||||||
|
return [a, b]
|
||||||
|
var rand := 30.0
|
||||||
|
var min_x := minf(a.x, b.x) - rand
|
||||||
|
var max_x := maxf(a.x, b.x) + rand
|
||||||
|
var min_z := minf(a.z, b.z) - rand
|
||||||
|
var max_z := maxf(a.z, b.z) + rand
|
||||||
|
var schritt := 5.0
|
||||||
|
var nx := int((max_x - min_x) / schritt) + 1
|
||||||
|
var nz := int((max_z - min_z) / schritt) + 1
|
||||||
|
while nx * nz > 2600:
|
||||||
|
schritt *= 1.5
|
||||||
|
nx = int((max_x - min_x) / schritt) + 1
|
||||||
|
nz = int((max_z - min_z) / schritt) + 1
|
||||||
|
|
||||||
|
var start := Vector2i(clampi(int(round((a.x - min_x) / schritt)), 0, nx - 1),
|
||||||
|
clampi(int(round((a.z - min_z) / schritt)), 0, nz - 1))
|
||||||
|
var ziel := Vector2i(clampi(int(round((b.x - min_x) / schritt)), 0, nx - 1),
|
||||||
|
clampi(int(round((b.z - min_z) / schritt)), 0, nz - 1))
|
||||||
|
|
||||||
|
var kosten: Dictionary = {start: 0.0}
|
||||||
|
var her: Dictionary = {}
|
||||||
|
var offen: Array = [start]
|
||||||
|
var gefunden := false
|
||||||
|
while not offen.is_empty():
|
||||||
|
var best := 0
|
||||||
|
for i in range(1, offen.size()):
|
||||||
|
if float(kosten[offen[i]]) < float(kosten[offen[best]]):
|
||||||
|
best = i
|
||||||
|
var c: Vector2i = offen[best]
|
||||||
|
offen.remove_at(best)
|
||||||
|
if c == ziel:
|
||||||
|
gefunden = true
|
||||||
|
break
|
||||||
|
var pa := Vector3(min_x + float(c.x) * schritt, 0.0, min_z + float(c.y) * schritt)
|
||||||
|
for dx in [-1, 0, 1]:
|
||||||
|
for dz in [-1, 0, 1]:
|
||||||
|
if dx == 0 and dz == 0:
|
||||||
|
continue
|
||||||
|
var n := Vector2i(c.x + dx, c.y + dz)
|
||||||
|
if n.x < 0 or n.y < 0 or n.x >= nx or n.y >= nz:
|
||||||
|
continue
|
||||||
|
var pb := Vector3(min_x + float(n.x) * schritt, 0.0,
|
||||||
|
min_z + float(n.y) * schritt)
|
||||||
|
if not _weg_frei(pa, pb, ausser):
|
||||||
|
continue
|
||||||
|
var neu: float = float(kosten[c]) + pa.distance_to(pb)
|
||||||
|
if not kosten.has(n) or neu < float(kosten[n]) - 0.001:
|
||||||
|
kosten[n] = neu
|
||||||
|
her[n] = c
|
||||||
|
if not offen.has(n):
|
||||||
|
offen.append(n)
|
||||||
|
if not gefunden:
|
||||||
|
return [a, b]
|
||||||
|
|
||||||
|
var roh: Array = []
|
||||||
|
var cur: Vector2i = ziel
|
||||||
|
while cur != start:
|
||||||
|
roh.push_front(Vector3(min_x + float(cur.x) * schritt, 0.0,
|
||||||
|
min_z + float(cur.y) * schritt))
|
||||||
|
cur = her[cur]
|
||||||
|
roh.push_front(a)
|
||||||
|
roh.append(b)
|
||||||
|
return _glaetten(roh, ausser)
|
||||||
|
|
||||||
|
|
||||||
|
## Zieht die Rasterspur auf wenige Knicke zusammen: immer so weit springen,
|
||||||
|
## wie die Sicht frei ist.
|
||||||
|
static func _glaetten(linie: Array, ausser: String) -> Array:
|
||||||
|
var out: Array = [linie[0]]
|
||||||
|
var i := 0
|
||||||
|
while i < linie.size() - 1:
|
||||||
|
var j := linie.size() - 1
|
||||||
|
while j > i + 1 and not _weg_frei(linie[i], linie[j], ausser):
|
||||||
|
j -= 1
|
||||||
|
out.append(linie[j])
|
||||||
|
i = j
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
## Ein Umweg über das halbe Dorf ist unschön. Liegt ein Knoten nah, ist aber im
|
||||||
|
## Netz weit weg, wird abgekürzt. Daraus entstehen Schleifen statt Sackgassen.
|
||||||
|
static func _schleife(neu: int, key: String) -> void:
|
||||||
|
var p: Vector3 = NETZ.knoten[neu]
|
||||||
|
var bester := -1
|
||||||
|
var beste := INF
|
||||||
|
for i in range(NETZ.knoten.size()):
|
||||||
|
if i == neu:
|
||||||
|
continue
|
||||||
|
var d := NETZ.knoten[i].distance_to(p)
|
||||||
|
if d > 52.0 or d < 11.0 or d >= beste:
|
||||||
|
continue
|
||||||
|
if NETZ.weglaenge(neu, i) < d * 1.9:
|
||||||
|
continue
|
||||||
|
if not _weg_frei(p, NETZ.knoten[i], key):
|
||||||
|
continue
|
||||||
|
beste = d
|
||||||
|
bester = i
|
||||||
|
if bester >= 0:
|
||||||
|
NETZ.verbinde(neu, bester)
|
||||||
|
|
||||||
|
|
||||||
|
## Kein Weg darf durch ein Gebäude laufen -- auch nicht durch das eigene.
|
||||||
|
## Am eigenen Haus reicht knappe Luft, sonst käme die Tür nie frei.
|
||||||
|
static func _weg_frei(a: Vector3, b: Vector3, ausser: String) -> bool:
|
||||||
|
for key in PLACES:
|
||||||
|
var s: Array = PLACES[key]["size"]
|
||||||
|
if float(s[0]) <= 0.0:
|
||||||
|
continue
|
||||||
|
var c := Orte.center(PLACES, key)
|
||||||
|
var rand := 0.2 if key == ausser else 1.0
|
||||||
|
if Netz.trifft_rechteck(a, b, c, Vector2(float(s[0]), float(s[1])), rand):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------ Maße
|
||||||
|
|
||||||
|
## Wie weit reicht das Dorf hinaus?
|
||||||
|
static func aussenradius() -> float:
|
||||||
|
var r := 0.0
|
||||||
|
for key in PLACES:
|
||||||
|
var s: Array = PLACES[key]["size"]
|
||||||
|
var c := Orte.center(PLACES, key)
|
||||||
|
var ecke := Vector2(absf(c.x) + float(s[0]) * 0.5, absf(c.z) + float(s[1]) * 0.5)
|
||||||
|
r = maxf(r, ecke.length())
|
||||||
|
for k in NETZ.knoten:
|
||||||
|
r = maxf(r, k.length() + 2.0)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------ Bewohner
|
||||||
|
|
||||||
static func _bewohner(i: int) -> Dictionary:
|
static func _bewohner(i: int) -> Dictionary:
|
||||||
var haus := haus_key(i)
|
var haus := haus_key(i)
|
||||||
var ablauf: Array = []
|
var ablauf: Array = []
|
||||||
@@ -120,7 +423,7 @@ static func _name(i: int) -> String:
|
|||||||
return str(SILBE_A[j % SILBE_A.size()]) + str(SILBE_B[(j / SILBE_A.size()) % SILBE_B.size()])
|
return str(SILBE_A[j % SILBE_A.size()]) + str(SILBE_B[(j / SILBE_A.size()) % SILBE_B.size()])
|
||||||
|
|
||||||
|
|
||||||
## Genitiv: "Ailens", aber "Morrs" und "Esas'" — Apostroph nur nach Zischlaut.
|
## Genitiv: "Ailens", aber "Esas'" -- Apostroph nur nach Zischlaut.
|
||||||
static func _besitz(n: String) -> String:
|
static func _besitz(n: String) -> String:
|
||||||
var letzter := n.substr(n.length() - 1).to_lower()
|
var letzter := n.substr(n.length() - 1).to_lower()
|
||||||
return n + "'" if ["s", "z", "x", "ß"].has(letzter) else n + "s"
|
return n + "'" if ["s", "z", "x", "ß"].has(letzter) else n + "s"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ var spawn := Vector3(0, 2.0, 20.0)
|
|||||||
var bewohner: Array = []
|
var bewohner: Array = []
|
||||||
|
|
||||||
var _deko: Array = []
|
var _deko: Array = []
|
||||||
|
var _freihalten: Array[Vector3] = []
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -17,6 +18,7 @@ func _ready() -> void:
|
|||||||
_schwarzes_brett()
|
_schwarzes_brett()
|
||||||
_lichtung()
|
_lichtung()
|
||||||
_tor_zur_erde()
|
_tor_zur_erde()
|
||||||
|
_gruen()
|
||||||
_wesen()
|
_wesen()
|
||||||
|
|
||||||
|
|
||||||
@@ -59,18 +61,14 @@ func _insel() -> void:
|
|||||||
Prims.cyl(self, Vector3(0, -4.0, 0), radius * 0.77, 4.0, Color(0.34, 0.26, 0.50), false)
|
Prims.cyl(self, Vector3(0, -4.0, 0), radius * 0.77, 4.0, Color(0.34, 0.26, 0.50), false)
|
||||||
Prims.cyl(self, Vector3(0, -9.0, 0), radius * 0.42, 6.0, Color(0.24, 0.17, 0.38), false)
|
Prims.cyl(self, Vector3(0, -9.0, 0), radius * 0.42, 6.0, Color(0.24, 0.17, 0.38), false)
|
||||||
|
|
||||||
# Ringweg, auf dem die Wesen gehen
|
_wege()
|
||||||
for i in range(36):
|
|
||||||
var a := TAU * float(i) / 36.0
|
|
||||||
Prims.box(self, Vector3(cos(a) * 15.0, 0.03, sin(a) * 15.0),
|
|
||||||
Vector3(2.6, 0.06, 2.6), Color(0.66, 0.58, 0.84), false)
|
|
||||||
|
|
||||||
# schwebende Deko
|
# schwebende Deko
|
||||||
var rng := RandomNumberGenerator.new()
|
var rng := RandomNumberGenerator.new()
|
||||||
rng.seed = 7
|
rng.seed = 7
|
||||||
for i in range(34):
|
for i in range(34):
|
||||||
var a := rng.randf() * TAU
|
var a := rng.randf() * TAU
|
||||||
var r := rng.randf_range(18.0, Game.insel_radius() - 4.0)
|
var r := rng.randf_range(18.0, maxf(24.0, Game.insel_radius() - 4.0))
|
||||||
var p := Vector3(cos(a) * r, rng.randf_range(5.0, 24.0), sin(a) * r)
|
var p := Vector3(cos(a) * r, rng.randf_range(5.0, 24.0), sin(a) * r)
|
||||||
var s := rng.randf_range(0.6, 2.0)
|
var s := rng.randf_range(0.6, 2.0)
|
||||||
var c := Color(0.6 + rng.randf() * 0.3, 0.5 + rng.randf() * 0.4, 1.0)
|
var c := Color(0.6 + rng.randf() * 0.3, 0.5 + rng.randf() * 0.4, 1.0)
|
||||||
@@ -79,6 +77,91 @@ func _insel() -> void:
|
|||||||
_deko.append(wuerfel)
|
_deko.append(wuerfel)
|
||||||
|
|
||||||
|
|
||||||
|
## Die gewachsenen Wege: je Kante ein flaches Band, an den Knoten ein Fleck,
|
||||||
|
## damit Ecken nicht ausfransen.
|
||||||
|
## Begrünung nach Regel, nicht von Hand: Poisson-Scheibe über der freien
|
||||||
|
## Fläche. Nichts wächst auf einem Weg, in einem Haus oder auf dem Dorfplatz.
|
||||||
|
func _gruen() -> void:
|
||||||
|
_freihalten.append(Vector3(spawn.x, 0.0, spawn.z))
|
||||||
|
var radius := Game.insel_radius() - 4.0
|
||||||
|
var rng := RandomNumberGenerator.new()
|
||||||
|
rng.seed = 91
|
||||||
|
var gesetzt: Array[Vector3] = []
|
||||||
|
var abstand := 9.0
|
||||||
|
for versuch in range(1400):
|
||||||
|
var a := rng.randf() * TAU
|
||||||
|
var r := sqrt(rng.randf()) * radius
|
||||||
|
var p := Vector3(cos(a) * r, 0.0, sin(a) * r)
|
||||||
|
if p.length() < 20.0:
|
||||||
|
continue
|
||||||
|
var frei := true
|
||||||
|
for g in gesetzt:
|
||||||
|
if g.distance_to(p) < abstand:
|
||||||
|
frei = false
|
||||||
|
break
|
||||||
|
if not frei:
|
||||||
|
continue
|
||||||
|
if not _freie_stelle(p, 3.2):
|
||||||
|
continue
|
||||||
|
gesetzt.append(p)
|
||||||
|
_baum(p, rng)
|
||||||
|
|
||||||
|
|
||||||
|
func _freie_stelle(p: Vector3, luft: float) -> bool:
|
||||||
|
for frei in _freihalten:
|
||||||
|
if p.distance_to(frei) < 7.0:
|
||||||
|
return false
|
||||||
|
for key in DreamDB.PLACES:
|
||||||
|
var s: Array = DreamDB.PLACES[key]["size"]
|
||||||
|
if float(s[0]) <= 0.0 or not Game.ort_offen(key):
|
||||||
|
continue
|
||||||
|
var c := Orte.center(DreamDB.PLACES, key)
|
||||||
|
if absf(p.x - c.x) < float(s[0]) * 0.5 + luft \
|
||||||
|
and absf(p.z - c.z) < float(s[1]) * 0.5 + luft:
|
||||||
|
return false
|
||||||
|
for k in DreamDB.NETZ.kanten:
|
||||||
|
if Netz.trifft_rechteck(DreamDB.NETZ.knoten[k[0]], DreamDB.NETZ.knoten[k[1]],
|
||||||
|
p, Vector2(luft * 2.0, luft * 2.0), 0.6):
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
## Traumbäume: schlanker Stamm, zwei versetzte Kronen, leuchtende Spitze.
|
||||||
|
func _baum(p: Vector3, rng: RandomNumberGenerator) -> void:
|
||||||
|
var hoehe := rng.randf_range(3.0, 6.0)
|
||||||
|
# Grün gegen den lila Grund -- das macht die Insel erst gemütlich.
|
||||||
|
var ton := Color.from_hsv(0.26 + rng.randf() * 0.12, 0.48 + rng.randf() * 0.18,
|
||||||
|
0.58 + rng.randf() * 0.26)
|
||||||
|
Prims.cyl(self, p + Vector3(0, hoehe * 0.5, 0), 0.24, hoehe,
|
||||||
|
Color(0.34, 0.26, 0.30))
|
||||||
|
var k1 := Prims.sphere(self, p + Vector3(0, hoehe + 0.4, 0),
|
||||||
|
rng.randf_range(1.1, 2.0), ton)
|
||||||
|
k1.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||||
|
if rng.randf() < 0.6:
|
||||||
|
var k2 := Prims.sphere(self, p + Vector3(rng.randf_range(-0.8, 0.8),
|
||||||
|
hoehe - 0.7, rng.randf_range(-0.8, 0.8)), rng.randf_range(0.8, 1.5),
|
||||||
|
ton.lightened(0.12))
|
||||||
|
k2.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||||
|
if rng.randf() < 0.3:
|
||||||
|
Prims.sphere(self, p + Vector3(0, hoehe + 1.6, 0), 0.28,
|
||||||
|
Color(0.9, 0.95, 1.0), 1.6)
|
||||||
|
|
||||||
|
|
||||||
|
func _wege() -> void:
|
||||||
|
var farbe := Color(0.66, 0.58, 0.84)
|
||||||
|
for k in DreamDB.NETZ.kanten:
|
||||||
|
var a: Vector3 = DreamDB.NETZ.knoten[k[0]]
|
||||||
|
var b: Vector3 = DreamDB.NETZ.knoten[k[1]]
|
||||||
|
var d := b - a
|
||||||
|
if d.length() < 0.2:
|
||||||
|
continue
|
||||||
|
var band := Prims.box(self, (a + b) * 0.5 + Vector3(0, 0.03, 0),
|
||||||
|
Vector3(2.6, 0.06, d.length()), farbe, false)
|
||||||
|
band.rotation.y = atan2(d.x, d.z)
|
||||||
|
for k in DreamDB.NETZ.knoten:
|
||||||
|
Prims.cyl(self, k + Vector3(0, 0.035, 0), 1.3, 0.06, farbe, false)
|
||||||
|
|
||||||
|
|
||||||
func _haeuser() -> void:
|
func _haeuser() -> void:
|
||||||
for key in DreamDB.PLACES:
|
for key in DreamDB.PLACES:
|
||||||
var pl: Dictionary = DreamDB.PLACES[key]
|
var pl: Dictionary = DreamDB.PLACES[key]
|
||||||
@@ -194,8 +277,10 @@ func _schwarzes_brett() -> void:
|
|||||||
_marke(pos + Vector3(0, 1.6, 1.2), "brett", "Schwarzes Brett")
|
_marke(pos + Vector3(0, 1.6, 1.2), "brett", "Schwarzes Brett")
|
||||||
|
|
||||||
|
|
||||||
## Unsichtbarer Punkt, an dem die Interaktion hängt.
|
## Unsichtbarer Punkt, an dem die Interaktion hängt. Um jede Marke bleibt ein
|
||||||
|
## Stück frei, damit später kein Baum davorsteht.
|
||||||
func _marke(pos: Vector3, kind: String, titel: String) -> Node3D:
|
func _marke(pos: Vector3, kind: String, titel: String) -> Node3D:
|
||||||
|
_freihalten.append(Vector3(pos.x, 0.0, pos.z))
|
||||||
var n := Node3D.new()
|
var n := Node3D.new()
|
||||||
n.position = pos
|
n.position = pos
|
||||||
add_child(n)
|
add_child(n)
|
||||||
@@ -243,7 +328,7 @@ func _wesen() -> void:
|
|||||||
continue
|
continue
|
||||||
var w := Bewohner.new()
|
var w := Bewohner.new()
|
||||||
add_child(w)
|
add_child(w)
|
||||||
w.setup(DreamDB.LIST[i], i, DreamDB.PLACES)
|
w.setup(DreamDB.LIST[i], i, DreamDB.PLACES, DreamDB.NETZ, DreamDB.ANKER)
|
||||||
bewohner.append(w)
|
bewohner.append(w)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ var _nachtlichter: Array = []
|
|||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_environment()
|
_environment()
|
||||||
_boden()
|
_boden()
|
||||||
|
_bebauung()
|
||||||
_orte()
|
_orte()
|
||||||
_park()
|
_park()
|
||||||
_saeule()
|
_saeule()
|
||||||
@@ -44,107 +45,142 @@ func _environment() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _boden() -> void:
|
func _boden() -> void:
|
||||||
var weite := 240.0
|
Prims.box(self, Vector3(0, -0.53, 0), Vector3(300, 1, 300), Color(0.42, 0.48, 0.40))
|
||||||
|
_strassen()
|
||||||
|
|
||||||
|
|
||||||
|
## Arterien breit, roji schmal -- beide als flaches Band mit Belag.
|
||||||
|
func _strassen() -> void:
|
||||||
|
for st in HumanDB.STRASSEN:
|
||||||
|
var a: Vector3 = st["a"]
|
||||||
|
var b: Vector3 = st["b"]
|
||||||
|
var d := b - a
|
||||||
|
if d.length() < 0.2:
|
||||||
|
continue
|
||||||
|
var breit := float(st["breite"])
|
||||||
|
var farbe := Color(0.62, 0.60, 0.58) if breit > 5.0 else Color(0.55, 0.53, 0.52)
|
||||||
|
var band := Prims.box(self, (a + b) * 0.5 + Vector3(0, 0.02, 0),
|
||||||
|
Vector3(breit, 0.06, d.length() + breit), farbe, false)
|
||||||
|
band.rotation.y = atan2(d.x, d.z)
|
||||||
|
|
||||||
|
|
||||||
|
## Alles, was nicht Wohnung oder Sonderbau ist, steht als geschlossener Bau da.
|
||||||
|
## Erst dadurch wird der Bezirk dicht.
|
||||||
|
func _bebauung() -> void:
|
||||||
|
var rng := RandomNumberGenerator.new()
|
||||||
|
rng.seed = 8123
|
||||||
|
var belegt: Dictionary = {}
|
||||||
for key in HumanDB.PLACES:
|
for key in HumanDB.PLACES:
|
||||||
var pl: Dictionary = HumanDB.PLACES[key]
|
var s: Array = HumanDB.PLACES[key]["size"]
|
||||||
var p: Array = pl["pos"]
|
if float(s[0]) > 0.0:
|
||||||
var s2: Array = pl["size"]
|
belegt[Orte.center(HumanDB.PLACES, key).snapped(Vector3.ONE * 0.1)] = true
|
||||||
weite = maxf(weite, (absf(float(p[0])) + float(s2[0])) * 2.6)
|
for p in HumanDB.PARZELLEN:
|
||||||
weite = maxf(weite, (absf(float(p[1])) + float(s2[1])) * 2.6)
|
var c: Vector3 = p["pos"]
|
||||||
Prims.box(self, Vector3(0, -0.53, 0), Vector3(weite, 1, weite), Color(0.36, 0.52, 0.30))
|
if belegt.has(c.snapped(Vector3.ONE * 0.1)):
|
||||||
# Ringweg, auf dem die Menschen gehen
|
continue
|
||||||
for i in range(36):
|
var g: Vector2 = p["size"]
|
||||||
var a := TAU * float(i) / 36.0
|
if minf(g.x, g.y) < 3.0:
|
||||||
Prims.box(self, Vector3(cos(a) * 15.0, 0.03, sin(a) * 15.0),
|
continue
|
||||||
Vector3(2.8, 0.06, 2.8), Color(0.62, 0.58, 0.50), false)
|
var hoehe := rng.randf_range(4.0, 9.0)
|
||||||
|
var ton := Color.from_hsv(fmod(0.07 + rng.randf() * 0.12, 1.0),
|
||||||
|
0.10 + rng.randf() * 0.14, 0.62 + rng.randf() * 0.26)
|
||||||
|
Prims.box(self, c + Vector3(0, hoehe * 0.5, 0), Vector3(g.x, hoehe, g.y), ton)
|
||||||
|
Prims.box(self, c + Vector3(0, hoehe + 0.2, 0),
|
||||||
|
Vector3(g.x + 0.5, 0.4, g.y + 0.5), ton.darkened(0.25))
|
||||||
|
if rng.randf() < 0.35:
|
||||||
|
Prims.box(self, c + Vector3(0, hoehe + 0.9, 0),
|
||||||
|
Vector3(g.x * 0.4, 1.0, g.y * 0.4), ton.darkened(0.1))
|
||||||
|
|
||||||
|
|
||||||
|
## Wohnungen und Sonderbauten stehen offen, damit der Geist von oben hineinsieht.
|
||||||
func _orte() -> void:
|
func _orte() -> void:
|
||||||
for key in HumanDB.PLACES:
|
for key in HumanDB.PLACES:
|
||||||
var pl: Dictionary = HumanDB.PLACES[key]
|
var pl: Dictionary = HumanDB.PLACES[key]
|
||||||
if float(pl["size"][0]) <= 0.0:
|
var s: Array = pl["size"]
|
||||||
|
if float(s[0]) <= 0.0:
|
||||||
continue
|
continue
|
||||||
var c := Orte.center(HumanDB.PLACES, key)
|
var c := Orte.center(HumanDB.PLACES, key)
|
||||||
var s: Array = pl["size"]
|
var groesse := Vector3(float(s[0]), 2.3, float(s[1]))
|
||||||
var groesse := Vector3(s[0], 2.3, s[1])
|
|
||||||
var farbe: Color = pl["color"]
|
var farbe: Color = pl["color"]
|
||||||
var richtung: Vector3 = Orte.DIR_VEC[pl["dir"]]
|
var tuer: float = minf(3.0, minf(groesse.x, groesse.z) - 3.0)
|
||||||
Prims.room(self, c, groesse, farbe, farbe.darkened(0.35), richtung, 3.4, true)
|
Prims.room(self, c, groesse, farbe, farbe.darkened(0.35),
|
||||||
Prims.label(self, c + Vector3(0, 5.0, 0), pl["name"], 0.6, Color(1, 1, 0.9))
|
Orte.DIR_VEC[pl["dir"]], maxf(1.6, tuer), true)
|
||||||
|
Prims.label(self, c + Vector3(0, 4.4, 0), str(pl["name"]), 0.55, Color(1, 1, 0.9))
|
||||||
_einrichtung(c, groesse, farbe, key)
|
_einrichtung(c, groesse, farbe, key)
|
||||||
|
|
||||||
var tuer := Orte.door(HumanDB.PLACES, key)
|
var licht := OmniLight3D.new()
|
||||||
var weg := tuer.normalized() * 15.0 - tuer
|
licht.position = c + Vector3(0, 2.0, 0)
|
||||||
var b := Prims.box(self, (tuer + tuer.normalized() * 15.0) * 0.5 + Vector3(0, 0.03, 0),
|
licht.omni_range = 16.0
|
||||||
Vector3(2.6, 0.06, weg.length()), Color(0.62, 0.58, 0.50), false)
|
licht.light_color = Color(1.0, 0.85, 0.6)
|
||||||
b.rotation.y = atan2(weg.x, weg.z)
|
licht.light_energy = 0.0
|
||||||
|
add_child(licht)
|
||||||
var l := OmniLight3D.new()
|
_nachtlichter.append(licht)
|
||||||
l.position = c + Vector3(0, 2.0, 0)
|
|
||||||
l.omni_range = 20.0
|
|
||||||
l.light_color = Color(1.0, 0.85, 0.6)
|
|
||||||
l.light_energy = 0.0
|
|
||||||
add_child(l)
|
|
||||||
_nachtlichter.append(l)
|
|
||||||
|
|
||||||
|
|
||||||
|
## Ein paar Möbel, damit man sieht, wofür ein Raum da ist.
|
||||||
func _einrichtung(c: Vector3, groesse: Vector3, farbe: Color, key: String) -> void:
|
func _einrichtung(c: Vector3, groesse: Vector3, farbe: Color, key: String) -> void:
|
||||||
var h := groesse.z * 0.5 - 1.6
|
var w := maxf(0.5, groesse.x * 0.5 - 2.0)
|
||||||
var w := groesse.x * 0.5 - 1.6
|
var h := maxf(0.5, groesse.z * 0.5 - 2.0)
|
||||||
if key.begins_with("heim"):
|
if key.begins_with("heim_"):
|
||||||
Prims.box(self, c + Vector3(-w + 1.0, 0.35, -h + 1.2), Vector3(2.0, 0.7, 3.4),
|
Prims.box(self, c + Vector3(-w * 0.6, 0.35, -h * 0.5), Vector3(1.9, 0.7, 3.0),
|
||||||
Color(0.85, 0.85, 0.90)) # Bett
|
Color(0.85, 0.85, 0.90))
|
||||||
Prims.box(self, c + Vector3(w - 1.0, 0.75, 0), Vector3(1.6, 0.15, 2.6),
|
Prims.box(self, c + Vector3(w * 0.6, 0.75, 0), Vector3(1.5, 0.15, 2.2),
|
||||||
farbe.darkened(0.5)) # Schreibtisch
|
farbe.darkened(0.5))
|
||||||
Prims.box(self, c + Vector3(w - 1.0, 1.05, 0), Vector3(0.9, 0.6, 0.1),
|
Prims.box(self, c + Vector3(w * 0.6, 1.05, 0), Vector3(0.8, 0.55, 0.1),
|
||||||
Color(0.2, 0.25, 0.3)) # Bildschirm
|
Color(0.2, 0.25, 0.3))
|
||||||
elif key == "cafe":
|
elif key == "cafe":
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
Prims.cyl(self, c + Vector3(-3.0 + i * 3.0, 0.4, 0.5), 0.8, 0.8,
|
Prims.cyl(self, c + Vector3(-w * 0.5 + float(i) * w * 0.5, 0.4, 0.4), 0.7, 0.8,
|
||||||
Color(0.55, 0.40, 0.30))
|
Color(0.55, 0.40, 0.30))
|
||||||
Prims.box(self, c + Vector3(0, 0.55, -3.2), Vector3(groesse.x - 3.0, 1.1, 0.8),
|
Prims.box(self, c + Vector3(0, 0.55, -h * 0.7), Vector3(groesse.x - 3.0, 1.1, 0.8),
|
||||||
Color(0.45, 0.32, 0.24))
|
Color(0.45, 0.32, 0.24))
|
||||||
elif key == "buero":
|
elif key == "buero":
|
||||||
for i in range(4):
|
for i in range(3):
|
||||||
Prims.box(self, c + Vector3(-4.5 + i * 3.0, 0.75, -1.0), Vector3(2.2, 0.15, 1.4),
|
Prims.box(self, c + Vector3(-w * 0.5 + float(i) * w * 0.5, 0.75, 0),
|
||||||
Color(0.75, 0.75, 0.78))
|
Vector3(1.6, 0.15, 1.2), Color(0.75, 0.75, 0.78))
|
||||||
elif key == "baeckerei":
|
elif key == "baeckerei":
|
||||||
Prims.box(self, c + Vector3(0, 0.55, 2.0), Vector3(groesse.x - 3.0, 1.1, 1.0),
|
Prims.box(self, c + Vector3(0, 0.55, h * 0.5), Vector3(groesse.x - 3.0, 1.1, 1.0),
|
||||||
Color(0.70, 0.55, 0.35))
|
Color(0.70, 0.55, 0.35))
|
||||||
Prims.box(self, c + Vector3(0, 0.85, -3.0), Vector3(3.0, 1.7, 1.5), Color(0.4, 0.4, 0.42))
|
Prims.box(self, c + Vector3(0, 0.85, -h * 0.6), Vector3(2.6, 1.7, 1.2),
|
||||||
|
Color(0.4, 0.4, 0.42))
|
||||||
elif key == "uni":
|
elif key == "uni":
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
Prims.box(self, c + Vector3(0, 0.5, -2.0 + i * 2.2), Vector3(groesse.x - 4.0, 0.2, 0.8),
|
Prims.box(self, c + Vector3(0, 0.5, -h * 0.6 + float(i) * h * 0.6),
|
||||||
Color(0.66, 0.62, 0.58))
|
Vector3(groesse.x - 3.0, 0.2, 0.7), Color(0.66, 0.62, 0.58))
|
||||||
Prims.box(self, c + Vector3(0, 1.1, -4.0), Vector3(5.0, 1.6, 0.15), Color(0.15, 0.25, 0.20))
|
Prims.box(self, c + Vector3(0, 1.1, -h), Vector3(groesse.x * 0.55, 1.6, 0.15),
|
||||||
|
Color(0.15, 0.25, 0.20))
|
||||||
elif key == "werkstatt":
|
elif key == "werkstatt":
|
||||||
Prims.box(self, c + Vector3(0, 0.55, 0), Vector3(groesse.x - 4.0, 1.1, 1.4),
|
Prims.box(self, c + Vector3(0, 0.6, 0), Vector3(groesse.x - 4.0, 1.2, 1.4),
|
||||||
Color(0.55, 0.50, 0.48))
|
Color(0.52, 0.48, 0.46))
|
||||||
for i in range(3):
|
|
||||||
Prims.box(self, c + Vector3(-2.0 + i * 2.0, 1.5, -h + 0.6), Vector3(0.5, 0.9, 0.15),
|
|
||||||
Color(0.72, 0.68, 0.60), false)
|
|
||||||
|
|
||||||
|
|
||||||
|
## Der mittlere Block bleibt frei: Rasen, Teich, Bäume, Bänke. Der Park ist
|
||||||
|
## der einzige Ort im Bezirk, an dem nicht gebaut wird.
|
||||||
func _park() -> void:
|
func _park() -> void:
|
||||||
var rng := RandomNumberGenerator.new()
|
var rng := RandomNumberGenerator.new()
|
||||||
rng.seed = 3
|
rng.seed = 3
|
||||||
Prims.cyl(self, Vector3(-6, 0.05, 2), 4.0, 0.12, Color(0.30, 0.50, 0.72), false)
|
var halb: float = (float(HumanDB.ACHSEN[2]) - float(HumanDB.ACHSEN[1])) * 0.5 \
|
||||||
for i in range(14):
|
- HumanDB.ARTERIE * 0.5
|
||||||
var a := rng.randf() * TAU
|
Prims.box(self, Vector3(0, 0.01, 0), Vector3(halb * 2.0, 0.05, halb * 2.0),
|
||||||
var r := rng.randf_range(5.0, 12.0)
|
Color(0.40, 0.58, 0.34), false)
|
||||||
var p := Vector3(cos(a) * r, 0, sin(a) * r + 2.0)
|
Prims.cyl(self, Vector3(-halb * 0.4, 0.06, halb * 0.25), halb * 0.28, 0.1,
|
||||||
if p.length() < 4.0:
|
Color(0.30, 0.50, 0.72), false)
|
||||||
|
for i in range(26):
|
||||||
|
var p := Vector3(rng.randf_range(-halb, halb), 0, rng.randf_range(-halb, halb))
|
||||||
|
if p.distance_to(Vector3(-halb * 0.4, 0, halb * 0.25)) < halb * 0.38:
|
||||||
continue
|
continue
|
||||||
var hoehe := rng.randf_range(2.4, 4.2)
|
if p.distance_to(Vector3(0, 0, 12.0)) < 6.0:
|
||||||
|
continue
|
||||||
|
var hoehe := rng.randf_range(2.6, 5.0)
|
||||||
Prims.cyl(self, p + Vector3(0, hoehe * 0.5, 0), 0.3, hoehe, Color(0.35, 0.26, 0.18))
|
Prims.cyl(self, p + Vector3(0, hoehe * 0.5, 0), 0.3, hoehe, Color(0.35, 0.26, 0.18))
|
||||||
Prims.sphere(self, p + Vector3(0, hoehe + 0.8, 0), rng.randf_range(1.2, 1.9),
|
Prims.sphere(self, p + Vector3(0, hoehe + 0.9, 0), rng.randf_range(1.3, 2.2),
|
||||||
Color(0.25, 0.5 + rng.randf() * 0.2, 0.22))
|
Color(0.24, 0.48 + rng.randf() * 0.22, 0.22))
|
||||||
for i in range(4):
|
for i in range(5):
|
||||||
var a := TAU * float(i) / 4.0 + 0.4
|
var a := TAU * float(i) / 5.0 + 0.4
|
||||||
var p := Vector3(cos(a) * 8.0, 0.45, sin(a) * 8.0 + 2.0)
|
var p := Vector3(cos(a) * halb * 0.62, 0.45, sin(a) * halb * 0.62)
|
||||||
var b := Prims.box(self, p, Vector3(2.4, 0.15, 0.7), Color(0.55, 0.40, 0.28))
|
var b := Prims.box(self, p, Vector3(2.4, 0.15, 0.7), Color(0.55, 0.40, 0.28))
|
||||||
b.rotation.y = -a
|
b.rotation.y = -a
|
||||||
Prims.label(self, Vector3(0, 5.0, 10.0), "Stadtpark", 0.55, Color(1, 1, 0.9))
|
Prims.label(self, Vector3(0, 5.0, halb - 3.0), "Stadtpark", 0.6, Color(1, 1, 0.9))
|
||||||
|
|
||||||
|
|
||||||
func _saeule() -> void:
|
func _saeule() -> void:
|
||||||
@@ -167,7 +203,7 @@ func _menschen() -> void:
|
|||||||
for i in range(HumanDB.LIST.size()):
|
for i in range(HumanDB.LIST.size()):
|
||||||
var m := Bewohner.new()
|
var m := Bewohner.new()
|
||||||
add_child(m)
|
add_child(m)
|
||||||
m.setup(HumanDB.LIST[i], i, HumanDB.PLACES)
|
m.setup(HumanDB.LIST[i], i, HumanDB.PLACES, HumanDB.NETZ, HumanDB.ANKER)
|
||||||
humans.append(m)
|
humans.append(m)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
187
scripts/game.gd
187
scripts/game.gd
@@ -9,22 +9,40 @@ const DAY_SECONDS := 360.0 # eine Erdenrunde in Echtzeit-Sekunden
|
|||||||
const XP_LEVELS := [0, 4, 10, 18, 28]
|
const XP_LEVELS := [0, 4, 10, 18, 28]
|
||||||
const MISSION_ZIEL := 100.0
|
const MISSION_ZIEL := 100.0
|
||||||
const RESIST_SEKUNDEN := 18.0
|
const RESIST_SEKUNDEN := 18.0
|
||||||
|
## Drei Stufen wie in Die Sims: normal, schnell, sehr schnell. Die Stufe ist
|
||||||
|
## nicht der Faktor — Stufe 2 läuft dreifach, Stufe 3 siebenfach.
|
||||||
|
const TEMPO_FAKTOR := [1.0, 3.0, 7.0]
|
||||||
|
const TECHNIK_COOLDOWN := 30.0 # eine Technik braucht Luft, bevor sie wieder greift
|
||||||
|
const FEHLVERSUCH_VERLUST := 14.0 # ein Fehlschlag wirft den Traum zurück
|
||||||
|
# Der Verlust ist mit Absicht so hoch, dass sich nur eine Technik lohnt, die
|
||||||
|
# Thema UND Moment trifft. Wer eine Regel blind auf jede Tätigkeit hängt,
|
||||||
|
# verliert unterm Strich — die Wahl muss aus der Technik gelesen werden.
|
||||||
|
|
||||||
var einsicht: int = 3
|
|
||||||
var known: Dictionary = {} # tech_id -> xp
|
var known: Dictionary = {} # tech_id -> xp
|
||||||
var active_mission: String = "" # human_id oder ""
|
var active_mission: String = "" # human_id oder ""
|
||||||
var progress: Dictionary = {} # human_id -> 0..100
|
var progress: Dictionary = {} # human_id -> 0..100
|
||||||
var used: Dictionary = {} # "human:tech" -> Anzahl
|
var used: Dictionary = {} # "human:tech" -> Anzahl
|
||||||
|
var cooldown: Dictionary = {} # tech_id -> Restsekunden
|
||||||
var pipeline: Dictionary = {} # human_id -> {activity -> tech_id}
|
var pipeline: Dictionary = {} # human_id -> {activity -> tech_id}
|
||||||
var completed: Array = []
|
var completed: Array = []
|
||||||
var time_h: float = 7.0
|
var time_h: float = 7.0
|
||||||
var day: int = 1
|
var day: int = 1
|
||||||
var teleport_lock: float = 0.0
|
var teleport_lock: float = 0.0
|
||||||
|
var tempo: int = 1 # 1, 2 oder 3 -- vom Spieler gewählt
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_setup_input()
|
_setup_input()
|
||||||
welt_erzeugen()
|
welt_erzeugen()
|
||||||
|
_startweg_lernen()
|
||||||
|
|
||||||
|
|
||||||
|
## Den ersten Weg bringt das Wesen schon mit. Ohne ihn hinge am Schwarzen Brett
|
||||||
|
## nichts aus, und der erste Gang wäre eine Pflichtübung.
|
||||||
|
func _startweg_lernen() -> void:
|
||||||
|
for id in DreamDB.techniken_von("hoersaal"):
|
||||||
|
if not known.has(str(id)):
|
||||||
|
known[str(id)] = 0
|
||||||
|
|
||||||
|
|
||||||
func _setup_input() -> void:
|
func _setup_input() -> void:
|
||||||
@@ -37,6 +55,9 @@ func _setup_input() -> void:
|
|||||||
"sinken": [KEY_SHIFT],
|
"sinken": [KEY_SHIFT],
|
||||||
"interagieren": [KEY_E],
|
"interagieren": [KEY_E],
|
||||||
"journal": [KEY_J, KEY_TAB],
|
"journal": [KEY_J, KEY_TAB],
|
||||||
|
"tempo_1": [KEY_1, KEY_KP_1],
|
||||||
|
"tempo_2": [KEY_2, KEY_KP_2],
|
||||||
|
"tempo_3": [KEY_3, KEY_KP_3],
|
||||||
}
|
}
|
||||||
for action in binds:
|
for action in binds:
|
||||||
if not InputMap.has_action(action):
|
if not InputMap.has_action(action):
|
||||||
@@ -47,7 +68,47 @@ func _setup_input() -> void:
|
|||||||
InputMap.action_add_event(action, ev)
|
InputMap.action_add_event(action, ev)
|
||||||
|
|
||||||
|
|
||||||
|
## Schläft der Mensch, dem du gerade hilfst? Dann ist ohnehin nichts zu holen.
|
||||||
|
func schlaeft_ziel() -> bool:
|
||||||
|
if active_mission == "":
|
||||||
|
return false
|
||||||
|
var h := HumanDB.by_id(active_mission)
|
||||||
|
if h.is_empty():
|
||||||
|
return false
|
||||||
|
var r: Array = h["routine"]
|
||||||
|
var cur: Array = r[r.size() - 1]
|
||||||
|
for e in r:
|
||||||
|
if time_h >= float(e[0]):
|
||||||
|
cur = e
|
||||||
|
return not erreichbar(str(cur[2]))
|
||||||
|
|
||||||
|
|
||||||
|
## Um wie viel schneller läuft die Welt gerade? Über der Nacht des Menschen
|
||||||
|
## läuft sie immer auf der höchsten Stufe -- warten bringt dort nichts.
|
||||||
|
func zeit_faktor() -> float:
|
||||||
|
return faktor_von(3 if schlaeft_ziel() else tempo)
|
||||||
|
|
||||||
|
|
||||||
|
static func faktor_von(stufe: int) -> float:
|
||||||
|
return float(TEMPO_FAKTOR[clampi(stufe, 1, 3) - 1])
|
||||||
|
|
||||||
|
|
||||||
|
func tempo_setzen(stufe: int) -> void:
|
||||||
|
var neu := clampi(stufe, 1, 3)
|
||||||
|
if neu == tempo:
|
||||||
|
return
|
||||||
|
tempo = neu
|
||||||
|
notified.emit("Tempo %d — %d×" % [tempo, int(faktor_von(tempo))], Color(0.8, 0.9, 1))
|
||||||
|
state_changed.emit()
|
||||||
|
|
||||||
|
|
||||||
func advance_time(delta: float) -> void:
|
func advance_time(delta: float) -> void:
|
||||||
|
for id in cooldown.keys():
|
||||||
|
var rest: float = float(cooldown[id]) - delta
|
||||||
|
if rest <= 0.0:
|
||||||
|
cooldown.erase(id)
|
||||||
|
else:
|
||||||
|
cooldown[id] = rest
|
||||||
time_h += delta * 24.0 / DAY_SECONDS
|
time_h += delta * 24.0 / DAY_SECONDS
|
||||||
while time_h >= 24.0:
|
while time_h >= 24.0:
|
||||||
time_h -= 24.0
|
time_h -= 24.0
|
||||||
@@ -75,7 +136,6 @@ func time_string() -> String:
|
|||||||
# lehrt eine Technik, die es vorher nirgends gab.
|
# lehrt eine Technik, die es vorher nirgends gab.
|
||||||
|
|
||||||
const INSEL_GRUND := 46.0
|
const INSEL_GRUND := 46.0
|
||||||
const OFFENE_MISSIONEN := 3
|
|
||||||
|
|
||||||
|
|
||||||
func haeuser() -> int:
|
func haeuser() -> int:
|
||||||
@@ -83,14 +143,13 @@ func haeuser() -> int:
|
|||||||
|
|
||||||
|
|
||||||
func insel_radius() -> float:
|
func insel_radius() -> float:
|
||||||
var ring := DreamDB.aeusserster_ring(haeuser())
|
return maxf(INSEL_GRUND, DreamDB.aussenradius() + 12.0)
|
||||||
return maxf(INSEL_GRUND, ring + 14.0)
|
|
||||||
|
|
||||||
|
|
||||||
## Baut Traumdorf und Menschheit auf den Stand der erfüllten Träume.
|
## Baut Traumdorf und Menschheit auf den Stand der erfüllten Träume.
|
||||||
func welt_erzeugen() -> void:
|
func welt_erzeugen() -> void:
|
||||||
DreamDB.erzeuge(haeuser())
|
DreamDB.erzeuge(haeuser())
|
||||||
HumanDB.erzeuge(completed.size() + OFFENE_MISSIONEN)
|
HumanDB.erzeuge()
|
||||||
|
|
||||||
|
|
||||||
## Das Traumdorf beginnt leer. Mit jedem erfüllten Traum zieht ein Wesen ein.
|
## Das Traumdorf beginnt leer. Mit jedem erfüllten Traum zieht ein Wesen ein.
|
||||||
@@ -117,7 +176,21 @@ func offene_techniken() -> int:
|
|||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------- Cooldown
|
||||||
|
# Jede Anwendung sperrt ihre Technik für eine Weile. Wer einen ganzen Tag
|
||||||
|
# abdecken will, braucht deshalb mehrere Wege — einer allein kommt nicht nach.
|
||||||
|
|
||||||
|
func cooldown_rest(tech_id: String) -> float:
|
||||||
|
return float(cooldown.get(tech_id, 0.0))
|
||||||
|
|
||||||
|
|
||||||
|
func bereit(tech_id: String) -> bool:
|
||||||
|
return cooldown_rest(tech_id) <= 0.0
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Fertigkeiten
|
# ---------------------------------------------------------------- Fertigkeiten
|
||||||
|
# Gelernt wird an einem Lehrort, besser wird eine Technik nur durch Anwendung.
|
||||||
|
# Es gibt keinen Weg, sie am Schreibtisch zu trainieren.
|
||||||
|
|
||||||
func knows(tech_id: String) -> bool:
|
func knows(tech_id: String) -> bool:
|
||||||
return known.has(tech_id)
|
return known.has(tech_id)
|
||||||
@@ -149,10 +222,6 @@ func learn(tech_id: String) -> bool:
|
|||||||
notified.emit("Diesen Weg kennt im Dorf noch niemand. Erfülle einen Traum.",
|
notified.emit("Diesen Weg kennt im Dorf noch niemand. Erfülle einen Traum.",
|
||||||
Color(1, 0.8, 0.5))
|
Color(1, 0.8, 0.5))
|
||||||
return false
|
return false
|
||||||
if einsicht < 1:
|
|
||||||
notified.emit("Dafür fehlt dir Einsicht. Hilf erst einem Menschen.", Color(1, 0.6, 0.4))
|
|
||||||
return false
|
|
||||||
einsicht -= 1
|
|
||||||
known[tech_id] = 0
|
known[tech_id] = 0
|
||||||
var t := TechDB.by_id(tech_id)
|
var t := TechDB.by_id(tech_id)
|
||||||
notified.emit("Technik gelernt: %s" % t["name"], Color(0.6, 1, 0.7))
|
notified.emit("Technik gelernt: %s" % t["name"], Color(0.6, 1, 0.7))
|
||||||
@@ -171,18 +240,6 @@ func add_xp(tech_id: String, amount: int) -> void:
|
|||||||
state_changed.emit()
|
state_changed.emit()
|
||||||
|
|
||||||
|
|
||||||
func deepen(tech_id: String) -> bool:
|
|
||||||
if not known.has(tech_id):
|
|
||||||
return false
|
|
||||||
if einsicht < 1:
|
|
||||||
notified.emit("Keine Einsicht übrig.", Color(1, 0.6, 0.4))
|
|
||||||
return false
|
|
||||||
einsicht -= 1
|
|
||||||
add_xp(tech_id, 3)
|
|
||||||
notified.emit("Vertieft: %s (+3 EP)" % TechDB.by_id(tech_id)["name"], Color(0.7, 0.9, 1))
|
|
||||||
return true
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ Missionen
|
# ------------------------------------------------------------------ Missionen
|
||||||
|
|
||||||
func accept_mission(human_id: String) -> bool:
|
func accept_mission(human_id: String) -> bool:
|
||||||
@@ -204,6 +261,40 @@ func angenommen(human_id: String) -> bool:
|
|||||||
return progress.has(human_id)
|
return progress.has(human_id)
|
||||||
|
|
||||||
|
|
||||||
|
## Eine Mission ist machbar, wenn mindestens eine gelernte Technik das Thema
|
||||||
|
## des Menschen trifft und in seinem Tag auch einen Moment dafür findet.
|
||||||
|
## Nur solche werden am Schwarzen Brett überhaupt ausgehängt.
|
||||||
|
func machbar(human_id: String) -> bool:
|
||||||
|
var h := HumanDB.by_id(human_id)
|
||||||
|
if h.is_empty():
|
||||||
|
return false
|
||||||
|
var tag_liste: Array = HumanDB.aktivitaeten(human_id)
|
||||||
|
for id in known:
|
||||||
|
var t := TechDB.by_id(id)
|
||||||
|
var thema := false
|
||||||
|
for tag in t["tags"]:
|
||||||
|
if (h["tags"] as Array).has(tag):
|
||||||
|
thema = true
|
||||||
|
break
|
||||||
|
if not thema:
|
||||||
|
continue
|
||||||
|
for a in t["momente"]:
|
||||||
|
if tag_liste.has(a) and erreichbar(a):
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
## Die Menschen, die gerade am Brett hängen können.
|
||||||
|
func offene_missionen() -> Array:
|
||||||
|
var out: Array = []
|
||||||
|
for h in HumanDB.LIST:
|
||||||
|
var id: String = h["id"]
|
||||||
|
if completed.has(id) or not machbar(id):
|
||||||
|
continue
|
||||||
|
out.append(id)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
func mission_progress(human_id: String) -> float:
|
func mission_progress(human_id: String) -> float:
|
||||||
return progress.get(human_id, 0.0)
|
return progress.get(human_id, 0.0)
|
||||||
|
|
||||||
@@ -242,17 +333,17 @@ func regel_anzahl(human_id: String) -> int:
|
|||||||
return regeln(human_id).size()
|
return regeln(human_id).size()
|
||||||
|
|
||||||
|
|
||||||
## Wo hängt diese Technik im Regelwerk gerade? "" wenn nirgends.
|
## An welchen Tätigkeiten hängt diese Technik gerade?
|
||||||
func regel_moment(human_id: String, tech_id: String) -> String:
|
func regel_momente(human_id: String, tech_id: String) -> Array:
|
||||||
|
var out: Array = []
|
||||||
for activity in regeln(human_id):
|
for activity in regeln(human_id):
|
||||||
if regeln(human_id)[activity] == tech_id:
|
if regeln(human_id)[activity] == tech_id:
|
||||||
return activity
|
out.append(activity)
|
||||||
return ""
|
return out
|
||||||
|
|
||||||
|
|
||||||
## Leere tech_id löscht die Regel. Eine Technik hält immer nur einen Posten —
|
## Leere tech_id löscht die Regel. Eine Technik darf an beliebig vielen
|
||||||
## sie an einen neuen Moment zu hängen nimmt sie vom alten weg. Das zwingt zur
|
## Tätigkeiten hängen — ob das klug ist, muss der Spieler aus ihr herauslesen.
|
||||||
## Wahl und macht das Lernen weiterer Techniken erst wertvoll.
|
|
||||||
func regel_setzen(human_id: String, activity: String, tech_id: String) -> void:
|
func regel_setzen(human_id: String, activity: String, tech_id: String) -> void:
|
||||||
if not pipeline.has(human_id):
|
if not pipeline.has(human_id):
|
||||||
pipeline[human_id] = {}
|
pipeline[human_id] = {}
|
||||||
@@ -260,23 +351,20 @@ func regel_setzen(human_id: String, activity: String, tech_id: String) -> void:
|
|||||||
if tech_id == "":
|
if tech_id == "":
|
||||||
r.erase(activity)
|
r.erase(activity)
|
||||||
else:
|
else:
|
||||||
var vorher := regel_moment(human_id, tech_id)
|
|
||||||
if vorher != "" and vorher != activity:
|
|
||||||
r.erase(vorher)
|
|
||||||
notified.emit("%s hängt jetzt an »%s« statt an »%s«." %
|
|
||||||
[TechDB.by_id(tech_id)["name"], Orte.text(activity), Orte.text(vorher)],
|
|
||||||
Color(0.85, 0.9, 1))
|
|
||||||
r[activity] = tech_id
|
r[activity] = tech_id
|
||||||
state_changed.emit()
|
state_changed.emit()
|
||||||
|
|
||||||
|
|
||||||
## Der Mensch wechselt in eine Tätigkeit: greift dafür eine Regel?
|
## Greift für diese Tätigkeit eine Regel? Wird nicht nur beim Wechsel gefragt,
|
||||||
|
## sondern immer wieder — solange der Moment anhält und die Technik frei ist.
|
||||||
func regel_ausloesen(human_id: String, activity: String, resisting: bool) -> Dictionary:
|
func regel_ausloesen(human_id: String, activity: String, resisting: bool) -> Dictionary:
|
||||||
if human_id != active_mission:
|
if human_id != active_mission:
|
||||||
return {}
|
return {}
|
||||||
var tech_id := regel_fuer(human_id, activity)
|
var tech_id := regel_fuer(human_id, activity)
|
||||||
if tech_id == "" or not known.has(tech_id):
|
if tech_id == "" or not known.has(tech_id):
|
||||||
return {}
|
return {}
|
||||||
|
if not bereit(tech_id):
|
||||||
|
return {} # still: die Technik greift wieder, sobald ihre Sperre fällt
|
||||||
var res := apply_technique(tech_id, human_id, activity, resisting)
|
var res := apply_technique(tech_id, human_id, activity, resisting)
|
||||||
if not res["ok"]:
|
if not res["ok"]:
|
||||||
return {}
|
return {}
|
||||||
@@ -287,8 +375,8 @@ func regel_ausloesen(human_id: String, activity: String, resisting: bool) -> Dic
|
|||||||
[t["name"], Orte.text(activity), int(round(float(res["gain"])))],
|
[t["name"], Orte.text(activity), int(round(float(res["gain"])))],
|
||||||
Color(0.6, 1, 0.7))
|
Color(0.6, 1, 0.7))
|
||||||
else:
|
else:
|
||||||
notified.emit("Regel läuft ins Leere — %s bei »%s« (%d %% Chance)" %
|
notified.emit("Regel läuft ins Leere — %s bei »%s« (−%d %%)" %
|
||||||
[t["name"], Orte.text(activity), int(round(float(res["chance"]) * 100.0))],
|
[t["name"], Orte.text(activity), int(round(float(res["verlust"])))],
|
||||||
Color(1, 0.7, 0.5))
|
Color(1, 0.7, 0.5))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@@ -324,11 +412,13 @@ func evaluate(tech_id: String, human_id: String, activity: String, resisting: bo
|
|||||||
break
|
break
|
||||||
res["moment"] = t["momente"].has(activity)
|
res["moment"] = t["momente"].has(activity)
|
||||||
|
|
||||||
var c := 0.08 + 0.11 * float(skill)
|
# Der Moment wiegt am schwersten: eine Technik zur falschen Zeit greift kaum,
|
||||||
|
# egal wie gut sie zum Menschen passt. Genau das steht in ihrem "Wann".
|
||||||
|
var c := 0.05 + 0.09 * float(skill)
|
||||||
if res["tag"]:
|
if res["tag"]:
|
||||||
c += 0.25
|
c += 0.20
|
||||||
if res["moment"]:
|
if res["moment"]:
|
||||||
c += 0.22
|
c += 0.35
|
||||||
if resisting:
|
if resisting:
|
||||||
c -= 0.30
|
c -= 0.30
|
||||||
res["grund"] = "Der Mensch macht gerade dicht."
|
res["grund"] = "Der Mensch macht gerade dicht."
|
||||||
@@ -338,9 +428,13 @@ func evaluate(tech_id: String, human_id: String, activity: String, resisting: bo
|
|||||||
|
|
||||||
## Wendet die Technik wirklich an: würfelt, verteilt EP und Fortschritt.
|
## Wendet die Technik wirklich an: würfelt, verteilt EP und Fortschritt.
|
||||||
func apply_technique(tech_id: String, human_id: String, activity: String, resisting: bool) -> Dictionary:
|
func apply_technique(tech_id: String, human_id: String, activity: String, resisting: bool) -> Dictionary:
|
||||||
|
if not bereit(tech_id):
|
||||||
|
return {"ok": false, "grund": "%s ist noch nicht wieder einsatzbereit." %
|
||||||
|
TechDB.by_id(tech_id)["name"]}
|
||||||
var ev := evaluate(tech_id, human_id, activity, resisting)
|
var ev := evaluate(tech_id, human_id, activity, resisting)
|
||||||
if ev["blocked"]:
|
if ev["blocked"]:
|
||||||
return {"ok": false, "grund": ev["grund"]}
|
return {"ok": false, "grund": ev["grund"]}
|
||||||
|
cooldown[tech_id] = TECHNIK_COOLDOWN
|
||||||
|
|
||||||
var roll := randf()
|
var roll := randf()
|
||||||
var success: bool = roll < float(ev["chance"])
|
var success: bool = roll < float(ev["chance"])
|
||||||
@@ -349,6 +443,7 @@ func apply_technique(tech_id: String, human_id: String, activity: String, resist
|
|||||||
used[key] = wiederholung + 1
|
used[key] = wiederholung + 1
|
||||||
|
|
||||||
var gain := 0.0
|
var gain := 0.0
|
||||||
|
var verlust := 0.0
|
||||||
if success:
|
if success:
|
||||||
var base := 8.0 + 2.0 * float(ev["skill"])
|
var base := 8.0 + 2.0 * float(ev["skill"])
|
||||||
if ev["tag"]:
|
if ev["tag"]:
|
||||||
@@ -358,25 +453,25 @@ func apply_technique(tech_id: String, human_id: String, activity: String, resist
|
|||||||
gain = base / (1.0 + 0.6 * float(wiederholung))
|
gain = base / (1.0 + 0.6 * float(wiederholung))
|
||||||
add_xp(tech_id, 2)
|
add_xp(tech_id, 2)
|
||||||
else:
|
else:
|
||||||
|
verlust = FEHLVERSUCH_VERLUST
|
||||||
add_xp(tech_id, 1)
|
add_xp(tech_id, 1)
|
||||||
|
|
||||||
var done := false
|
var done := false
|
||||||
if human_id == active_mission and gain > 0.0:
|
if human_id == active_mission:
|
||||||
var p: float = progress.get(human_id, 0.0) + gain
|
var p: float = progress.get(human_id, 0.0) + gain - verlust
|
||||||
progress[human_id] = minf(p, MISSION_ZIEL)
|
progress[human_id] = clampf(p, 0.0, MISSION_ZIEL)
|
||||||
if progress[human_id] >= MISSION_ZIEL and not completed.has(human_id):
|
if progress[human_id] >= MISSION_ZIEL and not completed.has(human_id):
|
||||||
completed.append(human_id)
|
completed.append(human_id)
|
||||||
active_mission = ""
|
active_mission = ""
|
||||||
einsicht += 3
|
|
||||||
done = true
|
done = true
|
||||||
mission_done.emit(human_id)
|
mission_done.emit(human_id)
|
||||||
notified.emit("%s hat es geschafft. (+3 Einsicht)" % HumanDB.by_id(human_id)["name"],
|
notified.emit("%s hat es geschafft." % HumanDB.by_id(human_id)["name"],
|
||||||
Color(1, 0.85, 0.4))
|
Color(1, 0.85, 0.4))
|
||||||
_insel_waechst()
|
_insel_waechst()
|
||||||
state_changed.emit()
|
state_changed.emit()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"ok": true, "success": success, "chance": ev["chance"], "gain": gain,
|
"ok": true, "success": success, "chance": ev["chance"], "gain": gain,
|
||||||
"wiederholung": wiederholung, "mission_done": done,
|
"verlust": verlust, "wiederholung": wiederholung, "mission_done": done,
|
||||||
"tag": ev["tag"], "moment": ev["moment"],
|
"tag": ev["tag"], "moment": ev["moment"],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,38 @@
|
|||||||
class_name HumanDB
|
class_name HumanDB
|
||||||
## Die Kleinstadt auf der Erde. Die Gebäude im Ring um den Park stehen fest;
|
## Die Erde: ein Stadtbezirk nach japanischem Vorbild. Weite Arterien in einem
|
||||||
## Wohnhäuser und ihre Bewohner kommen dazu, solange Menschen Hilfe brauchen.
|
## groben Raster spannen Blöcke auf. Am Blockrand stehen die Häuser direkt an
|
||||||
|
## der Straße, ohne Vorgarten. Ins Blockinnere führen schmale Sackgassen --
|
||||||
|
## roji --, an denen die hinteren Parzellen hängen. So entsteht die typische
|
||||||
|
## Mischung aus klarer Ordnung außen und Gewinkel innen.
|
||||||
|
##
|
||||||
|
## Anders als das Traumdorf wächst hier nichts: alle Menschen sind von Anfang
|
||||||
|
## an da und leben ihren Tag.
|
||||||
|
|
||||||
const HAEUSER_JE_RING := 8
|
const EINWOHNER := 16
|
||||||
const RING_ERSTER := 62.0
|
const ACHSEN := [-72.0, -24.0, 24.0, 72.0] # Arterien in x wie in z
|
||||||
const RING_ABSTAND := 16.0
|
const ARTERIE := 7.0 # Breite einer Arterie
|
||||||
|
const ROJI := 3.2 # Breite einer Gasse
|
||||||
|
const RAND_TIEFE := 11.0 # Tiefe der Parzellen an der Straße
|
||||||
|
const PARZELLE_MIN := 8.0 # schmalste Parzelle
|
||||||
|
const PARZELLE_MAX := 12.0
|
||||||
|
|
||||||
## Gebäude liegen im Ring um den Park. Alle Türen zeigen zur Mitte.
|
## Alle Parzellen des Bezirks: {pos, size, dir, strasse}
|
||||||
static var FEST: Dictionary = {
|
static var PARZELLEN: Array = []
|
||||||
"uni": {
|
## Alle Straßenbänder: {a, b, breite}
|
||||||
"name": "Hochschule", "pos": [-28.0, -27.0], "size": [14.0, 10.0],
|
static var STRASSEN: Array = []
|
||||||
"dir": "back", "color": Color(0.72, 0.70, 0.66),
|
static var NETZ: Netz = Netz.new()
|
||||||
},
|
static var ANKER: Dictionary = {}
|
||||||
"cafe": {
|
|
||||||
"name": "Café Nadel", "pos": [0.0, -27.0], "size": [12.0, 10.0],
|
|
||||||
"dir": "back", "color": Color(0.82, 0.62, 0.45),
|
|
||||||
},
|
|
||||||
"buero": {
|
|
||||||
"name": "Büro Ostwind", "pos": [28.0, -27.0], "size": [14.0, 10.0],
|
|
||||||
"dir": "back", "color": Color(0.62, 0.66, 0.72),
|
|
||||||
},
|
|
||||||
"baeckerei": {
|
|
||||||
"name": "Bäckerei Keller", "pos": [-31.0, 0.0], "size": [10.0, 12.0],
|
|
||||||
"dir": "right", "color": Color(0.85, 0.72, 0.50),
|
|
||||||
},
|
|
||||||
"werkstatt": {
|
|
||||||
"name": "Werkstatt Loos", "pos": [31.0, 0.0], "size": [10.0, 12.0],
|
|
||||||
"dir": "left", "color": Color(0.70, 0.58, 0.55),
|
|
||||||
},
|
|
||||||
"park": {
|
|
||||||
"name": "Stadtpark", "pos": [0.0, 4.0], "size": [0.0, 0.0],
|
|
||||||
"dir": "none", "color": Color(0.45, 0.65, 0.38),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
static var PLACES: Dictionary = FEST.duplicate(true)
|
## Die fünf Sonderbauten. Sie bekommen eine Parzelle an einer Arterie.
|
||||||
|
static var SONDER: Array = [
|
||||||
|
["uni", "Hochschule", Color(0.72, 0.70, 0.66)],
|
||||||
|
["cafe", "Café Nadel", Color(0.82, 0.62, 0.45)],
|
||||||
|
["buero", "Büro Ostwind", Color(0.62, 0.66, 0.72)],
|
||||||
|
["baeckerei", "Bäckerei Keller", Color(0.85, 0.72, 0.50)],
|
||||||
|
["werkstatt", "Werkstatt Loos", Color(0.70, 0.58, 0.55)],
|
||||||
|
]
|
||||||
|
|
||||||
|
static var PLACES: Dictionary = {}
|
||||||
static var LIST: Array = []
|
static var LIST: Array = []
|
||||||
|
|
||||||
## Arbeitsplätze, die ein erzeugter Mensch bekommen kann.
|
## Arbeitsplätze, die ein erzeugter Mensch bekommen kann.
|
||||||
@@ -125,46 +123,299 @@ static var ABLAEUFE: Array = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
## Zu jeder Technik ein Anliegen und ein Tagesablauf, bei dem sie greift:
|
||||||
|
## [Anliegen, Ablauf]. Mensch i bekommt den Eintrag zu Technik i — und Technik i
|
||||||
|
## ist genau dann entdeckt, wenn i Träume erfüllt sind. So hängt am Schwarzen
|
||||||
|
## Brett immer mindestens ein Mensch, dem mit dem Gelernten zu helfen ist.
|
||||||
|
static var PASSEND: Array = [
|
||||||
|
[0, 0], # Pomodoro -> aufschieben, zögert
|
||||||
|
[6, 4], # Zwei-Minuten -> Werkstatt, räumt auf
|
||||||
|
[4, 3], # Wenn-Dann -> Gewohnheit, Morgenroutine
|
||||||
|
[1, 1], # Gewohnheiten -> Klavier, isst / Abendroutine
|
||||||
|
[9, 0], # Zerlegen -> Überforderung, grübelt
|
||||||
|
[0, 4], # Umgebung -> Ablenkung, räumt auf
|
||||||
|
[8, 0], # Umdeuten -> Selbstzweifel, grübelt
|
||||||
|
[7, 2], # Box-Atmung -> Angst, angespannt
|
||||||
|
[8, 2], # Selbstmitgefühl -> Rückschlag, grübelt
|
||||||
|
[4, 1], # Schlaffenster -> Erschöpfung, ruht
|
||||||
|
[5, 2], # Hilfe bitten -> Einsamkeit, wartet
|
||||||
|
[3, 3], # Nein sagen -> Grenzen, arbeitet
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
static func heim_key(i: int) -> String:
|
static func heim_key(i: int) -> String:
|
||||||
return "heim_%d" % i
|
return "heim_%d" % i
|
||||||
|
|
||||||
|
|
||||||
## Baut Wohnhäuser und Menschen für so viele Bewohner neu auf.
|
## Baut den ganzen Bezirk neu auf: Straßen, Parzellen, Sonderbauten, Wohnhäuser.
|
||||||
static func erzeuge(menschen: int) -> void:
|
## Die Zahl der Menschen liegt fest -- sie sind alle von Anfang an da.
|
||||||
PLACES = FEST.duplicate(true)
|
static func erzeuge(_ungenutzt: int = 0) -> void:
|
||||||
|
_bezirk()
|
||||||
|
PLACES = {}
|
||||||
LIST = []
|
LIST = []
|
||||||
for i in range(maxi(menschen, 0)):
|
_park_eintragen()
|
||||||
PLACES[heim_key(i)] = _heim(i)
|
_sonderbauten_verteilen()
|
||||||
|
_wohnhaeuser_verteilen()
|
||||||
|
_anker_setzen()
|
||||||
|
for i in range(EINWOHNER):
|
||||||
LIST.append(_mensch(i))
|
LIST.append(_mensch(i))
|
||||||
|
|
||||||
|
|
||||||
static func _heim(i: int) -> Dictionary:
|
# ------------------------------------------------------------ Bezirk
|
||||||
var ring := i / HAEUSER_JE_RING
|
|
||||||
var winkel := float(i % HAEUSER_JE_RING) * TAU / float(HAEUSER_JE_RING) + 0.39
|
## Arterien im Raster, Blöcke dazwischen, roji ins Blockinnere, Parzellen
|
||||||
var r := RING_ERSTER + float(ring) * RING_ABSTAND
|
## an jeder Straße. Danach steht das Straßennetz, auf dem alle laufen.
|
||||||
var x := sin(winkel) * r
|
static func _bezirk() -> void:
|
||||||
var z := cos(winkel) * r
|
PARZELLEN = []
|
||||||
return {
|
STRASSEN = []
|
||||||
"name": "%s %s" % [_nachname(i), "Weg %d" % (i + 1)],
|
NETZ = Netz.new()
|
||||||
"pos": [x, z],
|
ANKER = {}
|
||||||
"size": [12.0 + float(i % 3) * 2.0, 10.0],
|
var rng := RandomNumberGenerator.new()
|
||||||
"dir": _tuer_zur_mitte(x, z),
|
rng.seed = 31337
|
||||||
|
|
||||||
|
# Arterien: die Achsen leicht versetzt, damit kein reines Schachbrett steht
|
||||||
|
var xs: Array = []
|
||||||
|
var zs: Array = []
|
||||||
|
for i in range(ACHSEN.size()):
|
||||||
|
xs.append(float(ACHSEN[i]) + rng.randf_range(-3.0, 3.0))
|
||||||
|
zs.append(float(ACHSEN[i]) + rng.randf_range(-3.0, 3.0))
|
||||||
|
var von: float = float(ACHSEN[0]) - 6.0
|
||||||
|
var bis: float = float(ACHSEN[ACHSEN.size() - 1]) + 6.0
|
||||||
|
for x in xs:
|
||||||
|
_strasse(Vector3(x, 0, von), Vector3(x, 0, bis), ARTERIE)
|
||||||
|
for z in zs:
|
||||||
|
_strasse(Vector3(von, 0, z), Vector3(bis, 0, z), ARTERIE)
|
||||||
|
|
||||||
|
# Blöcke zwischen den Arterien
|
||||||
|
for ix in range(xs.size() - 1):
|
||||||
|
for iz in range(zs.size() - 1):
|
||||||
|
var x0: float = float(xs[ix]) + ARTERIE * 0.5
|
||||||
|
var x1: float = float(xs[ix + 1]) - ARTERIE * 0.5
|
||||||
|
var z0: float = float(zs[iz]) + ARTERIE * 0.5
|
||||||
|
var z1: float = float(zs[iz + 1]) - ARTERIE * 0.5
|
||||||
|
if ix == 1 and iz == 1:
|
||||||
|
continue # die Mitte bleibt Park
|
||||||
|
_block(Vector3((x0 + x1) * 0.5, 0, (z0 + z1) * 0.5),
|
||||||
|
Vector2(x1 - x0, z1 - z0), rng)
|
||||||
|
_netz_bauen()
|
||||||
|
|
||||||
|
|
||||||
|
static func _strasse(a: Vector3, b: Vector3, breite: float) -> void:
|
||||||
|
STRASSEN.append({"a": a, "b": b, "breite": breite})
|
||||||
|
|
||||||
|
|
||||||
|
## Aus den Straßenbändern ein zusammenhängendes Netz machen: an jeder Kreuzung
|
||||||
|
## sitzt ein Knoten, dazwischen alle 16 Meter einer.
|
||||||
|
static func _netz_bauen() -> void:
|
||||||
|
for i in range(STRASSEN.size()):
|
||||||
|
var a: Vector3 = STRASSEN[i]["a"]
|
||||||
|
var b: Vector3 = STRASSEN[i]["b"]
|
||||||
|
var laenge := a.distance_to(b)
|
||||||
|
if laenge < 0.5:
|
||||||
|
continue
|
||||||
|
var stellen: Array = [0.0, 1.0]
|
||||||
|
for j in range(STRASSEN.size()):
|
||||||
|
if j == i:
|
||||||
|
continue
|
||||||
|
var t := _kreuzung(a, b, STRASSEN[j]["a"], STRASSEN[j]["b"])
|
||||||
|
if t >= 0.0:
|
||||||
|
stellen.append(t)
|
||||||
|
stellen.sort()
|
||||||
|
var fein: Array = [float(stellen[0])]
|
||||||
|
for k in range(1, stellen.size()):
|
||||||
|
var von: float = float(fein[fein.size() - 1])
|
||||||
|
var bis: float = float(stellen[k])
|
||||||
|
if bis - von < 0.001:
|
||||||
|
continue
|
||||||
|
var n := maxi(1, int((bis - von) * laenge / 16.0))
|
||||||
|
for m in range(1, n + 1):
|
||||||
|
fein.append(von + (bis - von) * float(m) / float(n))
|
||||||
|
var vorher := -1
|
||||||
|
for t2 in fein:
|
||||||
|
var idx := NETZ.fuege_knoten(a.lerp(b, float(t2)))
|
||||||
|
if vorher >= 0:
|
||||||
|
NETZ.verbinde(vorher, idx)
|
||||||
|
vorher = idx
|
||||||
|
|
||||||
|
|
||||||
|
## Parameter t auf der Strecke a-b, an dem sie c-d schneidet. -1 wenn nicht.
|
||||||
|
static func _kreuzung(a: Vector3, b: Vector3, c: Vector3, d: Vector3) -> float:
|
||||||
|
var r := b - a
|
||||||
|
var s2 := d - c
|
||||||
|
var nenner := r.x * s2.z - r.z * s2.x
|
||||||
|
if absf(nenner) < 0.0001:
|
||||||
|
return -1.0
|
||||||
|
var t := ((c.x - a.x) * s2.z - (c.z - a.z) * s2.x) / nenner
|
||||||
|
var u := ((c.x - a.x) * r.z - (c.z - a.z) * r.x) / nenner
|
||||||
|
if t < 0.0 or t > 1.0 or u < 0.0 or u > 1.0:
|
||||||
|
return -1.0
|
||||||
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
## Ein Block: außen ein Kranz von Parzellen an den Arterien, innen eine Gasse
|
||||||
|
## mit den hinteren Parzellen daran. Die Nord- und Südreihe laufen über die
|
||||||
|
## volle Breite, die Ost- und Westreihe nur dazwischen -- sonst überlappen die
|
||||||
|
## Ecken.
|
||||||
|
static func _block(mitte: Vector3, groesse: Vector2, rng: RandomNumberGenerator) -> void:
|
||||||
|
var tiefe := minf(RAND_TIEFE, minf(groesse.x, groesse.y) * 0.34)
|
||||||
|
var seiten := [
|
||||||
|
["back", Vector3(0, 0, 1), groesse.x, groesse.y],
|
||||||
|
["forward", Vector3(0, 0, -1), groesse.x, groesse.y],
|
||||||
|
["right", Vector3(1, 0, 0), groesse.y - tiefe * 2.0, groesse.x],
|
||||||
|
["left", Vector3(-1, 0, 0), groesse.y - tiefe * 2.0, groesse.x],
|
||||||
|
]
|
||||||
|
for eintrag in seiten:
|
||||||
|
var dir: String = eintrag[0]
|
||||||
|
var n: Vector3 = eintrag[1]
|
||||||
|
var laenge: float = eintrag[2]
|
||||||
|
var quer_mass: float = eintrag[3]
|
||||||
|
if laenge < PARZELLE_MIN:
|
||||||
|
continue
|
||||||
|
var quer := Vector3(absf(n.z), 0, absf(n.x))
|
||||||
|
var anzahl := maxi(1, int(round(laenge / rng.randf_range(PARZELLE_MIN + 1.0,
|
||||||
|
PARZELLE_MAX))))
|
||||||
|
var breite := laenge / float(anzahl)
|
||||||
|
var rand := mitte + n * (quer_mass * 0.5 - tiefe * 0.5)
|
||||||
|
for k in range(anzahl):
|
||||||
|
var versatz := (float(k) + 0.5 - float(anzahl) * 0.5) * breite
|
||||||
|
var quer_ist := absf(n.z) > 0.0
|
||||||
|
PARZELLEN.append({
|
||||||
|
"pos": rand + quer * versatz,
|
||||||
|
"size": Vector2(breite - 0.8, tiefe - 0.8) if quer_ist
|
||||||
|
else Vector2(tiefe - 0.8, breite - 0.8),
|
||||||
|
"dir": dir,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Blockinneres: eine Gasse von der längeren Seite hinein
|
||||||
|
var innen := groesse - Vector2(tiefe, tiefe) * 2.0
|
||||||
|
if innen.x < 12.0 or innen.y < 12.0:
|
||||||
|
return
|
||||||
|
var laengs := innen.y >= innen.x
|
||||||
|
var richtung := Vector3(0, 0, -1) if laengs else Vector3(-1, 0, 0)
|
||||||
|
# Die Gasse setzt an der Arterie an, sonst hinge sie in der Luft
|
||||||
|
var aussen: float = (groesse.y if laengs else groesse.x) * 0.5 + ARTERIE * 0.5
|
||||||
|
var start := mitte - richtung * aussen
|
||||||
|
var ende := mitte + richtung * ((innen.y if laengs else innen.x) * 0.5 - 2.0)
|
||||||
|
_strasse(start, ende, ROJI)
|
||||||
|
var flanke := Vector3(1, 0, 0) if laengs else Vector3(0, 0, 1)
|
||||||
|
var p_breite := (innen.x if laengs else innen.y) * 0.5 - ROJI * 0.5 - 0.8
|
||||||
|
if p_breite < PARZELLE_MIN * 0.8:
|
||||||
|
return
|
||||||
|
# Nur der Teil im Blockinneren trägt Parzellen
|
||||||
|
var innen_start := mitte - richtung * ((innen.y if laengs else innen.x) * 0.5)
|
||||||
|
var laenge_g := innen_start.distance_to(ende)
|
||||||
|
var anzahl_g := maxi(1, int(round(laenge_g / 13.0)))
|
||||||
|
var tief_g := laenge_g / float(anzahl_g) - 0.8
|
||||||
|
for seite: float in [1.0, -1.0]:
|
||||||
|
for k in range(anzahl_g):
|
||||||
|
var t := (float(k) + 0.5) / float(anzahl_g)
|
||||||
|
var pos := innen_start.lerp(ende, t) + flanke * (seite * (ROJI * 0.5 + p_breite * 0.5))
|
||||||
|
PARZELLEN.append({
|
||||||
|
"pos": pos,
|
||||||
|
"size": Vector2(p_breite, tief_g) if laengs else Vector2(tief_g, p_breite),
|
||||||
|
"dir": ("left" if seite > 0.0 else "right") if laengs
|
||||||
|
else ("forward" if seite > 0.0 else "back"),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------ Belegung
|
||||||
|
|
||||||
|
static func _park_eintragen() -> void:
|
||||||
|
PLACES["park"] = {
|
||||||
|
"name": "Stadtpark", "pos": [0.0, 0.0], "size": [0.0, 0.0],
|
||||||
|
"dir": "none", "color": Color(0.45, 0.65, 0.38),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Die Sonderbauten bekommen Parzellen an einer Arterie, gut verteilt.
|
||||||
|
static func _sonderbauten_verteilen() -> void:
|
||||||
|
var kandidaten: Array = []
|
||||||
|
for i in range(PARZELLEN.size()):
|
||||||
|
var p: Dictionary = PARZELLEN[i]
|
||||||
|
if minf(p["size"].x, p["size"].y) >= 10.0:
|
||||||
|
kandidaten.append(i)
|
||||||
|
for s in range(SONDER.size()):
|
||||||
|
if kandidaten.is_empty():
|
||||||
|
break
|
||||||
|
# Das Café gehört in die Mitte -- jeder Tagesablauf führt dorthin.
|
||||||
|
# Die übrigen Sonderbauten verteilen sich rundum.
|
||||||
|
var wunsch := Vector3.ZERO
|
||||||
|
if str(SONDER[s][0]) != "cafe":
|
||||||
|
var w := TAU * float(s) / float(SONDER.size() - 1)
|
||||||
|
wunsch = Vector3(cos(w), 0, sin(w)) * 42.0
|
||||||
|
var idx: int = -1
|
||||||
|
var beste := INF
|
||||||
|
for k in kandidaten:
|
||||||
|
var q: Dictionary = PARZELLEN[k]
|
||||||
|
if q.has("belegt"):
|
||||||
|
continue
|
||||||
|
var d: float = (q["pos"] as Vector3).distance_to(wunsch)
|
||||||
|
if d < beste:
|
||||||
|
beste = d
|
||||||
|
idx = int(k)
|
||||||
|
if idx < 0:
|
||||||
|
continue
|
||||||
|
var p: Dictionary = PARZELLEN[idx]
|
||||||
|
p["belegt"] = true
|
||||||
|
PLACES[str(SONDER[s][0])] = {
|
||||||
|
"name": str(SONDER[s][1]),
|
||||||
|
"pos": [p["pos"].x, p["pos"].z],
|
||||||
|
"size": [p["size"].x, p["size"].y],
|
||||||
|
"dir": p["dir"],
|
||||||
|
"color": SONDER[s][2],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Die Wohnhäuser: gleichmäßig über den Bezirk verteilt, damit die Wege
|
||||||
|
## nicht alle in derselben Ecke enden.
|
||||||
|
static func _wohnhaeuser_verteilen() -> void:
|
||||||
|
var frei: Array = []
|
||||||
|
for i in range(PARZELLEN.size()):
|
||||||
|
if not PARZELLEN[i].has("belegt"):
|
||||||
|
frei.append(i)
|
||||||
|
if frei.is_empty():
|
||||||
|
return
|
||||||
|
for i in range(EINWOHNER):
|
||||||
|
var idx: int = int(frei[(i * 11 + 5) % frei.size()])
|
||||||
|
var p: Dictionary = PARZELLEN[idx]
|
||||||
|
if p.has("belegt"):
|
||||||
|
for versuch in range(frei.size()):
|
||||||
|
idx = int(frei[(i * 11 + 5 + versuch) % frei.size()])
|
||||||
|
p = PARZELLEN[idx]
|
||||||
|
if not p.has("belegt"):
|
||||||
|
break
|
||||||
|
p["belegt"] = true
|
||||||
|
PLACES[heim_key(i)] = {
|
||||||
|
"name": "%s %d" % [_nachname(i), i + 1],
|
||||||
|
"pos": [p["pos"].x, p["pos"].z],
|
||||||
|
"size": [p["size"].x, p["size"].y],
|
||||||
|
"dir": p["dir"],
|
||||||
"color": Color.from_hsv(fmod(0.08 + float(i) * 0.113, 1.0), 0.28, 0.78),
|
"color": Color.from_hsv(fmod(0.08 + float(i) * 0.113, 1.0), 0.28, 0.78),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static func _tuer_zur_mitte(x: float, z: float) -> String:
|
static func _anker_setzen() -> void:
|
||||||
if absf(x) > absf(z):
|
for key in PLACES:
|
||||||
return "left" if x > 0.0 else "right"
|
var s: Array = PLACES[key]["size"]
|
||||||
return "forward" if z > 0.0 else "back"
|
var tuer := Orte.door(PLACES, key)
|
||||||
|
if float(s[0]) <= 0.0:
|
||||||
|
tuer = Orte.center(PLACES, key)
|
||||||
|
ANKER[key] = int(NETZ.andocken(tuer)["knoten"])
|
||||||
|
|
||||||
|
|
||||||
static func _mensch(i: int) -> Dictionary:
|
static func _mensch(i: int) -> Dictionary:
|
||||||
var anliegen: Dictionary = ANLIEGEN[i % ANLIEGEN.size()]
|
# Bis alle Wege entdeckt sind, folgt jeder Mensch dem Weg gleicher Nummer.
|
||||||
var arbeit: String = str(ARBEIT[i % ARBEIT.size()])
|
# Danach mischt sich das Feld wieder.
|
||||||
|
var a_nr: int = i % ANLIEGEN.size()
|
||||||
|
var b_nr: int = i % ABLAEUFE.size()
|
||||||
|
if i < PASSEND.size():
|
||||||
|
a_nr = int(PASSEND[i][0])
|
||||||
|
b_nr = int(PASSEND[i][1])
|
||||||
|
var anliegen: Dictionary = ANLIEGEN[a_nr]
|
||||||
var heim := heim_key(i)
|
var heim := heim_key(i)
|
||||||
|
var arbeit := _naechster_arbeitsplatz(heim, i)
|
||||||
var ablauf: Array = []
|
var ablauf: Array = []
|
||||||
for e in ABLAEUFE[i % ABLAEUFE.size()]:
|
for e in ABLAEUFE[b_nr]:
|
||||||
var ort: String = str(e[1])
|
var ort: String = str(e[1])
|
||||||
if ort == "heim":
|
if ort == "heim":
|
||||||
ort = heim
|
ort = heim
|
||||||
@@ -186,6 +437,24 @@ static func _mensch(i: int) -> Dictionary:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## Wer wo wohnt, arbeitet in der Nähe. Sonst laufen alle quer durch den Bezirk
|
||||||
|
## und kommen nie an.
|
||||||
|
static func _naechster_arbeitsplatz(heim: String, i: int) -> String:
|
||||||
|
if not PLACES.has(heim):
|
||||||
|
return str(ARBEIT[i % ARBEIT.size()])
|
||||||
|
var von := Orte.center(PLACES, heim)
|
||||||
|
var beste := INF
|
||||||
|
var ziel := str(ARBEIT[i % ARBEIT.size()])
|
||||||
|
for a in ARBEIT:
|
||||||
|
if not PLACES.has(a):
|
||||||
|
continue
|
||||||
|
var d := Orte.center(PLACES, str(a)).distance_to(von)
|
||||||
|
if d < beste:
|
||||||
|
beste = d
|
||||||
|
ziel = str(a)
|
||||||
|
return ziel
|
||||||
|
|
||||||
|
|
||||||
static func _vorname(i: int) -> String:
|
static func _vorname(i: int) -> String:
|
||||||
return str(VORNAMEN[i % VORNAMEN.size()])
|
return str(VORNAMEN[i % VORNAMEN.size()])
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func _setze_welt(neu: Node3D) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
Game.advance_time(delta)
|
Game.advance_time(delta * Game.zeit_faktor())
|
||||||
Game.teleport_lock = maxf(0.0, Game.teleport_lock - delta)
|
Game.teleport_lock = maxf(0.0, Game.teleport_lock - delta)
|
||||||
if ui.offen:
|
if ui.offen:
|
||||||
ui.setze_hinweis("")
|
ui.setze_hinweis("")
|
||||||
@@ -132,6 +132,11 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||||
player.look_enabled = true
|
player.look_enabled = true
|
||||||
return
|
return
|
||||||
|
for stufe in [1, 2, 3]:
|
||||||
|
if event.is_action_pressed("tempo_%d" % stufe):
|
||||||
|
Game.tempo_setzen(stufe)
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
if event.is_action_pressed("journal"):
|
if event.is_action_pressed("journal"):
|
||||||
ui.journal_panel()
|
ui.journal_panel()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
|
|||||||
141
scripts/netz.gd
Normal file
141
scripts/netz.gd
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
class_name Netz
|
||||||
|
extends RefCounted
|
||||||
|
## Ein Wegenetz: Knoten und Kanten in der Ebene. Wege wachsen daran, indem
|
||||||
|
## neue Punkte an die nächste Stelle des bestehenden Netzes angebunden werden.
|
||||||
|
## Wiederverwendung ist gratis -- deshalb bündeln sich Wege und gabeln sich,
|
||||||
|
## statt sternförmig zur Mitte zu laufen.
|
||||||
|
|
||||||
|
var knoten: Array[Vector3] = []
|
||||||
|
var kanten: Array = [] # [a, b] Indexpaare
|
||||||
|
|
||||||
|
|
||||||
|
func fuege_knoten(p: Vector3) -> int:
|
||||||
|
var q := Vector3(p.x, 0.0, p.z)
|
||||||
|
for i in range(knoten.size()):
|
||||||
|
if knoten[i].distance_to(q) < 0.6:
|
||||||
|
return i
|
||||||
|
knoten.append(q)
|
||||||
|
return knoten.size() - 1
|
||||||
|
|
||||||
|
|
||||||
|
func verbinde(a: int, b: int) -> void:
|
||||||
|
if a == b:
|
||||||
|
return
|
||||||
|
for k in kanten:
|
||||||
|
if (k[0] == a and k[1] == b) or (k[0] == b and k[1] == a):
|
||||||
|
return
|
||||||
|
kanten.append([a, b])
|
||||||
|
|
||||||
|
|
||||||
|
func nachbarn(i: int) -> Array:
|
||||||
|
var out: Array = []
|
||||||
|
for k in kanten:
|
||||||
|
if k[0] == i:
|
||||||
|
out.append(k[1])
|
||||||
|
elif k[1] == i:
|
||||||
|
out.append(k[0])
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
## Nächster Punkt auf einer Kante -- gibt {knoten, dist} zurück und teilt die
|
||||||
|
## Kante, falls der Fußpunkt zwischen zwei Knoten liegt.
|
||||||
|
func andocken(p: Vector3) -> Dictionary:
|
||||||
|
var q := Vector3(p.x, 0.0, p.z)
|
||||||
|
var beste := INF
|
||||||
|
var best_i := -1
|
||||||
|
var best_k := -1
|
||||||
|
var best_p := Vector3.ZERO
|
||||||
|
for i in range(knoten.size()):
|
||||||
|
var d := knoten[i].distance_to(q)
|
||||||
|
if d < beste:
|
||||||
|
beste = d
|
||||||
|
best_i = i
|
||||||
|
best_k = -1
|
||||||
|
for ki in range(kanten.size()):
|
||||||
|
var a: Vector3 = knoten[kanten[ki][0]]
|
||||||
|
var b: Vector3 = knoten[kanten[ki][1]]
|
||||||
|
var f := Geometry3D.get_closest_point_to_segment(q, a, b)
|
||||||
|
var d := f.distance_to(q)
|
||||||
|
if d < beste - 0.01 and f.distance_to(a) > 1.5 and f.distance_to(b) > 1.5:
|
||||||
|
beste = d
|
||||||
|
best_k = ki
|
||||||
|
best_p = f
|
||||||
|
if best_k < 0:
|
||||||
|
return {"knoten": best_i, "dist": beste, "punkt": knoten[best_i]}
|
||||||
|
# Kante teilen
|
||||||
|
var alt: Array = kanten[best_k]
|
||||||
|
kanten.remove_at(best_k)
|
||||||
|
var neu := fuege_knoten(best_p)
|
||||||
|
verbinde(alt[0], neu)
|
||||||
|
verbinde(neu, alt[1])
|
||||||
|
return {"knoten": neu, "dist": beste, "punkt": knoten[neu]}
|
||||||
|
|
||||||
|
|
||||||
|
## Kürzester Weg als Knotenfolge, leer wenn es keinen gibt.
|
||||||
|
func pfad(start: int, ziel: int) -> Array:
|
||||||
|
if start == ziel:
|
||||||
|
return [start]
|
||||||
|
var offen: Array = [start]
|
||||||
|
var kosten: Dictionary = {start: 0.0}
|
||||||
|
var her: Dictionary = {}
|
||||||
|
while not offen.is_empty():
|
||||||
|
var best := 0
|
||||||
|
for i in range(1, offen.size()):
|
||||||
|
if float(kosten[offen[i]]) < float(kosten[offen[best]]):
|
||||||
|
best = i
|
||||||
|
var k: int = offen[best]
|
||||||
|
offen.remove_at(best)
|
||||||
|
if k == ziel:
|
||||||
|
break
|
||||||
|
for n in nachbarn(k):
|
||||||
|
var neu: float = float(kosten[k]) + knoten[k].distance_to(knoten[n])
|
||||||
|
if not kosten.has(n) or neu < float(kosten[n]) - 0.001:
|
||||||
|
kosten[n] = neu
|
||||||
|
her[n] = k
|
||||||
|
if not offen.has(n):
|
||||||
|
offen.append(n)
|
||||||
|
if not kosten.has(ziel):
|
||||||
|
return []
|
||||||
|
var weg: Array = [ziel]
|
||||||
|
var cur: int = ziel
|
||||||
|
while cur != start:
|
||||||
|
cur = int(her[cur])
|
||||||
|
weg.push_front(cur)
|
||||||
|
return weg
|
||||||
|
|
||||||
|
|
||||||
|
func weglaenge(start: int, ziel: int) -> float:
|
||||||
|
var w := pfad(start, ziel)
|
||||||
|
if w.size() < 2:
|
||||||
|
return 0.0 if w.size() == 1 else INF
|
||||||
|
var l := 0.0
|
||||||
|
for i in range(w.size() - 1):
|
||||||
|
l += knoten[w[i]].distance_to(knoten[w[i + 1]])
|
||||||
|
return l
|
||||||
|
|
||||||
|
|
||||||
|
## Hängen alle Knoten zusammen?
|
||||||
|
func zusammenhaengend() -> bool:
|
||||||
|
if knoten.is_empty():
|
||||||
|
return true
|
||||||
|
var gesehen: Dictionary = {0: true}
|
||||||
|
var stapel: Array = [0]
|
||||||
|
while not stapel.is_empty():
|
||||||
|
var k: int = stapel.pop_back()
|
||||||
|
for n in nachbarn(k):
|
||||||
|
if not gesehen.has(n):
|
||||||
|
gesehen[n] = true
|
||||||
|
stapel.append(n)
|
||||||
|
return gesehen.size() == knoten.size()
|
||||||
|
|
||||||
|
|
||||||
|
## Schneidet die Strecke a-b ein Rechteck (Mittelpunkt c, Größe s, mit Rand)?
|
||||||
|
static func trifft_rechteck(a: Vector3, b: Vector3, c: Vector3, s: Vector2, rand: float) -> bool:
|
||||||
|
var hx := s.x * 0.5 + rand
|
||||||
|
var hz := s.y * 0.5 + rand
|
||||||
|
var schritte := int(maxf(2.0, a.distance_to(b) / 1.5))
|
||||||
|
for i in range(schritte + 1):
|
||||||
|
var p := a.lerp(b, float(i) / float(schritte))
|
||||||
|
if absf(p.x - c.x) <= hx and absf(p.z - c.z) <= hz:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
1
scripts/netz.gd.uid
Normal file
1
scripts/netz.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dsaev7wupa8ex
|
||||||
@@ -58,11 +58,22 @@ static func door(places: Dictionary, key: String) -> Vector3:
|
|||||||
return c + d * (half + 2.5)
|
return c + d * (half + 2.5)
|
||||||
|
|
||||||
|
|
||||||
## Route von Ort a nach Ort b: aus der Tür, über den freien Platz, wieder hinein.
|
## Route von Ort a nach Ort b. Gibt es ein Wegenetz, wird darauf gelaufen --
|
||||||
static func route(places: Dictionary, a: String, b: String, offset: Vector3) -> Array:
|
## sonst über den freien Platz in der Mitte.
|
||||||
|
static func route(places: Dictionary, a: String, b: String, offset: Vector3,
|
||||||
|
netz: Netz = null, anker: Dictionary = {}) -> Array:
|
||||||
var pts: Array = []
|
var pts: Array = []
|
||||||
if a == b:
|
if a == b:
|
||||||
return pts
|
return pts
|
||||||
|
if netz != null and anker.has(a) and anker.has(b):
|
||||||
|
var weg := netz.pfad(int(anker[a]), int(anker[b]))
|
||||||
|
if not weg.is_empty():
|
||||||
|
for idx in weg:
|
||||||
|
var k: Vector3 = netz.knoten[idx]
|
||||||
|
if pts.is_empty() or pts[pts.size() - 1].distance_to(k) > 0.6:
|
||||||
|
pts.append(k)
|
||||||
|
pts.append(center(places, b) + offset)
|
||||||
|
return pts
|
||||||
var da := door(places, a)
|
var da := door(places, a)
|
||||||
var db := door(places, b)
|
var db := door(places, b)
|
||||||
pts.append(da)
|
pts.append(da)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ static var LIST: Array = [
|
|||||||
"was": "25 Minuten am Stück an einer Sache arbeiten, dann 5 Minuten Pause. Nach vier Runden eine lange Pause.",
|
"was": "25 Minuten am Stück an einer Sache arbeiten, dann 5 Minuten Pause. Nach vier Runden eine lange Pause.",
|
||||||
"wann": "Wenn eine Aufgabe zu groß wirkt und der Anfang immer wieder verschoben wird.",
|
"wann": "Wenn eine Aufgabe zu groß wirkt und der Anfang immer wieder verschoben wird.",
|
||||||
"warum": "Ein klar begrenzter Zeitraum senkt die Einstiegshürde. Der Kopf muss nur 25 Minuten durchhalten, nicht den ganzen Tag.",
|
"warum": "Ein klar begrenzter Zeitraum senkt die Einstiegshürde. Der Kopf muss nur 25 Minuten durchhalten, nicht den ganzen Tag.",
|
||||||
|
"woran": "jemandem, der zögert — oder der arbeitet und trotzdem nicht vorankommt.",
|
||||||
"tags": ["prokrastination", "fokus"],
|
"tags": ["prokrastination", "fokus"],
|
||||||
"momente": ["arbeitet", "zoegert"],
|
"momente": ["arbeitet", "zoegert"],
|
||||||
"farbe": Color(0.95, 0.45, 0.30),
|
"farbe": Color(0.95, 0.45, 0.30),
|
||||||
@@ -19,6 +20,7 @@ static var LIST: Array = [
|
|||||||
"was": "Was unter zwei Minuten dauert, sofort erledigen. Große Vorhaben auf einen Zwei-Minuten-Anfang schrumpfen.",
|
"was": "Was unter zwei Minuten dauert, sofort erledigen. Große Vorhaben auf einen Zwei-Minuten-Anfang schrumpfen.",
|
||||||
"wann": "Bei einem Berg von Kleinkram oder wenn schon der erste Schritt blockiert.",
|
"wann": "Bei einem Berg von Kleinkram oder wenn schon der erste Schritt blockiert.",
|
||||||
"warum": "Der Start kostet die meiste Energie. Zwei Minuten sind zu klein zum Ablehnen und ziehen meist die ganze Aufgabe nach sich.",
|
"warum": "Der Start kostet die meiste Energie. Zwei Minuten sind zu klein zum Ablehnen und ziehen meist die ganze Aufgabe nach sich.",
|
||||||
|
"woran": "jemandem, der zögert, lieber aufräumt als anzufangen, oder beim Arbeiten im Kleinkram hängt.",
|
||||||
"tags": ["prokrastination", "ueberforderung"],
|
"tags": ["prokrastination", "ueberforderung"],
|
||||||
"momente": ["zoegert", "aufraeumen", "arbeitet"],
|
"momente": ["zoegert", "aufraeumen", "arbeitet"],
|
||||||
"farbe": Color(0.98, 0.72, 0.25),
|
"farbe": Color(0.98, 0.72, 0.25),
|
||||||
@@ -29,6 +31,7 @@ static var LIST: Array = [
|
|||||||
"was": "Den Vorsatz an einen konkreten Auslöser binden: \"Wenn ich X tue, dann mache ich Y.\"",
|
"was": "Den Vorsatz an einen konkreten Auslöser binden: \"Wenn ich X tue, dann mache ich Y.\"",
|
||||||
"wann": "Wenn ein Vorhaben im Alltag jedes Mal untergeht.",
|
"wann": "Wenn ein Vorhaben im Alltag jedes Mal untergeht.",
|
||||||
"warum": "Der Plan hängt an einer Situation statt an Willenskraft. Der Auslöser erinnert von allein.",
|
"warum": "Der Plan hängt an einer Situation statt an Willenskraft. Der Auslöser erinnert von allein.",
|
||||||
|
"woran": "der Morgenroutine und der Pause — den festen Punkten, an die sich ein Vorsatz hängen lässt.",
|
||||||
"tags": ["gewohnheit", "prokrastination"],
|
"tags": ["gewohnheit", "prokrastination"],
|
||||||
"momente": ["morgenroutine", "pause"],
|
"momente": ["morgenroutine", "pause"],
|
||||||
"farbe": Color(0.45, 0.80, 0.55),
|
"farbe": Color(0.45, 0.80, 0.55),
|
||||||
@@ -39,6 +42,7 @@ static var LIST: Array = [
|
|||||||
"was": "Eine neue, kleine Gewohnheit direkt an eine bestehende anhängen.",
|
"was": "Eine neue, kleine Gewohnheit direkt an eine bestehende anhängen.",
|
||||||
"wann": "Wenn eine neue Routine nie einen Platz im Tag findet.",
|
"wann": "Wenn eine neue Routine nie einen Platz im Tag findet.",
|
||||||
"warum": "Die alte Gewohnheit ist der Anker. Es muss kein neuer Zeitpunkt erfunden werden.",
|
"warum": "Die alte Gewohnheit ist der Anker. Es muss kein neuer Zeitpunkt erfunden werden.",
|
||||||
|
"woran": "den Ankern des Tages: Morgenroutine, Essen, Abendroutine.",
|
||||||
"tags": ["gewohnheit"],
|
"tags": ["gewohnheit"],
|
||||||
"momente": ["morgenroutine", "essen", "abendroutine"],
|
"momente": ["morgenroutine", "essen", "abendroutine"],
|
||||||
"farbe": Color(0.40, 0.70, 0.85),
|
"farbe": Color(0.40, 0.70, 0.85),
|
||||||
@@ -49,6 +53,7 @@ static var LIST: Array = [
|
|||||||
"was": "Ein großes Ziel in benennbare Einzelschritte teilen, bis der nächste Schritt trivial ist.",
|
"was": "Ein großes Ziel in benennbare Einzelschritte teilen, bis der nächste Schritt trivial ist.",
|
||||||
"wann": "Wenn jemand vor einem Vorhaben erstarrt und nicht weiß, wo er anfangen soll.",
|
"wann": "Wenn jemand vor einem Vorhaben erstarrt und nicht weiß, wo er anfangen soll.",
|
||||||
"warum": "Unklarheit lähmt stärker als Aufwand. Ein sichtbarer nächster Schritt macht Handeln wieder möglich.",
|
"warum": "Unklarheit lähmt stärker als Aufwand. Ein sichtbarer nächster Schritt macht Handeln wieder möglich.",
|
||||||
|
"woran": "jemandem, der grübelt statt anzufangen, oder der arbeitet und sich im Vorhaben verliert.",
|
||||||
"tags": ["ueberforderung", "prokrastination", "ziel"],
|
"tags": ["ueberforderung", "prokrastination", "ziel"],
|
||||||
"momente": ["gruebelt", "arbeitet"],
|
"momente": ["gruebelt", "arbeitet"],
|
||||||
"farbe": Color(0.75, 0.55, 0.95),
|
"farbe": Color(0.75, 0.55, 0.95),
|
||||||
@@ -59,6 +64,7 @@ static var LIST: Array = [
|
|||||||
"was": "Das gewünschte Verhalten leichter machen, das störende schwerer. Handy in einen anderen Raum, Instrument sichtbar aufstellen.",
|
"was": "Das gewünschte Verhalten leichter machen, das störende schwerer. Handy in einen anderen Raum, Instrument sichtbar aufstellen.",
|
||||||
"wann": "Wenn gute Vorsätze regelmäßig an Ablenkung scheitern.",
|
"wann": "Wenn gute Vorsätze regelmäßig an Ablenkung scheitern.",
|
||||||
"warum": "Verhalten folgt dem geringsten Widerstand. Die Umgebung wirkt zuverlässiger als Disziplin.",
|
"warum": "Verhalten folgt dem geringsten Widerstand. Die Umgebung wirkt zuverlässiger als Disziplin.",
|
||||||
|
"woran": "jemandem, der aufräumt, arbeitet oder sich ausruht — dann ist der Raum um ihn herum greifbar.",
|
||||||
"tags": ["ablenkung", "gewohnheit"],
|
"tags": ["ablenkung", "gewohnheit"],
|
||||||
"momente": ["aufraeumen", "arbeitet", "ruht"],
|
"momente": ["aufraeumen", "arbeitet", "ruht"],
|
||||||
"farbe": Color(0.55, 0.85, 0.40),
|
"farbe": Color(0.55, 0.85, 0.40),
|
||||||
@@ -69,6 +75,7 @@ static var LIST: Array = [
|
|||||||
"was": "Einen belastenden Gedanken prüfen und durch eine ebenso wahre, aber tragfähigere Sicht ersetzen.",
|
"was": "Einen belastenden Gedanken prüfen und durch eine ebenso wahre, aber tragfähigere Sicht ersetzen.",
|
||||||
"wann": "Wenn jemand in einem Gedanken feststeckt, der ihn klein hält.",
|
"wann": "Wenn jemand in einem Gedanken feststeckt, der ihn klein hält.",
|
||||||
"warum": "Nicht das Ereignis belastet, sondern seine Deutung. Eine andere Deutung verändert Gefühl und Handlung.",
|
"warum": "Nicht das Ereignis belastet, sondern seine Deutung. Eine andere Deutung verändert Gefühl und Handlung.",
|
||||||
|
"woran": "jemandem, der grübelt, angespannt ist oder sich ausruht und den Gedanken doch nicht loswird.",
|
||||||
"tags": ["selbstzweifel", "angst"],
|
"tags": ["selbstzweifel", "angst"],
|
||||||
"momente": ["gruebelt", "ruht", "angespannt"],
|
"momente": ["gruebelt", "ruht", "angespannt"],
|
||||||
"farbe": Color(0.95, 0.55, 0.75),
|
"farbe": Color(0.95, 0.55, 0.75),
|
||||||
@@ -79,6 +86,7 @@ static var LIST: Array = [
|
|||||||
"was": "Vier Sekunden einatmen, vier halten, vier ausatmen, vier halten. Mehrere Runden lang.",
|
"was": "Vier Sekunden einatmen, vier halten, vier ausatmen, vier halten. Mehrere Runden lang.",
|
||||||
"wann": "Bei akuter Anspannung, kurz vor einem gefürchteten Gespräch oder Auftritt.",
|
"wann": "Bei akuter Anspannung, kurz vor einem gefürchteten Gespräch oder Auftritt.",
|
||||||
"warum": "Langes Ausatmen bremst den Alarmzustand des Körpers. Der Puls sinkt, der Kopf wird wieder klar.",
|
"warum": "Langes Ausatmen bremst den Alarmzustand des Körpers. Der Puls sinkt, der Kopf wird wieder klar.",
|
||||||
|
"woran": "jemandem, der angespannt ist, wartet oder in der Pause nicht runterkommt.",
|
||||||
"tags": ["angst", "stress"],
|
"tags": ["angst", "stress"],
|
||||||
"momente": ["angespannt", "wartet", "pause"],
|
"momente": ["angespannt", "wartet", "pause"],
|
||||||
"farbe": Color(0.40, 0.85, 0.95),
|
"farbe": Color(0.40, 0.85, 0.95),
|
||||||
@@ -89,6 +97,7 @@ static var LIST: Array = [
|
|||||||
"was": "Mit sich selbst so sprechen wie mit einem guten Freund in derselben Lage.",
|
"was": "Mit sich selbst so sprechen wie mit einem guten Freund in derselben Lage.",
|
||||||
"wann": "Nach einem Fehler oder Rückschlag, wenn die innere Kritik lauter wird als der Vorsatz.",
|
"wann": "Nach einem Fehler oder Rückschlag, wenn die innere Kritik lauter wird als der Vorsatz.",
|
||||||
"warum": "Harte Selbstkritik erhöht die Vermeidung. Freundlichkeit hält handlungsfähig und senkt die Abbruchquote.",
|
"warum": "Harte Selbstkritik erhöht die Vermeidung. Freundlichkeit hält handlungsfähig und senkt die Abbruchquote.",
|
||||||
|
"woran": "jemandem, der nach einem Fehler grübelt, angespannt ist oder sich ausruht, ohne zur Ruhe zu kommen.",
|
||||||
"tags": ["selbstzweifel", "rueckschlag"],
|
"tags": ["selbstzweifel", "rueckschlag"],
|
||||||
"momente": ["gruebelt", "ruht", "angespannt"],
|
"momente": ["gruebelt", "ruht", "angespannt"],
|
||||||
"farbe": Color(1.0, 0.80, 0.60),
|
"farbe": Color(1.0, 0.80, 0.60),
|
||||||
@@ -99,6 +108,7 @@ static var LIST: Array = [
|
|||||||
"was": "Feste Aufsteh- und Schlafenszeit, die letzte Stunde ohne Bildschirm, Zimmer dunkel und kühl.",
|
"was": "Feste Aufsteh- und Schlafenszeit, die letzte Stunde ohne Bildschirm, Zimmer dunkel und kühl.",
|
||||||
"wann": "Bei Dauermüdigkeit, Reizbarkeit und wegbrechender Konzentration.",
|
"wann": "Bei Dauermüdigkeit, Reizbarkeit und wegbrechender Konzentration.",
|
||||||
"warum": "Der innere Rhythmus braucht feste Anker. Erholter Schlaf ist die Grundlage für jede andere Veränderung.",
|
"warum": "Der innere Rhythmus braucht feste Anker. Erholter Schlaf ist die Grundlage für jede andere Veränderung.",
|
||||||
|
"woran": "der Abendroutine, an jemandem, der müde ist oder sich mitten am Tag ausruht.",
|
||||||
"tags": ["erschoepfung", "gesundheit"],
|
"tags": ["erschoepfung", "gesundheit"],
|
||||||
"momente": ["abendroutine", "muede", "ruht"],
|
"momente": ["abendroutine", "muede", "ruht"],
|
||||||
"farbe": Color(0.45, 0.50, 0.90),
|
"farbe": Color(0.45, 0.50, 0.90),
|
||||||
@@ -109,6 +119,7 @@ static var LIST: Array = [
|
|||||||
"was": "Eine klare, kleine Bitte an eine konkrete Person richten, statt allgemein zu hoffen.",
|
"was": "Eine klare, kleine Bitte an eine konkrete Person richten, statt allgemein zu hoffen.",
|
||||||
"wann": "Wenn jemand allein an etwas hängen bleibt, das andere leicht lösen könnten.",
|
"wann": "Wenn jemand allein an etwas hängen bleibt, das andere leicht lösen könnten.",
|
||||||
"warum": "Vage Bitten werden überhört. Menschen helfen gern, wenn sie genau wissen, was zu tun ist.",
|
"warum": "Vage Bitten werden überhört. Menschen helfen gern, wenn sie genau wissen, was zu tun ist.",
|
||||||
|
"woran": "jemandem, der wartet, Pause macht oder unterwegs ist — da ist Luft für eine Bitte.",
|
||||||
"tags": ["einsamkeit", "ueberforderung"],
|
"tags": ["einsamkeit", "ueberforderung"],
|
||||||
"momente": ["wartet", "pause", "unterwegs"],
|
"momente": ["wartet", "pause", "unterwegs"],
|
||||||
"farbe": Color(0.90, 0.90, 0.45),
|
"farbe": Color(0.90, 0.90, 0.45),
|
||||||
@@ -119,6 +130,7 @@ static var LIST: Array = [
|
|||||||
"was": "Absagen und zugleich benennen, was ginge: \"Heute nicht, am Donnerstag gern.\"",
|
"was": "Absagen und zugleich benennen, was ginge: \"Heute nicht, am Donnerstag gern.\"",
|
||||||
"wann": "Wenn der eigene Kalender von fremden Anliegen aufgebraucht wird.",
|
"wann": "Wenn der eigene Kalender von fremden Anliegen aufgebraucht wird.",
|
||||||
"warum": "Ein nacktes Nein fühlt sich riskant an. Mit Alternative bleibt die Beziehung heil und die Zeit geschützt.",
|
"warum": "Ein nacktes Nein fühlt sich riskant an. Mit Alternative bleibt die Beziehung heil und die Zeit geschützt.",
|
||||||
|
"woran": "jemandem, der arbeitet, angespannt ist oder unterwegs zwischen Terminen hängt.",
|
||||||
"tags": ["stress", "grenzen"],
|
"tags": ["stress", "grenzen"],
|
||||||
"momente": ["arbeitet", "angespannt", "unterwegs"],
|
"momente": ["arbeitet", "angespannt", "unterwegs"],
|
||||||
"farbe": Color(0.85, 0.35, 0.45),
|
"farbe": Color(0.85, 0.35, 0.45),
|
||||||
@@ -126,38 +138,6 @@ static var LIST: Array = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
static var TAG_TEXT: Dictionary = {
|
|
||||||
"prokrastination": "Aufschieben",
|
|
||||||
"fokus": "Konzentration",
|
|
||||||
"ueberforderung": "Überforderung",
|
|
||||||
"gewohnheit": "Gewohnheiten",
|
|
||||||
"ziel": "Ziele",
|
|
||||||
"ablenkung": "Ablenkung",
|
|
||||||
"selbstzweifel": "Selbstzweifel",
|
|
||||||
"angst": "Angst",
|
|
||||||
"stress": "Stress",
|
|
||||||
"rueckschlag": "Rückschläge",
|
|
||||||
"erschoepfung": "Erschöpfung",
|
|
||||||
"gesundheit": "Gesundheit",
|
|
||||||
"einsamkeit": "Einsamkeit",
|
|
||||||
"grenzen": "Grenzen setzen",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static func tags_text(tags: Array) -> String:
|
|
||||||
var out: Array = []
|
|
||||||
for t in tags:
|
|
||||||
out.append(TAG_TEXT.get(t, t))
|
|
||||||
return ", ".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
static func momente_text(momente: Array) -> String:
|
|
||||||
var out: Array = []
|
|
||||||
for m in momente:
|
|
||||||
out.append(Orte.text(m))
|
|
||||||
return ", ".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
static func by_id(id: String) -> Dictionary:
|
static func by_id(id: String) -> Dictionary:
|
||||||
for t in LIST:
|
for t in LIST:
|
||||||
if t["id"] == id:
|
if t["id"] == id:
|
||||||
|
|||||||
144
scripts/ui.gd
144
scripts/ui.gd
@@ -48,7 +48,7 @@ func _baue_hud() -> void:
|
|||||||
add_child(_hud)
|
add_child(_hud)
|
||||||
|
|
||||||
var steuerung := Label.new()
|
var steuerung := Label.new()
|
||||||
steuerung.text = "WASD Bewegen · Maus Umsehen · E Interagieren · J Journal · Esc Maus frei"
|
steuerung.text = "WASD Bewegen · Maus Umsehen · E Interagieren · J Journal · 1/2/3 Tempo · Esc Maus frei"
|
||||||
steuerung.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
steuerung.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||||
steuerung.position = Vector2(-640, 16)
|
steuerung.position = Vector2(-640, 16)
|
||||||
steuerung.size = Vector2(620, 24)
|
steuerung.size = Vector2(620, 24)
|
||||||
@@ -144,9 +144,14 @@ func _process(_delta: float) -> void:
|
|||||||
|
|
||||||
func _hud_aktualisieren() -> void:
|
func _hud_aktualisieren() -> void:
|
||||||
var zeilen: Array = []
|
var zeilen: Array = []
|
||||||
zeilen.append("[b]%s[/b]" % Game.time_string())
|
var takt := ("[color=#aaaaaa]Tempo %d (%d×)[/color]"
|
||||||
zeilen.append("Einsicht: [b]%d[/b] Techniken: [b]%d / %d[/b]" %
|
% [Game.tempo, int(Game.faktor_von(Game.tempo))])
|
||||||
[Game.einsicht, Game.known.size(), TechDB.LIST.size()])
|
if Game.schlaeft_ziel():
|
||||||
|
takt = ("[color=#9fb6ff]Tempo 3 (%d×) · Nachtsprung[/color]"
|
||||||
|
% int(Game.faktor_von(3)))
|
||||||
|
zeilen.append("[b]%s[/b] %s" % [Game.time_string(), takt])
|
||||||
|
zeilen.append("Techniken: [b]%d[/b] gelernt · [b]%d[/b] entdeckt · %d im Spiel" %
|
||||||
|
[Game.known.size(), Game.offene_techniken(), TechDB.LIST.size()])
|
||||||
if Game.active_mission != "":
|
if Game.active_mission != "":
|
||||||
var h := HumanDB.by_id(Game.active_mission)
|
var h := HumanDB.by_id(Game.active_mission)
|
||||||
zeilen.append("Mission: [color=#ffdd77]%s[/color] — %d %% · %d Regeln" %
|
zeilen.append("Mission: [color=#ffdd77]%s[/color] — %d %% · %d Regeln" %
|
||||||
@@ -246,8 +251,9 @@ func schliessen() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _hinweis_block(t: Dictionary) -> String:
|
func _hinweis_block(t: Dictionary) -> String:
|
||||||
return ("[b]Was:[/b] %s\n[b]Wann:[/b] %s\n[b]Warum:[/b] %s" %
|
return ("[b]Was:[/b] %s\n[b]Wann:[/b] %s\n[b]Warum:[/b] %s\n" +
|
||||||
[t["was"], t["wann"], t["warum"]])
|
"[color=#9fb6ff][b]Zu sehen an:[/b] %s[/color]") % \
|
||||||
|
[t["was"], t["wann"], t["warum"], t["woran"]]
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------- Journal
|
# -------------------------------------------------------- Journal
|
||||||
@@ -294,18 +300,17 @@ func _reiter() -> void:
|
|||||||
if Game.active_mission != "":
|
if Game.active_mission != "":
|
||||||
mission = "%s (%d %%)" % [HumanDB.by_id(Game.active_mission)["name"],
|
mission = "%s (%d %%)" % [HumanDB.by_id(Game.active_mission)["name"],
|
||||||
int(Game.mission_progress(Game.active_mission))]
|
int(Game.mission_progress(Game.active_mission))]
|
||||||
_text("[color=#aaaaaa]Einsicht: [b]%d[/b] · Mission: %s[/color]" %
|
_text("[color=#aaaaaa]Mission: %s[/color]" % mission, 15)
|
||||||
[Game.einsicht, mission], 15)
|
|
||||||
_trenner()
|
_trenner()
|
||||||
|
|
||||||
|
|
||||||
func _journal_techniken() -> void:
|
func _journal_techniken() -> void:
|
||||||
if Game.known.is_empty():
|
if Game.known.is_empty():
|
||||||
_text("[color=#ffcc66]Du hast noch keine Technik gelernt.[/color]\n" +
|
_text("[color=#ffcc66]Du hast noch keine Technik gelernt.[/color]\n" +
|
||||||
"Im [b]Hörsaal der Wege[/b] hinter dem Dorfplatz wird der erste Weg gelehrt.", 16)
|
"Im [b]Hörsaal der Wege[/b] hinter dem Dorfplatz werden sie gelehrt.", 16)
|
||||||
return
|
return
|
||||||
_text("Was du gelernt hast. Jede Anwendung draußen macht eine Technik von allein " +
|
_text("Was du gelernt hast. Besser wird eine Technik nur, indem du sie draußen " +
|
||||||
"besser; vertieft wird im Hörsaal.", 15)
|
"anwendest — Üben lässt sie sich nicht.", 15)
|
||||||
_trenner()
|
_trenner()
|
||||||
for id in TechDB.ids():
|
for id in TechDB.ids():
|
||||||
if not Game.knows(id):
|
if not Game.knows(id):
|
||||||
@@ -314,8 +319,6 @@ func _journal_techniken() -> void:
|
|||||||
_text("[b][color=#ffe3a0]%s[/color][/b] · Stufe %d (%s)" %
|
_text("[b][color=#ffe3a0]%s[/color][/b] · Stufe %d (%s)" %
|
||||||
[t["name"], Game.skill_of(id), Game.xp_to_next(id)], 18)
|
[t["name"], Game.skill_of(id), Game.xp_to_next(id)], 18)
|
||||||
_text(_hinweis_block(t), 15)
|
_text(_hinweis_block(t), 15)
|
||||||
_text("[color=#9fb6ff]Hilft bei:[/color] %s [color=#9fb6ff]Momente:[/color] %s" %
|
|
||||||
[TechDB.tags_text(t["tags"]), TechDB.momente_text(t["momente"])], 14)
|
|
||||||
_trenner()
|
_trenner()
|
||||||
|
|
||||||
|
|
||||||
@@ -335,7 +338,6 @@ func _journal_missionen() -> void:
|
|||||||
[h["name"], h["alter"], h["beruf"]], 18)
|
[h["name"], h["alter"], h["beruf"]], 18)
|
||||||
_text("[b]Traum:[/b] %s" % h["traum"], 16)
|
_text("[b]Traum:[/b] %s" % h["traum"], 16)
|
||||||
_text("[b]Was im Weg steht:[/b] %s" % h["problem"], 15)
|
_text("[b]Was im Weg steht:[/b] %s" % h["problem"], 15)
|
||||||
_text("[color=#9fb6ff]Themen:[/color] %s" % TechDB.tags_text(h["tags"]), 14)
|
|
||||||
if Game.completed.has(id):
|
if Game.completed.has(id):
|
||||||
_text("[color=#88ff99]★ Der Traum ist erfüllt.[/color]", 15)
|
_text("[color=#88ff99]★ Der Traum ist erfüllt.[/color]", 15)
|
||||||
elif Game.active_mission == id:
|
elif Game.active_mission == id:
|
||||||
@@ -359,7 +361,8 @@ func pipeline_panel(human_id: String) -> void:
|
|||||||
var h := HumanDB.by_id(human_id)
|
var h := HumanDB.by_id(human_id)
|
||||||
_leeren("Regelwerk — %s" % h["name"])
|
_leeren("Regelwerk — %s" % h["name"])
|
||||||
_text("Jede Tätigkeit im Tag von %s kann eine Technik auslösen. " % h["name"] +
|
_text("Jede Tätigkeit im Tag von %s kann eine Technik auslösen. " % h["name"] +
|
||||||
"Wechselt sie hinein, greift die Regel von allein — solange du auf der Erde bist.", 15)
|
"Wechselt sie hinein, greift die Regel von allein — solange du auf der Erde bist.\n" +
|
||||||
|
"Nach jedem Einsatz braucht eine Technik Ruhe, bevor sie wieder anschlägt.", 15)
|
||||||
_text("[color=#aaaaaa]Fortschritt: [b]%d %%[/b] · Regeln: [b]%d[/b][/color]" %
|
_text("[color=#aaaaaa]Fortschritt: [b]%d %%[/b] · Regeln: [b]%d[/b][/color]" %
|
||||||
[int(Game.mission_progress(human_id)), Game.regel_anzahl(human_id)], 15)
|
[int(Game.mission_progress(human_id)), Game.regel_anzahl(human_id)], 15)
|
||||||
_trenner()
|
_trenner()
|
||||||
@@ -371,9 +374,9 @@ func pipeline_panel(human_id: String) -> void:
|
|||||||
var tech: String = Game.regel_fuer(human_id, activity)
|
var tech: String = Game.regel_fuer(human_id, activity)
|
||||||
var beschriftung := "%s → keine Regel" % tat
|
var beschriftung := "%s → keine Regel" % tat
|
||||||
if tech != "":
|
if tech != "":
|
||||||
var ev := Game.evaluate(tech, human_id, activity, false)
|
beschriftung = "%s → %s" % [tat, TechDB.by_id(tech)["name"]]
|
||||||
beschriftung = "%s → %s · %d %%" % [tat, TechDB.by_id(tech)["name"],
|
if not Game.bereit(tech):
|
||||||
int(round(float(ev["chance"]) * 100.0))]
|
beschriftung += " (noch gesperrt)"
|
||||||
_knopf(beschriftung, func() -> void: regel_panel(human_id, activity))
|
_knopf(beschriftung, func() -> void: regel_panel(human_id, activity))
|
||||||
_trenner()
|
_trenner()
|
||||||
_knopf("Zurück zum Journal", func() -> void: journal_panel("missionen"))
|
_knopf("Zurück zum Journal", func() -> void: journal_panel("missionen"))
|
||||||
@@ -392,38 +395,32 @@ func regel_panel(human_id: String, activity: String) -> void:
|
|||||||
_knopf("Zurück", func() -> void: pipeline_panel(human_id))
|
_knopf("Zurück", func() -> void: pipeline_panel(human_id))
|
||||||
_zeigen("regel")
|
_zeigen("regel")
|
||||||
return
|
return
|
||||||
_text("Was soll greifen, wenn %s %s? Die Erfolgschance gilt für genau diesen Moment. " %
|
_text("Was soll greifen, wenn %s %s? Ob eine Technik hier taugt, steht in ihr " %
|
||||||
[h["name"], Orte.text(activity)] +
|
[h["name"], Orte.text(activity)] +
|
||||||
"Jede Technik hält nur einen Posten — sie hier einzusetzen nimmt sie anderswo weg.", 15)
|
"selbst — nachzulesen im Journal. Ein Fehlgriff wirft den Traum zurück.", 15)
|
||||||
_trenner()
|
_trenner()
|
||||||
for id in TechDB.ids():
|
for id in TechDB.ids():
|
||||||
if not Game.knows(id):
|
if not Game.knows(id):
|
||||||
continue
|
continue
|
||||||
var t := TechDB.by_id(id)
|
var t := TechDB.by_id(id)
|
||||||
var ev := Game.evaluate(id, human_id, activity, false)
|
var ev := Game.evaluate(id, human_id, activity, false)
|
||||||
var marker: Array = []
|
var wo: Array = Game.regel_momente(human_id, id)
|
||||||
if ev["tag"]:
|
|
||||||
marker.append("[color=#88ff99]passt zum Thema[/color]")
|
|
||||||
if ev["moment"]:
|
|
||||||
marker.append("[color=#88ff99]passender Moment[/color]")
|
|
||||||
if ev["blocked"]:
|
|
||||||
marker.append("[color=#ff8877]%s[/color]" % ev["grund"])
|
|
||||||
if marker.is_empty():
|
|
||||||
marker.append("[color=#aaaaaa]weder Thema noch Moment passen[/color]")
|
|
||||||
var gewaehlt := ""
|
var gewaehlt := ""
|
||||||
var haengt := Game.regel_moment(human_id, id)
|
if wo.has(activity):
|
||||||
if haengt == activity:
|
gewaehlt = " — hier eingestellt"
|
||||||
gewaehlt = " [color=#ffdd77](hier eingestellt)[/color]"
|
elif not wo.is_empty():
|
||||||
elif haengt != "":
|
var namen: Array = []
|
||||||
gewaehlt = " [color=#ffaa66](hängt an »%s«)[/color]" % Orte.text(haengt)
|
for a in wo:
|
||||||
_text("[b]%s[/b] · Stufe %d%s\n[color=#cccccc]%s[/color]\n%s" %
|
namen.append("»%s«" % Orte.text(a))
|
||||||
[t["name"], Game.skill_of(id), gewaehlt, t["wann"], " · ".join(marker)], 16)
|
gewaehlt = " — hängt an %s" % ", ".join(namen)
|
||||||
var beschriftung := "Blockiert" if ev["blocked"] else \
|
var beschriftung := "%s · Stufe %d" % [t["name"], Game.skill_of(id)]
|
||||||
"Diese Regel — %d %% Erfolgschance" % int(round(float(ev["chance"]) * 100.0))
|
if ev["blocked"]:
|
||||||
|
beschriftung += " — " + str(ev["grund"])
|
||||||
|
elif gewaehlt != "":
|
||||||
|
beschriftung += gewaehlt
|
||||||
_knopf(beschriftung, func() -> void:
|
_knopf(beschriftung, func() -> void:
|
||||||
Game.regel_setzen(human_id, activity, id)
|
Game.regel_setzen(human_id, activity, id)
|
||||||
pipeline_panel(human_id), not ev["blocked"])
|
pipeline_panel(human_id), not ev["blocked"])
|
||||||
_trenner()
|
|
||||||
_knopf("Keine Regel für diese Tätigkeit", func() -> void:
|
_knopf("Keine Regel für diese Tätigkeit", func() -> void:
|
||||||
Game.regel_setzen(human_id, activity, "")
|
Game.regel_setzen(human_id, activity, "")
|
||||||
pipeline_panel(human_id))
|
pipeline_panel(human_id))
|
||||||
@@ -441,8 +438,8 @@ func lehre_panel(ort: String = "hoersaal") -> void:
|
|||||||
var pl: Dictionary = DreamDB.PLACES[ort]
|
var pl: Dictionary = DreamDB.PLACES[ort]
|
||||||
var ids: Array = DreamDB.techniken_von(ort)
|
var ids: Array = DreamDB.techniken_von(ort)
|
||||||
_leeren(str(pl["name"]))
|
_leeren(str(pl["name"]))
|
||||||
_text("[color=#aaaaaa]Einsicht: [b]%d[/b] · Gelernt: [b]%d / %d[/b] von %d entdeckten[/color]" %
|
_text("[color=#aaaaaa]Gelernt: [b]%d[/b] von %d entdeckten Wegen · %d im Spiel[/color]" %
|
||||||
[Game.einsicht, Game.known.size(), TechDB.LIST.size(), Game.offene_techniken()], 15)
|
[Game.known.size(), Game.offene_techniken(), TechDB.LIST.size()], 15)
|
||||||
_trenner()
|
_trenner()
|
||||||
if ids.is_empty():
|
if ids.is_empty():
|
||||||
_text("[color=#ffcc66]Hier wird nichts gelehrt.[/color]", 16)
|
_text("[color=#ffcc66]Hier wird nichts gelehrt.[/color]", 16)
|
||||||
@@ -454,16 +451,13 @@ func lehre_panel(ort: String = "hoersaal") -> void:
|
|||||||
else:
|
else:
|
||||||
_text("[b][color=#9aa0b8]%s[/color][/b] · noch nicht gelernt" % t["name"], 18)
|
_text("[b][color=#9aa0b8]%s[/color][/b] · noch nicht gelernt" % t["name"], 18)
|
||||||
_text(_hinweis_block(t), 15)
|
_text(_hinweis_block(t), 15)
|
||||||
_text("[color=#9fb6ff]Hilft bei:[/color] %s [color=#9fb6ff]Momente:[/color] %s" %
|
|
||||||
[TechDB.tags_text(t["tags"]), TechDB.momente_text(t["momente"])], 14)
|
|
||||||
if Game.knows(id):
|
if Game.knows(id):
|
||||||
_knopf("Vertiefen (1 Einsicht)", func() -> void:
|
_text("[color=#88ff99]Gelernt. Stufe %d steigt nur, wenn du diesen Weg " %
|
||||||
if Game.deepen(id):
|
Game.skill_of(id) + "draußen anwendest — hier ist nichts mehr zu holen.[/color]", 15)
|
||||||
lehre_panel(ort), Game.einsicht >= 1)
|
|
||||||
else:
|
else:
|
||||||
_knopf("Lernen (1 Einsicht)", func() -> void:
|
_knopf("Diesen Weg lernen", func() -> void:
|
||||||
if Game.learn(id):
|
if Game.learn(id):
|
||||||
lehre_panel(ort), Game.einsicht >= 1)
|
lehre_panel(ort))
|
||||||
_trenner()
|
_trenner()
|
||||||
_naechster_weg()
|
_naechster_weg()
|
||||||
_zeigen("lehre")
|
_zeigen("lehre")
|
||||||
@@ -489,19 +483,24 @@ func _naechster_weg() -> void:
|
|||||||
func brett_panel() -> void:
|
func brett_panel() -> void:
|
||||||
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "brett" else 0
|
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "brett" else 0
|
||||||
_leeren("Schwarzes Brett")
|
_leeren("Schwarzes Brett")
|
||||||
_text("Menschen, deren Traum feststeckt. Nimm einen an — das Tor zur Erde " +
|
_text("Ausgehängt wird nur, wem du mit dem, was du kannst, auch helfen kannst. " +
|
||||||
"öffnet sich nur mit einer Mission.", 15)
|
"Das Tor zur Erde öffnet sich nur mit einer Mission.", 15)
|
||||||
_trenner()
|
_trenner()
|
||||||
|
var gezeigt := 0
|
||||||
|
var verdeckt := 0
|
||||||
for h in HumanDB.LIST:
|
for h in HumanDB.LIST:
|
||||||
var id: String = h["id"]
|
var id: String = h["id"]
|
||||||
|
if Game.completed.has(id):
|
||||||
|
continue
|
||||||
|
if Game.active_mission != id and not Game.machbar(id):
|
||||||
|
verdeckt += 1
|
||||||
|
continue
|
||||||
|
gezeigt += 1
|
||||||
_text("[b][color=#ffe3a0]%s[/color][/b], %d — %s" %
|
_text("[b][color=#ffe3a0]%s[/color][/b], %d — %s" %
|
||||||
[h["name"], h["alter"], h["beruf"]], 18)
|
[h["name"], h["alter"], h["beruf"]], 18)
|
||||||
_text("[b]Traum:[/b] %s" % h["traum"], 16)
|
_text("[b]Traum:[/b] %s" % h["traum"], 16)
|
||||||
_text("[b]Was im Weg steht:[/b] %s" % h["problem"], 15)
|
_text("[b]Was im Weg steht:[/b] %s" % h["problem"], 15)
|
||||||
_text("[color=#9fb6ff]Themen:[/color] %s" % TechDB.tags_text(h["tags"]), 14)
|
if Game.active_mission == id:
|
||||||
if Game.completed.has(id):
|
|
||||||
_text("[color=#88ff99]★ Der Traum ist erfüllt.[/color]", 15)
|
|
||||||
elif Game.active_mission == id:
|
|
||||||
_text("[color=#ffdd77]Aktive Mission — %d %%[/color]" %
|
_text("[color=#ffdd77]Aktive Mission — %d %%[/color]" %
|
||||||
int(Game.mission_progress(id)), 15)
|
int(Game.mission_progress(id)), 15)
|
||||||
else:
|
else:
|
||||||
@@ -512,6 +511,12 @@ func brett_panel() -> void:
|
|||||||
Game.accept_mission(id)
|
Game.accept_mission(id)
|
||||||
brett_panel())
|
brett_panel())
|
||||||
_trenner()
|
_trenner()
|
||||||
|
if gezeigt == 0:
|
||||||
|
_text("[color=#ffcc66]Gerade hängt nichts aus, das zu deinen Wegen passt.[/color]\n" +
|
||||||
|
"Lerne im Hörsaal oder in einem Haus einen weiteren Weg.", 16)
|
||||||
|
if verdeckt > 0:
|
||||||
|
_text("[color=#aaaaaa]%d weitere Anliegen bleiben verdeckt. Keiner deiner Wege " %
|
||||||
|
verdeckt + "trifft ihr Thema in einem Moment ihres Tages.[/color]", 15)
|
||||||
_zeigen("brett")
|
_zeigen("brett")
|
||||||
_scroll_wunsch = scroll_alt
|
_scroll_wunsch = scroll_alt
|
||||||
_scroll_frames = 4
|
_scroll_frames = 4
|
||||||
@@ -538,28 +543,22 @@ func anwenden_panel(mensch: Node3D) -> void:
|
|||||||
_text("[color=#ffcc66]Du kennst keine Technik.[/color]")
|
_text("[color=#ffcc66]Du kennst keine Technik.[/color]")
|
||||||
_zeigen("anwenden")
|
_zeigen("anwenden")
|
||||||
return
|
return
|
||||||
|
_text("[color=#aaaaaa]Was eine Technik tut, steht im Journal.[/color]", 15)
|
||||||
|
|
||||||
for id in TechDB.ids():
|
for id in TechDB.ids():
|
||||||
if not Game.knows(id):
|
if not Game.knows(id):
|
||||||
continue
|
continue
|
||||||
var t := TechDB.by_id(id)
|
var t := TechDB.by_id(id)
|
||||||
var ev := Game.evaluate(id, h["id"], aktiv, mensch.resist_left > 0.0)
|
var ev := Game.evaluate(id, h["id"], aktiv, mensch.resist_left > 0.0)
|
||||||
var marker: Array = []
|
var gesperrt := not Game.bereit(id)
|
||||||
if ev["tag"]:
|
var beschriftung := "%s · Stufe %d" % [t["name"], Game.skill_of(id)]
|
||||||
marker.append("[color=#88ff99]passt zum Thema[/color]")
|
if gesperrt:
|
||||||
if ev["moment"]:
|
beschriftung += " — noch %d s gesperrt" % int(ceil(Game.cooldown_rest(id)))
|
||||||
marker.append("[color=#88ff99]passender Moment[/color]")
|
elif ev["blocked"]:
|
||||||
if ev["blocked"]:
|
beschriftung += " — " + str(ev["grund"])
|
||||||
marker.append("[color=#ff8877]%s[/color]" % ev["grund"])
|
|
||||||
elif mensch.resist_left > 0.0:
|
elif mensch.resist_left > 0.0:
|
||||||
marker.append("[color=#ff8877]macht gerade dicht[/color]")
|
beschriftung += " — macht gerade dicht"
|
||||||
if marker.is_empty():
|
_knopf(beschriftung, func() -> void: _anwenden(id), not ev["blocked"] and not gesperrt)
|
||||||
marker.append("[color=#aaaaaa]weder Thema noch Moment passen[/color]")
|
|
||||||
_text("[b]%s[/b] · Stufe %d\n[color=#cccccc]%s[/color]\n%s" %
|
|
||||||
[t["name"], Game.skill_of(id), t["wann"], " · ".join(marker)], 16)
|
|
||||||
var beschriftung := "Blockiert" if ev["blocked"] else "Anwenden — %d %% Erfolgschance" % int(round(float(ev["chance"]) * 100.0))
|
|
||||||
_knopf(beschriftung, func() -> void: _anwenden(id), not ev["blocked"])
|
|
||||||
_trenner()
|
|
||||||
_zeigen("anwenden")
|
_zeigen("anwenden")
|
||||||
|
|
||||||
|
|
||||||
@@ -583,9 +582,10 @@ func _anwenden(id: String) -> void:
|
|||||||
Game.notified.emit("Immer dieselbe Technik wirkt schwächer. Probier eine andere.",
|
Game.notified.emit("Immer dieselbe Technik wirkt schwächer. Probier eine andere.",
|
||||||
Color(0.85, 0.85, 0.6))
|
Color(0.85, 0.85, 0.6))
|
||||||
else:
|
else:
|
||||||
mensch.reagiere(false, "%s prallt ab" % t["name"])
|
mensch.reagiere(false, "%s prallt ab\n−%d %%" % [t["name"],
|
||||||
Game.notified.emit("%s dringt gerade nicht durch (%d %% Chance)." %
|
int(round(float(res["verlust"])))])
|
||||||
[t["name"], int(round(float(res["chance"]) * 100.0))], Color(1, 0.6, 0.5))
|
Game.notified.emit("%s dringt nicht durch. (−%d %%)" %
|
||||||
|
[t["name"], int(round(float(res["verlust"])))], Color(1, 0.6, 0.5))
|
||||||
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
|||||||
@@ -193,7 +193,6 @@ func _ready() -> void:
|
|||||||
if not Game.completed.has("mensch_0"):
|
if not Game.completed.has("mensch_0"):
|
||||||
Game.completed.append("mensch_0")
|
Game.completed.append("mensch_0")
|
||||||
Game.active_mission = ""
|
Game.active_mission = ""
|
||||||
Game.einsicht = 3
|
|
||||||
Game.welt_erzeugen()
|
Game.welt_erzeugen()
|
||||||
main._traumwelt()
|
main._traumwelt()
|
||||||
await warte(20)
|
await warte(20)
|
||||||
|
|||||||
770
tests/verify.gd
770
tests/verify.gd
@@ -25,14 +25,18 @@ func _ready() -> void:
|
|||||||
await test_start()
|
await test_start()
|
||||||
await test_journal()
|
await test_journal()
|
||||||
await test_traumdorf()
|
await test_traumdorf()
|
||||||
|
await test_dorf_organisch()
|
||||||
await test_haus_betreten()
|
await test_haus_betreten()
|
||||||
await test_hoersaal()
|
await test_hoersaal()
|
||||||
await test_mission_annehmen()
|
await test_mission_annehmen()
|
||||||
await test_reise_zur_erde()
|
await test_reise_zur_erde()
|
||||||
|
await test_bezirk()
|
||||||
await test_tagesablauf()
|
await test_tagesablauf()
|
||||||
await test_wege_machbar()
|
await test_wege_machbar()
|
||||||
await test_geist()
|
await test_geist()
|
||||||
await test_technik_anwenden()
|
await test_technik_anwenden()
|
||||||
|
await test_cooldown()
|
||||||
|
await test_chance_verdeckt()
|
||||||
await test_pipeline()
|
await test_pipeline()
|
||||||
await test_mission_abschliessen()
|
await test_mission_abschliessen()
|
||||||
await test_welt_waechst()
|
await test_welt_waechst()
|
||||||
@@ -40,6 +44,9 @@ func _ready() -> void:
|
|||||||
await test_journal_ueberall()
|
await test_journal_ueberall()
|
||||||
await test_steuerung()
|
await test_steuerung()
|
||||||
await test_erster_traum_schaffbar()
|
await test_erster_traum_schaffbar()
|
||||||
|
await test_brett_nie_leer()
|
||||||
|
await test_lesen_lohnt_sich()
|
||||||
|
await test_tempo()
|
||||||
|
|
||||||
print("\n=========================================")
|
print("\n=========================================")
|
||||||
print("Fehlende Prüfungen: %d" % fehler)
|
print("Fehlende Prüfungen: %d" % fehler)
|
||||||
@@ -79,6 +86,16 @@ func _journal_text() -> String:
|
|||||||
return _panel_text()
|
return _panel_text()
|
||||||
|
|
||||||
|
|
||||||
|
func _lehre_text() -> String:
|
||||||
|
return _panel_text()
|
||||||
|
|
||||||
|
|
||||||
|
## Hebt alle Technik-Sperren auf. Nur für Messschleifen, die viele Anwendungen
|
||||||
|
## in Folge brauchen — der Cooldown selbst wird eigens geprüft.
|
||||||
|
func _frei() -> void:
|
||||||
|
Game.cooldown.clear()
|
||||||
|
|
||||||
|
|
||||||
## Welche Gebäude stehen wirklich in der Traumwelt? Erkennbar an ihren Schildern.
|
## Welche Gebäude stehen wirklich in der Traumwelt? Erkennbar an ihren Schildern.
|
||||||
## Was der Hörsaal von Anfang an lehrt.
|
## Was der Hörsaal von Anfang an lehrt.
|
||||||
func _basiswege() -> Array:
|
func _basiswege() -> Array:
|
||||||
@@ -163,8 +180,13 @@ func test_start() -> void:
|
|||||||
arten[n.get_meta("kind")] = true
|
arten[n.get_meta("kind")] = true
|
||||||
p(arten.has("erde") and arten.has("lehre") and arten.has("brett"),
|
p(arten.has("erde") and arten.has("lehre") and arten.has("brett"),
|
||||||
"Traumwelt bietet Tor, Hörsaal und Schwarzes Brett (%s)" % ", ".join(arten.keys()))
|
"Traumwelt bietet Tor, Hörsaal und Schwarzes Brett (%s)" % ", ".join(arten.keys()))
|
||||||
p(Game.einsicht == 3, "Startkapital 3 Einsicht")
|
p(Game.known.size() == 1, "Ein Weg ist von Anfang an gelernt (%d)" % Game.known.size())
|
||||||
p(Game.known.is_empty(), "Noch keine Technik gelernt")
|
p(Game.knows(str(_basiswege()[0])),
|
||||||
|
"Und zwar der des Hörsaals: %s" % TechDB.by_id(str(_basiswege()[0]))["name"])
|
||||||
|
p(Game.skill_of(str(_basiswege()[0])) == 1, "Auf Stufe 1, wie frisch gelernt")
|
||||||
|
p(not Game.offene_missionen().is_empty(),
|
||||||
|
"Damit hängt vom ersten Moment an eine Mission aus (%d)" %
|
||||||
|
Game.offene_missionen().size())
|
||||||
p((welt() as DreamWorld).bewohner.is_empty(),
|
p((welt() as DreamWorld).bewohner.is_empty(),
|
||||||
"Das Traumdorf steht am Anfang leer (%d Wesen)" %
|
"Das Traumdorf steht am Anfang leer (%d Wesen)" %
|
||||||
(welt() as DreamWorld).bewohner.size())
|
(welt() as DreamWorld).bewohner.size())
|
||||||
@@ -182,17 +204,49 @@ func test_start() -> void:
|
|||||||
func test_journal() -> void:
|
func test_journal() -> void:
|
||||||
abschnitt("Journal")
|
abschnitt("Journal")
|
||||||
for t in TechDB.LIST:
|
for t in TechDB.LIST:
|
||||||
var vollstaendig: bool = t["was"] != "" and t["wann"] != "" and t["warum"] != ""
|
var vollstaendig: bool = t["was"] != "" and t["wann"] != "" and t["warum"] != "" \
|
||||||
|
and t["woran"] != ""
|
||||||
if not vollstaendig:
|
if not vollstaendig:
|
||||||
p(false, "Technik %s hat alle drei Hinweise" % t["id"])
|
p(false, "Technik %s hat alle vier Hinweise" % t["id"])
|
||||||
p(true, "Alle %d Techniken tragen Was/Wann/Warum" % TechDB.LIST.size())
|
p(true, "Alle %d Techniken tragen Was/Wann/Warum/Woran" % TechDB.LIST.size())
|
||||||
|
|
||||||
|
# Der Woran-Hinweis muss jeden Moment der Technik wirklich benennen —
|
||||||
|
# sonst ist die Technik im Spiel nicht lesbar.
|
||||||
|
var stamm := {
|
||||||
|
"schlaeft": "schläf", "morgenroutine": "morgenroutine", "arbeitet": "arbeit",
|
||||||
|
"pause": "pause", "essen": "essen", "gruebelt": "grübel", "ruht": "ausruh",
|
||||||
|
"angespannt": "angespannt", "abendroutine": "abendroutine",
|
||||||
|
"aufraeumen": "aufräum", "wartet": "wartet", "muede": "müde",
|
||||||
|
"unterwegs": "unterwegs", "zoegert": "zöger",
|
||||||
|
}
|
||||||
|
var luecken: Array = []
|
||||||
|
for t in TechDB.LIST:
|
||||||
|
var text: String = str(t["woran"]).to_lower()
|
||||||
|
for m in t["momente"]:
|
||||||
|
if not stamm.has(m):
|
||||||
|
luecken.append("%s: %s unbekannt" % [t["id"], m])
|
||||||
|
elif not text.contains(str(stamm[m])):
|
||||||
|
luecken.append("%s nennt »%s« nicht" % [t["id"], m])
|
||||||
|
p(luecken.is_empty(), "Jeder Woran-Hinweis benennt alle Momente seiner Technik%s" %
|
||||||
|
("" if luecken.is_empty() else " — " + ", ".join(luecken)))
|
||||||
|
|
||||||
|
# Aber er verrät nicht mehr als die Momente
|
||||||
|
var zuviel: Array = []
|
||||||
|
for t in TechDB.LIST:
|
||||||
|
var text: String = str(t["woran"]).to_lower()
|
||||||
|
for m in stamm:
|
||||||
|
if (t["momente"] as Array).has(m):
|
||||||
|
continue
|
||||||
|
if text.contains(str(stamm[m])):
|
||||||
|
zuviel.append("%s nennt fremdes »%s«" % [t["id"], m])
|
||||||
|
p(zuviel.is_empty(), "Und keinen Moment, der nicht dazugehört%s" %
|
||||||
|
("" if zuviel.is_empty() else " — " + ", ".join(zuviel)))
|
||||||
|
|
||||||
ui().journal_panel("techniken")
|
ui().journal_panel("techniken")
|
||||||
p(ui().offen and ui()._kind == "journal", "Journal öffnet sich")
|
p(ui().offen and ui()._kind == "journal", "Journal öffnet sich")
|
||||||
p(_journal_text().contains("Hörsaal"),
|
p(_journal_text().contains(str(TechDB.by_id(str(_basiswege()[0]))["name"])),
|
||||||
"Ohne gelernte Technik verweist das Journal auf den Hörsaal")
|
"Das Journal führt den mitgebrachten Weg")
|
||||||
p(not _knopf_da("Lernen") and not _knopf_da("Vertiefen"),
|
p(not _knopf_da("Diesen Weg"), "Das Journal lehrt nicht selbst")
|
||||||
"Das Journal lehrt und vertieft nicht selbst")
|
|
||||||
ui().journal_panel("missionen")
|
ui().journal_panel("missionen")
|
||||||
p(_journal_text().contains("Schwarzen Brett"),
|
p(_journal_text().contains("Schwarzen Brett"),
|
||||||
"Ohne Mission verweist das Journal auf das Schwarze Brett")
|
"Ohne Mission verweist das Journal auf das Schwarze Brett")
|
||||||
@@ -202,10 +256,10 @@ func test_journal() -> void:
|
|||||||
var erster := str(_basiswege()[0])
|
var erster := str(_basiswege()[0])
|
||||||
p(Game.offene_techniken() == 1,
|
p(Game.offene_techniken() == 1,
|
||||||
"Am Anfang ist genau ein Weg entdeckt (%d)" % Game.offene_techniken())
|
"Am Anfang ist genau ein Weg entdeckt (%d)" % Game.offene_techniken())
|
||||||
p(Game.learn(erster), "Der erste Weg ist gelernt: %s" % TechDB.by_id(erster)["name"])
|
p(Game.knows(erster), "Er ist schon gelernt: %s" % TechDB.by_id(erster)["name"])
|
||||||
p(Game.einsicht == 2, "Das kostet 1 von 3 Einsicht (%d)" % Game.einsicht)
|
p(not Game.learn(erster), "Zweimal lernen geht nicht")
|
||||||
p(not Game.learn(str(TechDB.LIST[1]["id"])),
|
p(not Game.learn(str(TechDB.LIST[1]["id"])),
|
||||||
"Der zweite Weg ist trotz Einsicht nirgends zu lernen")
|
"Der zweite Weg ist nirgends zu lernen — er ist noch nicht entdeckt")
|
||||||
p(Game.skill_of(erster) == 1, "Frisch gelernte Technik ist Stufe 1")
|
p(Game.skill_of(erster) == 1, "Frisch gelernte Technik ist Stufe 1")
|
||||||
Game.add_xp(erster, 4)
|
Game.add_xp(erster, 4)
|
||||||
p(Game.skill_of(erster) == 2, "4 EP heben auf Stufe 2")
|
p(Game.skill_of(erster) == 2, "4 EP heben auf Stufe 2")
|
||||||
@@ -369,37 +423,37 @@ func test_hoersaal() -> void:
|
|||||||
p(basis_da, "Die %d Basiswege werden gelehrt" % 1)
|
p(basis_da, "Die %d Basiswege werden gelehrt" % 1)
|
||||||
var lernbar := 0
|
var lernbar := 0
|
||||||
for c in ui()._content.get_children():
|
for c in ui()._content.get_children():
|
||||||
if c is Button and (c as Button).text.begins_with("Lernen"):
|
if c is Button and (c as Button).text.begins_with("Diesen Weg"):
|
||||||
lernbar += 1
|
lernbar += 1
|
||||||
p(lernbar <= 1, "Und höchstens einer davon ist zu lernen (%d)" % lernbar)
|
p(lernbar <= 1, "Und höchstens einer davon ist zu lernen (%d)" % lernbar)
|
||||||
p(seite.contains(str(TechDB.LIST[1]["name"])),
|
p(seite.contains(str(TechDB.LIST[1]["name"])),
|
||||||
"Der Hörsaal nennt den nächsten noch unentdeckten Weg")
|
"Der Hörsaal nennt den nächsten noch unentdeckten Weg")
|
||||||
p(not Game.learn("box_atmung"), "Eine unentdeckte Technik lässt sich nicht lernen")
|
p(not Game.learn("box_atmung"), "Eine unentdeckte Technik lässt sich nicht lernen")
|
||||||
|
|
||||||
Game.einsicht = 1
|
|
||||||
Game.known.clear()
|
Game.known.clear()
|
||||||
ui().lehre_panel()
|
ui().lehre_panel()
|
||||||
var vorher := Game.known.size()
|
var vorher := Game.known.size()
|
||||||
p(_druecke("Lernen"), "Ein Lernen-Knopf steht bereit")
|
p(_druecke("Diesen Weg"), "Ein Lern-Knopf steht bereit")
|
||||||
p(Game.known.size() == vorher + 1, "Im Hörsaal gelernt (%d -> %d)" % [vorher, Game.known.size()])
|
p(Game.known.size() == vorher + 1, "Im Hörsaal gelernt (%d -> %d)" % [vorher, Game.known.size()])
|
||||||
p(Game.einsicht == 0, "Lernen kostet 1 Einsicht")
|
|
||||||
|
|
||||||
Game.einsicht = 1
|
# Gelernt heißt fertig: der Lehrort bietet danach nichts mehr an
|
||||||
ui().lehre_panel()
|
ui().lehre_panel()
|
||||||
var xp_summe := 0
|
var xp_summe := 0
|
||||||
for id in Game.known:
|
for id in Game.known:
|
||||||
xp_summe += int(Game.known[id])
|
xp_summe += int(Game.known[id])
|
||||||
p(_druecke("Vertiefen"), "Ein Vertiefen-Knopf steht bereit")
|
p(not _knopf_da("Vertiefen") and not _knopf_da("Üben") and not _knopf_da("Trainieren"),
|
||||||
|
"Kein Knopf trainiert eine gelernte Technik")
|
||||||
|
p(not _druecke("Diesen Weg"), "Auch der Lern-Knopf ist verschwunden")
|
||||||
var xp_neu := 0
|
var xp_neu := 0
|
||||||
for id in Game.known:
|
for id in Game.known:
|
||||||
xp_neu += int(Game.known[id])
|
xp_neu += int(Game.known[id])
|
||||||
p(xp_neu == xp_summe + 3, "Vertiefen bringt 3 EP (%d -> %d)" % [xp_summe, xp_neu])
|
p(xp_neu == xp_summe, "Der Besuch im Hörsaal bringt keine Erfahrung (%d)" % xp_neu)
|
||||||
p(Game.einsicht == 0, "Vertiefen kostet 1 Einsicht")
|
p(_lehre_text().contains("anwendest"),
|
||||||
|
"Stattdessen verweist der Hörsaal aufs Anwenden")
|
||||||
ui().schliessen()
|
ui().schliessen()
|
||||||
|
|
||||||
# Ausgangslage für die folgenden Prüfungen wiederherstellen
|
# Ausgangslage für die folgenden Prüfungen wiederherstellen
|
||||||
Game.known = {"pomodoro": 0, "zerlegen": 0, "umgebung": 0}
|
Game.known = {"pomodoro": 0, "zerlegen": 0, "umgebung": 0}
|
||||||
Game.einsicht = 0
|
|
||||||
|
|
||||||
|
|
||||||
func test_mission_annehmen() -> void:
|
func test_mission_annehmen() -> void:
|
||||||
@@ -419,11 +473,39 @@ func test_mission_annehmen() -> void:
|
|||||||
ui().schliessen()
|
ui().schliessen()
|
||||||
main._interagieren()
|
main._interagieren()
|
||||||
p(ui().offen and ui()._kind == "brett", "Das Brett öffnet sich")
|
p(ui().offen and ui()._kind == "brett", "Das Brett öffnet sich")
|
||||||
var alle_aus := true
|
# Nur wer mit dem Gelernten zu schaffen ist, hängt aus.
|
||||||
|
var alle_wege: Dictionary = Game.known.duplicate()
|
||||||
|
Game.known = {str(_basiswege()[0]): 0}
|
||||||
|
ui().brett_panel()
|
||||||
|
var aushang := _panel_text()
|
||||||
|
var gezeigt := 0
|
||||||
|
var fehlend := 0
|
||||||
for h in HumanDB.LIST:
|
for h in HumanDB.LIST:
|
||||||
if not _panel_text().contains(str(h["name"])):
|
var id: String = h["id"]
|
||||||
alle_aus = false
|
if aushang.contains(str(h["name"])):
|
||||||
p(alle_aus, "Alle %d Menschen hängen aus" % HumanDB.LIST.size())
|
gezeigt += 1
|
||||||
|
if not Game.machbar(id):
|
||||||
|
p(false, "%s hängt aus, obwohl kein Weg greift" % h["name"])
|
||||||
|
elif Game.machbar(id):
|
||||||
|
p(false, "%s wäre lösbar, hängt aber nicht aus" % h["name"])
|
||||||
|
else:
|
||||||
|
fehlend += 1
|
||||||
|
p(gezeigt > 0, "Mit einem Weg hängt mindestens ein Aushang (%d von %d)" %
|
||||||
|
[gezeigt, HumanDB.LIST.size()])
|
||||||
|
p(fehlend > 0, "Und mindestens einer bleibt verdeckt (%d)" % fehlend)
|
||||||
|
p(aushang.contains("verdeckt"), "Das Brett sagt, dass etwas verdeckt bleibt")
|
||||||
|
p(Game.offene_missionen().size() == gezeigt,
|
||||||
|
"Brett und offene_missionen() sind sich einig (%d)" % gezeigt)
|
||||||
|
|
||||||
|
# Ohne jede Technik bleibt das Brett leer, mit Hinweis auf den Hörsaal
|
||||||
|
Game.known.clear()
|
||||||
|
ui().brett_panel()
|
||||||
|
p(Game.offene_missionen().is_empty(), "Ohne Technik ist nichts zu holen")
|
||||||
|
p(_panel_text().contains("Hörsaal"), "Und das Brett schickt in den Hörsaal")
|
||||||
|
Game.known = alle_wege
|
||||||
|
ui().brett_panel()
|
||||||
|
p(Game.offene_missionen().size() >= gezeigt,
|
||||||
|
"Mehr Wege heißt mehr Aushänge (%d)" % Game.offene_missionen().size())
|
||||||
|
|
||||||
p(Game.active_mission == "", "Vorher keine Mission")
|
p(Game.active_mission == "", "Vorher keine Mission")
|
||||||
var gedrueckt := _druecke("Mission annehmen")
|
var gedrueckt := _druecke("Mission annehmen")
|
||||||
@@ -547,11 +629,12 @@ func test_tagesablauf() -> void:
|
|||||||
|
|
||||||
func test_wege_machbar() -> void:
|
func test_wege_machbar() -> void:
|
||||||
abschnitt("Wege passen in die Zeitfenster")
|
abschnitt("Wege passen in die Zeitfenster")
|
||||||
_wege_pruefen("Erde", HumanDB.PLACES, HumanDB.LIST)
|
_wege_pruefen("Erde", HumanDB.PLACES, HumanDB.LIST, HumanDB.NETZ, HumanDB.ANKER)
|
||||||
_wege_pruefen("Traumwelt", DreamDB.PLACES, DreamDB.LIST)
|
_wege_pruefen("Traumwelt", DreamDB.PLACES, DreamDB.LIST, DreamDB.NETZ, DreamDB.ANKER)
|
||||||
|
|
||||||
|
|
||||||
func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array) -> void:
|
func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array,
|
||||||
|
netz: Netz = null, anker: Dictionary = {}) -> void:
|
||||||
var sek_pro_stunde := Game.DAY_SECONDS / 24.0
|
var sek_pro_stunde := Game.DAY_SECONDS / 24.0
|
||||||
var alles_ok := true
|
var alles_ok := true
|
||||||
var engste := 999.0
|
var engste := 999.0
|
||||||
@@ -563,7 +646,7 @@ func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array) -> void:
|
|||||||
var nach: String = String(r[i][1])
|
var nach: String = String(r[i][1])
|
||||||
if von == nach:
|
if von == nach:
|
||||||
continue
|
continue
|
||||||
var dauer := _weg_laenge(places, von, nach) / Bewohner.GEHTEMPO
|
var dauer := _weg_laenge(places, von, nach, netz, anker) / Bewohner.GEHTEMPO
|
||||||
var slot_h: float = (float(r[i + 1][0]) - float(r[i][0])) if i + 1 < r.size() \
|
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]))
|
else (24.0 - float(r[i][0]))
|
||||||
var fenster := slot_h * sek_pro_stunde
|
var fenster := slot_h * sek_pro_stunde
|
||||||
@@ -578,8 +661,9 @@ func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array) -> void:
|
|||||||
p(alles_ok, "%s — jeder Weg passt, engster Fall: %s" % [welt_name, engste_text])
|
p(alles_ok, "%s — jeder Weg passt, engster Fall: %s" % [welt_name, engste_text])
|
||||||
|
|
||||||
|
|
||||||
func _weg_laenge(places: Dictionary, von: String, nach: String) -> float:
|
func _weg_laenge(places: Dictionary, von: String, nach: String,
|
||||||
var pts: Array = Orte.route(places, von, nach, Vector3.ZERO)
|
netz: Netz = null, anker: Dictionary = {}) -> float:
|
||||||
|
var pts: Array = Orte.route(places, von, nach, Vector3.ZERO, netz, anker)
|
||||||
var l := 0.0
|
var l := 0.0
|
||||||
var pos := Orte.center(places, von)
|
var pos := Orte.center(places, von)
|
||||||
for pt in pts:
|
for pt in pts:
|
||||||
@@ -657,20 +741,26 @@ func test_technik_anwenden() -> void:
|
|||||||
p(Game.evaluate("nein_sagen", "mensch_0", "zoegert", false)["blocked"],
|
p(Game.evaluate("nein_sagen", "mensch_0", "zoegert", false)["blocked"],
|
||||||
"Ungelernte Techniken lassen sich nicht anwenden")
|
"Ungelernte Techniken lassen sich nicht anwenden")
|
||||||
|
|
||||||
|
_frei()
|
||||||
|
Game.progress["mensch_0"] = 50.0
|
||||||
var vorher_p: float = Game.mission_progress("mensch_0")
|
var vorher_p: float = Game.mission_progress("mensch_0")
|
||||||
var res := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false)
|
var res := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false)
|
||||||
p(res["ok"], "Anwendung wird ausgeführt")
|
p(res["ok"], "Anwendung wird ausgeführt")
|
||||||
if res["success"]:
|
if res["success"]:
|
||||||
p(Game.mission_progress("mensch_0") > vorher_p, "Erfolg bringt Missionsfortschritt")
|
p(Game.mission_progress("mensch_0") > vorher_p, "Erfolg bringt Missionsfortschritt")
|
||||||
else:
|
else:
|
||||||
p(Game.mission_progress("mensch_0") == vorher_p, "Misserfolg bringt keinen Fortschritt")
|
p(Game.mission_progress("mensch_0") < vorher_p,
|
||||||
|
"Fehlversuch kostet Fortschritt (%.0f -> %.0f)" %
|
||||||
|
[vorher_p, Game.mission_progress("mensch_0")])
|
||||||
p(Game.known["pomodoro"] > 0, "Jede Anwendung bringt Erfahrung")
|
p(Game.known["pomodoro"] > 0, "Jede Anwendung bringt Erfahrung")
|
||||||
|
|
||||||
|
_frei()
|
||||||
Game.progress["mensch_0"] = 0.0
|
Game.progress["mensch_0"] = 0.0
|
||||||
Game.used.clear()
|
Game.used.clear()
|
||||||
Game.known["pomodoro"] = 28
|
Game.known["pomodoro"] = 28
|
||||||
var ertraege: Array = []
|
var ertraege: Array = []
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
|
_frei()
|
||||||
var r := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false)
|
var r := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false)
|
||||||
if r["success"]:
|
if r["success"]:
|
||||||
ertraege.append(r["gain"])
|
ertraege.append(r["gain"])
|
||||||
@@ -713,8 +803,8 @@ func test_pipeline() -> void:
|
|||||||
p(not schlaf_aktiv, "Und lässt sich gar nicht erst anklicken")
|
p(not schlaf_aktiv, "Und lässt sich gar nicht erst anklicken")
|
||||||
ui().regel_panel("mensch_0", "zoegert")
|
ui().regel_panel("mensch_0", "zoegert")
|
||||||
p(ui()._kind == "regel", "Die Technikwahl für »zögert« öffnet sich")
|
p(ui()._kind == "regel", "Die Technikwahl für »zögert« öffnet sich")
|
||||||
p(_panel_text().contains("Pomodoro-Technik"), "Gelernte Techniken stehen zur Wahl")
|
p(_knopf_texte().contains("Pomodoro-Technik"), "Gelernte Techniken stehen zur Wahl")
|
||||||
p(_druecke("Diese Regel"), "Eine Technik lässt sich zuordnen")
|
p(_druecke("Pomodoro-Technik"), "Eine Technik lässt sich zuordnen")
|
||||||
p(Game.regel_fuer("mensch_0", "zoegert") != "",
|
p(Game.regel_fuer("mensch_0", "zoegert") != "",
|
||||||
"Regel steht: zögert -> %s" % Game.regel_fuer("mensch_0", "zoegert"))
|
"Regel steht: zögert -> %s" % Game.regel_fuer("mensch_0", "zoegert"))
|
||||||
p(ui()._kind == "pipeline", "Danach ist wieder das Regelwerk offen")
|
p(ui()._kind == "pipeline", "Danach ist wieder das Regelwerk offen")
|
||||||
@@ -724,15 +814,20 @@ func test_pipeline() -> void:
|
|||||||
Game.regel_setzen("mensch_0", "aufraeumen", "umgebung")
|
Game.regel_setzen("mensch_0", "aufraeumen", "umgebung")
|
||||||
p(Game.regel_anzahl("mensch_0") == 3, "Drei Regeln im Werk (%d)" % Game.regel_anzahl("mensch_0"))
|
p(Game.regel_anzahl("mensch_0") == 3, "Drei Regeln im Werk (%d)" % Game.regel_anzahl("mensch_0"))
|
||||||
|
|
||||||
# Eine Technik hält nur einen Posten
|
# Eine Technik darf an mehreren Tätigkeiten hängen
|
||||||
Game.regel_setzen("mensch_0", "arbeitet", "zerlegen")
|
Game.regel_setzen("mensch_0", "arbeitet", "zerlegen")
|
||||||
p(Game.regel_anzahl("mensch_0") == 3,
|
p(Game.regel_anzahl("mensch_0") == 4,
|
||||||
"Dieselbe Technik anderswo einzusetzen fügt keine Regel hinzu (%d)" %
|
"Dieselbe Technik anderswo einzusetzen kommt hinzu (%d)" %
|
||||||
Game.regel_anzahl("mensch_0"))
|
Game.regel_anzahl("mensch_0"))
|
||||||
p(Game.regel_fuer("mensch_0", "gruebelt") == "" and Game.regel_fuer("mensch_0", "arbeitet") == "zerlegen",
|
p(Game.regel_fuer("mensch_0", "gruebelt") == "zerlegen"
|
||||||
"Sie ist umgezogen statt sich zu verdoppeln")
|
and Game.regel_fuer("mensch_0", "arbeitet") == "zerlegen",
|
||||||
|
"Sie hängt jetzt an beiden Tätigkeiten")
|
||||||
|
p(Game.regel_momente("mensch_0", "zerlegen").size() == 2,
|
||||||
|
"Und das Spiel weiß von beiden Posten (%d)" %
|
||||||
|
Game.regel_momente("mensch_0", "zerlegen").size())
|
||||||
Game.regel_setzen("mensch_0", "arbeitet", "")
|
Game.regel_setzen("mensch_0", "arbeitet", "")
|
||||||
Game.regel_setzen("mensch_0", "gruebelt", "zerlegen")
|
p(Game.regel_momente("mensch_0", "zerlegen").size() == 1,
|
||||||
|
"Einzeln löschen lässt den anderen Posten stehen")
|
||||||
|
|
||||||
# Ohne Zutun einen Tag laufen lassen
|
# Ohne Zutun einen Tag laufen lassen
|
||||||
var lena: Bewohner = null
|
var lena: Bewohner = null
|
||||||
@@ -787,18 +882,19 @@ func test_mission_abschliessen() -> void:
|
|||||||
abschnitt("Mission abschließen")
|
abschnitt("Mission abschließen")
|
||||||
Game.progress["mensch_0"] = 0.0
|
Game.progress["mensch_0"] = 0.0
|
||||||
Game.used.clear()
|
Game.used.clear()
|
||||||
var einsicht_vorher := Game.einsicht
|
|
||||||
var runden := 0
|
var runden := 0
|
||||||
while Game.mission_progress("mensch_0") < Game.MISSION_ZIEL and runden < 400:
|
while Game.mission_progress("mensch_0") < Game.MISSION_ZIEL and runden < 400:
|
||||||
|
_frei()
|
||||||
Game.apply_technique("pomodoro", "mensch_0", "zoegert", false)
|
Game.apply_technique("pomodoro", "mensch_0", "zoegert", false)
|
||||||
|
_frei()
|
||||||
Game.apply_technique("zerlegen", "mensch_0", "gruebelt", false)
|
Game.apply_technique("zerlegen", "mensch_0", "gruebelt", false)
|
||||||
|
_frei()
|
||||||
Game.apply_technique("umgebung", "mensch_0", "aufraeumen", false)
|
Game.apply_technique("umgebung", "mensch_0", "aufraeumen", false)
|
||||||
runden += 1
|
runden += 1
|
||||||
p(Game.mission_progress("mensch_0") >= Game.MISSION_ZIEL,
|
p(Game.mission_progress("mensch_0") >= Game.MISSION_ZIEL,
|
||||||
"Lenas Traum erreicht 100 %% (nach %d Runden)" % runden)
|
"Lenas Traum erreicht 100 %% (nach %d Runden)" % runden)
|
||||||
p(Game.completed.has("mensch_0"), "Mission gilt als abgeschlossen")
|
p(Game.completed.has("mensch_0"), "Mission gilt als abgeschlossen")
|
||||||
p(Game.active_mission == "", "Aktive Mission wurde freigegeben")
|
p(Game.active_mission == "", "Aktive Mission wurde freigegeben")
|
||||||
p(Game.einsicht == einsicht_vorher + 3, "Abschluss bringt 3 Einsicht")
|
|
||||||
p(DreamDB.LIST.size() == Game.completed.size(),
|
p(DreamDB.LIST.size() == Game.completed.size(),
|
||||||
"Der Abschluss lässt das Dorf wachsen (%d Wesen)" % DreamDB.LIST.size())
|
"Der Abschluss lässt das Dorf wachsen (%d Wesen)" % DreamDB.LIST.size())
|
||||||
schritte(3)
|
schritte(3)
|
||||||
@@ -836,13 +932,12 @@ func test_welt_waechst() -> void:
|
|||||||
p(Game.insel_radius() >= Game.INSEL_GRUND,
|
p(Game.insel_radius() >= Game.INSEL_GRUND,
|
||||||
"Die Insel trägt das Dorf (%.0f)" % Game.insel_radius())
|
"Die Insel trägt das Dorf (%.0f)" % Game.insel_radius())
|
||||||
|
|
||||||
Game.einsicht = 3
|
|
||||||
var neu_id := str(TechDB.LIST[1]["id"])
|
var neu_id := str(TechDB.LIST[1]["id"])
|
||||||
ui().lehre_panel(DreamDB.haus_key(0))
|
ui().lehre_panel(DreamDB.haus_key(0))
|
||||||
p(ui()._kind == "lehre", "Das neue Haus lehrt")
|
p(ui()._kind == "lehre", "Das neue Haus lehrt")
|
||||||
p(_panel_text().contains(str(TechDB.by_id(neu_id)["name"])),
|
p(_panel_text().contains(str(TechDB.by_id(neu_id)["name"])),
|
||||||
"Und zwar den Weg, den sein Bewohner mitbrachte")
|
"Und zwar den Weg, den sein Bewohner mitbrachte")
|
||||||
p(_druecke("Lernen"), "Er lässt sich lernen")
|
p(_druecke("Diesen Weg"), "Er lässt sich lernen")
|
||||||
p(Game.knows(neu_id), "Gelernt: %s" % TechDB.by_id(neu_id)["name"])
|
p(Game.knows(neu_id), "Gelernt: %s" % TechDB.by_id(neu_id)["name"])
|
||||||
ui().schliessen()
|
ui().schliessen()
|
||||||
p(not Game.technik_offen(str(TechDB.LIST[2]["id"])),
|
p(not Game.technik_offen(str(TechDB.LIST[2]["id"])),
|
||||||
@@ -853,7 +948,6 @@ func test_welt_waechst() -> void:
|
|||||||
|
|
||||||
# Ausgangslage für die folgenden Prüfungen
|
# Ausgangslage für die folgenden Prüfungen
|
||||||
Game.known.erase(neu_id)
|
Game.known.erase(neu_id)
|
||||||
Game.einsicht = 2
|
|
||||||
Game.welt_erzeugen()
|
Game.welt_erzeugen()
|
||||||
main._erde()
|
main._erde()
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
@@ -910,35 +1004,49 @@ func _dorf_platz_pruefen() -> void:
|
|||||||
for i in range(stufen):
|
for i in range(stufen):
|
||||||
Game.completed.append("stufe_%d" % i)
|
Game.completed.append("stufe_%d" % i)
|
||||||
Game.welt_erzeugen()
|
Game.welt_erzeugen()
|
||||||
_wege_pruefen("Traumdorf mit %d Häusern" % stufen, DreamDB.PLACES, DreamDB.LIST)
|
_wege_pruefen("Traumdorf mit %d Häusern" % stufen, DreamDB.PLACES, DreamDB.LIST,
|
||||||
_wege_pruefen("Stadt mit %d Menschen" % HumanDB.LIST.size(), HumanDB.PLACES, HumanDB.LIST)
|
DreamDB.NETZ, DreamDB.ANKER)
|
||||||
|
_wege_pruefen("Stadt mit %d Menschen" % HumanDB.LIST.size(), HumanDB.PLACES,
|
||||||
|
HumanDB.LIST, HumanDB.NETZ, HumanDB.ANKER)
|
||||||
Game.completed.assign(merker)
|
Game.completed.assign(merker)
|
||||||
Game.welt_erzeugen()
|
Game.welt_erzeugen()
|
||||||
|
|
||||||
|
|
||||||
func test_techniken_verbessern_sich() -> void:
|
func test_techniken_verbessern_sich() -> void:
|
||||||
abschnitt("Techniken verbessern sich durch Anwendung")
|
abschnitt("Techniken verbessern sich nur durch Anwendung")
|
||||||
Game.einsicht = 2
|
|
||||||
p(Game.learn("zwei_minuten"), "Technik gelernt")
|
p(Game.learn("zwei_minuten"), "Technik gelernt")
|
||||||
p(Game.skill_of("zwei_minuten") == 1, "Start auf Stufe 1")
|
p(Game.skill_of("zwei_minuten") == 1, "Frisch gelernt heißt Stufe 1")
|
||||||
var einsicht_vorher := Game.einsicht
|
|
||||||
var anwendungen := 0
|
var anwendungen := 0
|
||||||
while Game.skill_of("zwei_minuten") < 3 and anwendungen < 60:
|
while Game.skill_of("zwei_minuten") < 3 and anwendungen < 60:
|
||||||
|
_frei()
|
||||||
Game.apply_technique("zwei_minuten", "mensch_2", "gruebelt", false)
|
Game.apply_technique("zwei_minuten", "mensch_2", "gruebelt", false)
|
||||||
anwendungen += 1
|
anwendungen += 1
|
||||||
p(Game.skill_of("zwei_minuten") >= 3,
|
p(Game.skill_of("zwei_minuten") >= 3,
|
||||||
"Nach %d Anwendungen von allein auf Stufe %d" % [anwendungen, Game.skill_of("zwei_minuten")])
|
"Nach %d Anwendungen von allein auf Stufe %d" % [anwendungen, Game.skill_of("zwei_minuten")])
|
||||||
p(Game.einsicht == einsicht_vorher, "Das kostet keine Einsicht")
|
|
||||||
|
|
||||||
var vor_xp: int = Game.known["zwei_minuten"]
|
var vor_xp: int = Game.known["zwei_minuten"]
|
||||||
|
_frei()
|
||||||
var res := Game.apply_technique("zwei_minuten", "mensch_2", "essen", false)
|
var res := Game.apply_technique("zwei_minuten", "mensch_2", "essen", false)
|
||||||
p(Game.known["zwei_minuten"] > vor_xp,
|
p(Game.known["zwei_minuten"] > vor_xp,
|
||||||
"Auch ein %s bringt Erfahrung" % ("Erfolg" if res["success"] else "Fehlversuch"))
|
"Auch ein %s bringt Erfahrung" % ("Erfolg" if res["success"] else "Fehlversuch"))
|
||||||
|
|
||||||
var xp_vor: int = Game.known["zwei_minuten"]
|
# Es gibt keinen zweiten Weg nach oben
|
||||||
p(Game.deepen("zwei_minuten"), "Vertiefen funktioniert")
|
p(not Game.has_method("deepen"), "Kein gezieltes Vertiefen mehr im Spiel")
|
||||||
p(Game.known["zwei_minuten"] == xp_vor + 3, "Vertiefen bringt 3 EP")
|
p(not ("einsicht" in Game), "Und keine Währung, die man dafür ausgeben könnte")
|
||||||
p(Game.einsicht == einsicht_vorher - 1, "Vertiefen kostet 1 Einsicht")
|
|
||||||
|
var stand: int = Game.known["zwei_minuten"]
|
||||||
|
Game.learn("zwei_minuten")
|
||||||
|
ui().lehre_panel(DreamDB.ort_von_technik("zwei_minuten"))
|
||||||
|
var geknoepft := _druecke("Diesen Weg") or _druecke("Vertiefen") or _druecke("Üben")
|
||||||
|
ui().schliessen()
|
||||||
|
p(not geknoepft and Game.known["zwei_minuten"] == stand,
|
||||||
|
"Ein zweiter Besuch am Lehrort ändert nichts (%d EP)" % stand)
|
||||||
|
|
||||||
|
# Zeitablauf allein bringt auch nichts
|
||||||
|
var vorher: int = Game.known["zwei_minuten"]
|
||||||
|
Game.advance_time(Game.DAY_SECONDS)
|
||||||
|
p(Game.known["zwei_minuten"] == vorher, "Und auch ein ganzer Tag bringt keine Erfahrung")
|
||||||
|
|
||||||
|
|
||||||
func test_journal_ueberall() -> void:
|
func test_journal_ueberall() -> void:
|
||||||
@@ -974,13 +1082,13 @@ func test_journal_ueberall() -> void:
|
|||||||
func test_steuerung() -> void:
|
func test_steuerung() -> void:
|
||||||
abschnitt("Steuerung")
|
abschnitt("Steuerung")
|
||||||
var aktionen := ["vor", "zurueck", "links", "rechts", "steigen", "sinken",
|
var aktionen := ["vor", "zurueck", "links", "rechts", "steigen", "sinken",
|
||||||
"interagieren", "journal"]
|
"interagieren", "journal", "tempo_1", "tempo_2", "tempo_3"]
|
||||||
var alle := true
|
var alle := true
|
||||||
for a in aktionen:
|
for a in aktionen:
|
||||||
if not InputMap.has_action(a) or InputMap.action_get_events(a).is_empty():
|
if not InputMap.has_action(a) or InputMap.action_get_events(a).is_empty():
|
||||||
alle = false
|
alle = false
|
||||||
p(false, "Aktion '%s' ist belegt" % a)
|
p(false, "Aktion '%s' ist belegt" % a)
|
||||||
p(alle, "Alle acht Steuerungsaktionen sind mit Tasten belegt")
|
p(alle, "Alle %d Steuerungsaktionen sind mit Tasten belegt" % aktionen.size())
|
||||||
|
|
||||||
ui().schliessen()
|
ui().schliessen()
|
||||||
main.player.look_enabled = true
|
main.player.look_enabled = true
|
||||||
@@ -1030,7 +1138,7 @@ func test_steuerung() -> void:
|
|||||||
## und zwar zuverlässig, nicht nur bei gutem Würfelglück.
|
## und zwar zuverlässig, nicht nur bei gutem Würfelglück.
|
||||||
func _erster_traum_tage(saat: int) -> int:
|
func _erster_traum_tage(saat: int) -> int:
|
||||||
seed(saat)
|
seed(saat)
|
||||||
Game.einsicht = 3
|
Game.cooldown.clear()
|
||||||
Game.known.clear()
|
Game.known.clear()
|
||||||
Game.used.clear()
|
Game.used.clear()
|
||||||
Game.pipeline.clear()
|
Game.pipeline.clear()
|
||||||
@@ -1088,6 +1196,548 @@ func test_erster_traum_schaffbar() -> void:
|
|||||||
p(bester >= 2, "Nie geschenkt — mindestens %d Tage" % bester)
|
p(bester >= 2, "Nie geschenkt — mindestens %d Tage" % bester)
|
||||||
p(schlimmster <= 8, "Nie zäh — höchstens %d Tage (von Hand geht es schneller)" %
|
p(schlimmster <= 8, "Nie zäh — höchstens %d Tage (von Hand geht es schneller)" %
|
||||||
schlimmster)
|
schlimmster)
|
||||||
p(Game.einsicht >= 3, "Der erfüllte Traum bringt 3 Einsicht (%d)" % Game.einsicht)
|
|
||||||
p(Game.offene_techniken() > 1,
|
p(Game.offene_techniken() > 1,
|
||||||
"Und öffnet neue Wege: %d entdeckt" % Game.offene_techniken())
|
"Und öffnet neue Wege: %d entdeckt" % Game.offene_techniken())
|
||||||
|
|
||||||
|
|
||||||
|
## Nach jedem Einsatz braucht eine Technik Ruhe. Ein Weg allein deckt daher
|
||||||
|
## keinen ganzen Tag ab — das ist der Grund, mehrere zu lernen.
|
||||||
|
func test_cooldown() -> void:
|
||||||
|
abschnitt("Techniken haben einen Cooldown")
|
||||||
|
_frei()
|
||||||
|
Game.known = {"pomodoro": 28, "zerlegen": 28}
|
||||||
|
Game.active_mission = "mensch_0"
|
||||||
|
Game.progress["mensch_0"] = 50.0
|
||||||
|
|
||||||
|
p(Game.bereit("pomodoro"), "Frisch ist eine Technik einsatzbereit")
|
||||||
|
var erste := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false)
|
||||||
|
p(erste["ok"], "Der erste Einsatz geht durch")
|
||||||
|
p(not Game.bereit("pomodoro"), "Danach ist sie gesperrt")
|
||||||
|
p(is_equal_approx(Game.cooldown_rest("pomodoro"), Game.TECHNIK_COOLDOWN),
|
||||||
|
"Und zwar %.0f Sekunden lang" % Game.TECHNIK_COOLDOWN)
|
||||||
|
|
||||||
|
var zweite := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false)
|
||||||
|
p(not zweite["ok"], "Ein zweiter Einsatz prallt an der Sperre ab")
|
||||||
|
p(str(zweite["grund"]).contains("einsatzbereit"), "Mit klarer Begründung")
|
||||||
|
|
||||||
|
p(Game.bereit("zerlegen"), "Die Sperre gilt nur für diese eine Technik")
|
||||||
|
var andere := Game.apply_technique("zerlegen", "mensch_0", "gruebelt", false)
|
||||||
|
p(andere["ok"], "Eine andere Technik greift weiter")
|
||||||
|
|
||||||
|
# Eine Regel bleibt still, solange die Technik gesperrt ist
|
||||||
|
Game.pipeline.clear()
|
||||||
|
Game.regel_setzen("mensch_0", "zoegert", "pomodoro")
|
||||||
|
var still := Game.regel_ausloesen("mensch_0", "zoegert", false)
|
||||||
|
p(still.is_empty(), "Auch eine Regel läuft nicht gegen die Sperre an")
|
||||||
|
|
||||||
|
Game.advance_time(Game.TECHNIK_COOLDOWN * 0.5)
|
||||||
|
p(not Game.bereit("pomodoro"), "Nach der halben Zeit noch immer gesperrt")
|
||||||
|
Game.advance_time(Game.TECHNIK_COOLDOWN * 0.6)
|
||||||
|
p(Game.bereit("pomodoro"), "Nach Ablauf wieder frei")
|
||||||
|
p(Game.cooldown_rest("pomodoro") == 0.0, "Und der Zähler steht auf null")
|
||||||
|
var wieder := Game.regel_ausloesen("mensch_0", "zoegert", false)
|
||||||
|
p(not wieder.is_empty(), "Jetzt greift die Regel wieder")
|
||||||
|
|
||||||
|
# Im Anwenden-Panel ist die Sperre sichtbar und der Knopf tot
|
||||||
|
var lena: Bewohner = null
|
||||||
|
for m in welt().humans:
|
||||||
|
if m.data["id"] == "mensch_0":
|
||||||
|
lena = m
|
||||||
|
ui().anwenden_panel(lena)
|
||||||
|
var gesperrt_sichtbar := _knopf_texte().contains("gesperrt")
|
||||||
|
var knopf_tot := true
|
||||||
|
for c in ui()._content.get_children():
|
||||||
|
if c is Button and (c as Button).text.contains("gesperrt") \
|
||||||
|
and not (c as Button).disabled:
|
||||||
|
knopf_tot = false
|
||||||
|
p(gesperrt_sichtbar, "Der Anwenden-Knopf weist die Sperre aus")
|
||||||
|
p(knopf_tot, "Und lässt sich nicht drücken")
|
||||||
|
ui().schliessen()
|
||||||
|
Game.pipeline.clear()
|
||||||
|
_frei()
|
||||||
|
|
||||||
|
|
||||||
|
## Wie gut eine Technik im Moment steht, sagt das Spiel nicht. Es nennt nur,
|
||||||
|
## was zum Thema und was zum Moment passt — entscheiden muss der Spieler.
|
||||||
|
func test_chance_verdeckt() -> void:
|
||||||
|
abschnitt("Die Erfolgschance bleibt verdeckt")
|
||||||
|
_frei()
|
||||||
|
Game.known = {"pomodoro": 28, "zerlegen": 28}
|
||||||
|
Game.active_mission = "mensch_0"
|
||||||
|
Game.pipeline.clear()
|
||||||
|
Game.regel_setzen("mensch_0", "zoegert", "pomodoro")
|
||||||
|
|
||||||
|
ui().regel_panel("mensch_0", "zoegert")
|
||||||
|
var knoepfe := _knopf_texte()
|
||||||
|
p(not knoepfe.contains("%"), "Kein Knopf der Technikwahl nennt eine Prozentzahl")
|
||||||
|
p(not _panel_text().contains("Erfolgschance"),
|
||||||
|
"Und das Wort Erfolgschance kommt nicht vor")
|
||||||
|
var wahl := _panel_text()
|
||||||
|
p(not wahl.contains("passt zum Thema") and not wahl.contains("passender Moment")
|
||||||
|
and not wahl.contains("weder Thema"),
|
||||||
|
"Auch kein Hinweis, ob Thema oder Moment passen")
|
||||||
|
p(not wahl.contains("Wann:") and not wahl.contains("Zu sehen an:"),
|
||||||
|
"Die Auswahl zeigt nur die Namen, keine Beschreibung")
|
||||||
|
p(_knopf_texte().contains("Pomodoro-Technik · Stufe"),
|
||||||
|
"Jede Wahl steht als Name mit Stufe da")
|
||||||
|
ui().schliessen()
|
||||||
|
|
||||||
|
ui().pipeline_panel("mensch_0")
|
||||||
|
var zeilen := _knopf_texte()
|
||||||
|
p(not zeilen.contains("%"), "Auch die Regelzeilen nennen keine Chance")
|
||||||
|
p(zeilen.contains("Pomodoro-Technik"), "Sie nennen nur die eingehängte Technik")
|
||||||
|
ui().schliessen()
|
||||||
|
|
||||||
|
var lena: Bewohner = null
|
||||||
|
for m in welt().humans:
|
||||||
|
if m.data["id"] == "mensch_0":
|
||||||
|
lena = m
|
||||||
|
ui().anwenden_panel(lena)
|
||||||
|
var anwenden := _panel_text()
|
||||||
|
p(not _knopf_texte().contains("%"), "Und der Anwenden-Knopf nennt keine Chance")
|
||||||
|
p(_knopf_texte().contains("Pomodoro-Technik · Stufe"),
|
||||||
|
"Er trägt nur Name und Stufe")
|
||||||
|
p(not anwenden.contains("passt zum Thema") and not anwenden.contains("passender Moment"),
|
||||||
|
"Auch beim Anwenden steht nicht da, ob es passt")
|
||||||
|
p(not anwenden.contains("Hilft bei") and not anwenden.contains("Momente:"),
|
||||||
|
"Und keine Liste der passenden Themen oder Momente")
|
||||||
|
p(not anwenden.contains("Wann:") and not anwenden.contains("Zu sehen an:"),
|
||||||
|
"Auch dort nur Namen — die Beschreibung steht im Journal")
|
||||||
|
p(anwenden.contains("Journal"), "Und das Panel sagt, wo sie steht")
|
||||||
|
ui().schliessen()
|
||||||
|
|
||||||
|
ui().journal_panel("techniken")
|
||||||
|
var buch := _panel_text()
|
||||||
|
p(not buch.contains("Hilft bei") and not buch.contains("Momente:"),
|
||||||
|
"Auch das Journal verrät die Passung nicht")
|
||||||
|
p(buch.contains("Wann:") and buch.contains("Zu sehen an:"),
|
||||||
|
"Das Journal dagegen führt Was, Wann, Warum und Zu sehen an")
|
||||||
|
ui().schliessen()
|
||||||
|
|
||||||
|
ui().brett_panel()
|
||||||
|
p(not _panel_text().contains("Themen:"),
|
||||||
|
"Und das Brett listet die Themen des Menschen nicht auf")
|
||||||
|
ui().schliessen()
|
||||||
|
Game.pipeline.clear()
|
||||||
|
|
||||||
|
|
||||||
|
## Wenn nur lösbare Missionen aushängen, darf das Brett nie ganz leer werden —
|
||||||
|
## sonst hinge Loop 2 fest: ohne Traum kein neuer Weg, ohne Weg kein Aushang.
|
||||||
|
func test_brett_nie_leer() -> void:
|
||||||
|
abschnitt("Das Brett läuft nie leer")
|
||||||
|
var merk_c: Array = Game.completed.duplicate()
|
||||||
|
var merk_k: Dictionary = Game.known.duplicate()
|
||||||
|
var leer_bei := -1
|
||||||
|
var kleinste := 99
|
||||||
|
var stufen := 14
|
||||||
|
for stufe in range(stufen):
|
||||||
|
# Ein Spieler, der alles lernt, was das Dorf hergibt
|
||||||
|
for t in TechDB.LIST:
|
||||||
|
if Game.technik_offen(str(t["id"])) and not Game.knows(str(t["id"])):
|
||||||
|
Game.known[str(t["id"])] = 0
|
||||||
|
var offen: Array = Game.offene_missionen()
|
||||||
|
kleinste = mini(kleinste, offen.size())
|
||||||
|
if offen.is_empty():
|
||||||
|
leer_bei = stufe
|
||||||
|
break
|
||||||
|
Game.completed.append(str(offen[0]))
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
p(leer_bei < 0, "Über %d Ausbaustufen hängt immer etwas aus%s" %
|
||||||
|
[stufen, "" if leer_bei < 0 else " — leer bei Stufe %d" % leer_bei])
|
||||||
|
p(kleinste >= 1, "Im knappsten Fall bleibt %d Aushang übrig" % kleinste)
|
||||||
|
|
||||||
|
# Und der jeweils neueste Mensch ist mit dem neuesten Weg zu schaffen
|
||||||
|
Game.completed.clear()
|
||||||
|
Game.known.clear()
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
var passt := 0
|
||||||
|
for i in range(TechDB.LIST.size()):
|
||||||
|
Game.completed.clear()
|
||||||
|
for k in range(i):
|
||||||
|
Game.completed.append("stufe_%d" % k)
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
Game.known = {str(TechDB.LIST[i]["id"]): 0}
|
||||||
|
if Game.machbar("mensch_%d" % i):
|
||||||
|
passt += 1
|
||||||
|
p(passt == TechDB.LIST.size(),
|
||||||
|
"Zu jedem der %d Wege gibt es den passenden Menschen (%d)" %
|
||||||
|
[TechDB.LIST.size(), passt])
|
||||||
|
|
||||||
|
Game.completed.assign(merk_c)
|
||||||
|
Game.known = merk_k
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
|
||||||
|
|
||||||
|
## Die Probe auf den Kern: wer die Technik liest und sie nur dort einhängt, wo
|
||||||
|
## ihr "Wann" trifft, kommt schneller ans Ziel als wer sie überall hinhängt.
|
||||||
|
## Ohne diesen Abstand wäre das Lesen umsonst.
|
||||||
|
func _traum_tage(saat: int, alles: bool) -> int:
|
||||||
|
seed(saat)
|
||||||
|
Game.cooldown.clear()
|
||||||
|
Game.known.clear()
|
||||||
|
Game.used.clear()
|
||||||
|
Game.pipeline.clear()
|
||||||
|
Game.progress.clear()
|
||||||
|
Game.completed.clear()
|
||||||
|
Game.active_mission = ""
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
var weg := str(_basiswege()[0])
|
||||||
|
Game.learn(weg)
|
||||||
|
Game.accept_mission("mensch_0")
|
||||||
|
var t := TechDB.by_id(weg)
|
||||||
|
for a in HumanDB.aktivitaeten("mensch_0"):
|
||||||
|
if not Game.erreichbar(a):
|
||||||
|
continue
|
||||||
|
if alles or (t["momente"] as Array).has(a):
|
||||||
|
Game.regel_setzen("mensch_0", a, weg)
|
||||||
|
main._erde()
|
||||||
|
await get_tree().process_frame
|
||||||
|
main.player.global_position = Vector3(0, 30.0, 60.0)
|
||||||
|
Game.time_h = 7.0
|
||||||
|
Game.day = 1
|
||||||
|
for i in range(200000):
|
||||||
|
schritte(1)
|
||||||
|
if Game.completed.has("mensch_0") or Game.day > 14:
|
||||||
|
break
|
||||||
|
return Game.day if Game.completed.has("mensch_0") else 99
|
||||||
|
|
||||||
|
|
||||||
|
func test_lesen_lohnt_sich() -> void:
|
||||||
|
abschnitt("Die Technik zu lesen zahlt sich aus")
|
||||||
|
var gezielt: Array = []
|
||||||
|
var blind: Array = []
|
||||||
|
for saat in [7, 555, 90210]:
|
||||||
|
gezielt.append(await _traum_tage(saat, false))
|
||||||
|
blind.append(await _traum_tage(saat, true))
|
||||||
|
var g := 0.0
|
||||||
|
var b := 0.0
|
||||||
|
for i in range(gezielt.size()):
|
||||||
|
g += float(gezielt[i])
|
||||||
|
b += float(blind[i])
|
||||||
|
g /= float(gezielt.size())
|
||||||
|
b /= float(blind.size())
|
||||||
|
p(b >= g * 1.25,
|
||||||
|
"Gezielt gesetzt geht es deutlich schneller: %.1f statt %.1f Tage (gezielt %s, blind %s)" %
|
||||||
|
[g, b, str(gezielt), str(blind)])
|
||||||
|
var schlechter := 0
|
||||||
|
for i in range(gezielt.size()):
|
||||||
|
if int(blind[i]) < int(gezielt[i]):
|
||||||
|
schlechter += 1
|
||||||
|
p(schlechter == 0,
|
||||||
|
"Und blind gesetzt ist in keinem der %d Läufe besser (%d)" % [gezielt.size(), schlechter])
|
||||||
|
|
||||||
|
# Der Grund: ein falscher Moment ist unterm Strich ein Verlustgeschäft
|
||||||
|
Game.known = {"pomodoro": 0}
|
||||||
|
var gut := Game.evaluate("pomodoro", "mensch_0", "zoegert", false)
|
||||||
|
var falsch := Game.evaluate("pomodoro", "mensch_0", "gruebelt", false)
|
||||||
|
p(gut["moment"] and not falsch["moment"], "»zögert« ist Pomodoros Moment, »grübelt« nicht")
|
||||||
|
var ertrag_gut := (8.0 + 2.0) + 5.0 + 5.0
|
||||||
|
var ertrag_falsch := (8.0 + 2.0) + 5.0
|
||||||
|
var ev_gut: float = float(gut["chance"]) * ertrag_gut \
|
||||||
|
- (1.0 - float(gut["chance"])) * Game.FEHLVERSUCH_VERLUST
|
||||||
|
var ev_falsch: float = float(falsch["chance"]) * ertrag_falsch \
|
||||||
|
- (1.0 - float(falsch["chance"])) * Game.FEHLVERSUCH_VERLUST
|
||||||
|
p(ev_gut > 0.0, "Im richtigen Moment trägt sie (%.1f je Einsatz)" % ev_gut)
|
||||||
|
p(ev_falsch < 0.0, "Im falschen kostet sie (%.1f je Einsatz)" % ev_falsch)
|
||||||
|
|
||||||
|
|
||||||
|
## Warten soll nicht wehtun: 1, 2, 3 stellen das Tempo. Über der Nacht des
|
||||||
|
## Menschen springt es von allein auf drei — dort ist ohnehin nichts zu holen.
|
||||||
|
func test_tempo() -> void:
|
||||||
|
abschnitt("Tempo lässt sich stellen")
|
||||||
|
Game.completed.clear()
|
||||||
|
Game.known = {"pomodoro": 0}
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
Game.active_mission = ""
|
||||||
|
Game.tempo = 1
|
||||||
|
p(is_equal_approx(Game.zeit_faktor(), 1.0), "Ohne Zutun läuft die Welt einfach")
|
||||||
|
|
||||||
|
# Wie in Die Sims: die Stufe ist nicht der Faktor
|
||||||
|
var erwartet := {1: 1.0, 2: 3.0, 3: 7.0}
|
||||||
|
for stufe in [2, 3, 1]:
|
||||||
|
Game.tempo_setzen(stufe)
|
||||||
|
p(Game.tempo == stufe and is_equal_approx(Game.zeit_faktor(), float(erwartet[stufe])),
|
||||||
|
"Stufe %d läuft %.0f-fach" % [stufe, Game.zeit_faktor()])
|
||||||
|
Game.tempo_setzen(3)
|
||||||
|
p(not is_equal_approx(Game.zeit_faktor(), 3.0),
|
||||||
|
"Stufe 3 heißt gerade nicht dreifach (%.0f×)" % Game.zeit_faktor())
|
||||||
|
Game.tempo_setzen(1)
|
||||||
|
Game.tempo_setzen(9)
|
||||||
|
p(Game.tempo == 3, "Höher als drei geht nicht (%d)" % Game.tempo)
|
||||||
|
Game.tempo_setzen(0)
|
||||||
|
p(Game.tempo == 1, "Und tiefer als eins auch nicht (%d)" % Game.tempo)
|
||||||
|
|
||||||
|
# Über der Nacht des Menschen springt es von allein
|
||||||
|
Game.accept_mission("mensch_0")
|
||||||
|
var wach := -1.0
|
||||||
|
var nacht := -1.0
|
||||||
|
for stunde in range(24):
|
||||||
|
Game.time_h = float(stunde) + 0.5
|
||||||
|
if Game.schlaeft_ziel():
|
||||||
|
nacht = Game.zeit_faktor()
|
||||||
|
else:
|
||||||
|
wach = Game.zeit_faktor()
|
||||||
|
p(nacht > 0.0 and wach > 0.0, "Lenas Tag hat wache und schlafende Stunden")
|
||||||
|
p(is_equal_approx(nacht, Game.faktor_von(3)),
|
||||||
|
"Im Schlaf läuft die Welt auf der höchsten Stufe (%.0f×)" % nacht)
|
||||||
|
p(is_equal_approx(wach, 1.0), "Wach wieder auf der gewählten Stufe (%.0f)" % wach)
|
||||||
|
|
||||||
|
Game.tempo_setzen(1)
|
||||||
|
Game.time_h = 2.0
|
||||||
|
p(Game.schlaeft_ziel(), "Um 02:00 schläft sie")
|
||||||
|
p(is_equal_approx(Game.zeit_faktor(), Game.faktor_von(3)),
|
||||||
|
"Die eigene Wahl Stufe 1 gilt dann nicht — die Nacht springt")
|
||||||
|
|
||||||
|
# Ohne Mission gibt es nichts zu überspringen
|
||||||
|
Game.active_mission = ""
|
||||||
|
p(not Game.schlaeft_ziel(), "Ohne Mission kein Nachtsprung")
|
||||||
|
p(is_equal_approx(Game.zeit_faktor(), 1.0), "Dann zählt nur die Wahl")
|
||||||
|
|
||||||
|
# Der Faktor beschleunigt die Uhr wirklich
|
||||||
|
Game.tempo_setzen(3)
|
||||||
|
Game.time_h = 8.0
|
||||||
|
Game.day = 1
|
||||||
|
var vorher := Game.time_h
|
||||||
|
Game.advance_time(1.0 * Game.zeit_faktor())
|
||||||
|
var schnell := Game.time_h - vorher
|
||||||
|
Game.tempo_setzen(1)
|
||||||
|
Game.time_h = 8.0
|
||||||
|
vorher = Game.time_h
|
||||||
|
Game.advance_time(1.0 * Game.zeit_faktor())
|
||||||
|
var langsam := Game.time_h - vorher
|
||||||
|
p(is_equal_approx(schnell, langsam * Game.faktor_von(3)),
|
||||||
|
"Auf Stufe 3 vergeht %.0f-mal so viel Zeit je Sekunde (%.4f statt %.4f h)" %
|
||||||
|
[Game.faktor_von(3), schnell, langsam])
|
||||||
|
|
||||||
|
# Das HUD sagt, woran man ist
|
||||||
|
Game.tempo_setzen(2)
|
||||||
|
ui().journal_panel()
|
||||||
|
ui().schliessen()
|
||||||
|
ui()._hud_aktualisieren()
|
||||||
|
p(ui()._hud.text.contains("Tempo 2 (3×)"),
|
||||||
|
"Das HUD nennt Stufe und Faktor getrennt")
|
||||||
|
Game.accept_mission("mensch_0")
|
||||||
|
Game.time_h = 2.0
|
||||||
|
ui()._hud_aktualisieren()
|
||||||
|
p(ui()._hud.text.contains("Nachtsprung"), "Und weist den Nachtsprung aus")
|
||||||
|
Game.tempo_setzen(1)
|
||||||
|
Game.active_mission = ""
|
||||||
|
|
||||||
|
|
||||||
|
## Das Dorf wird nicht gesetzt, sondern wächst. Also muss über viele
|
||||||
|
## Ausbaustufen gelten: nichts überlappt, kein Weg führt durch ein Haus, alles
|
||||||
|
## hängt zusammen, und was einmal steht, bleibt stehen.
|
||||||
|
func test_dorf_organisch() -> void:
|
||||||
|
abschnitt("Das Dorf wächst organisch")
|
||||||
|
var merker: Array = Game.completed.duplicate()
|
||||||
|
var stufen := 24
|
||||||
|
|
||||||
|
var ueberlappung := ""
|
||||||
|
var wegtreffer := ""
|
||||||
|
var lose := ""
|
||||||
|
var ohne_anker := ""
|
||||||
|
var unerreichbar := ""
|
||||||
|
var sektoren_12 := 0
|
||||||
|
var dichte := ""
|
||||||
|
for stufe in range(stufen + 1):
|
||||||
|
Game.completed.clear()
|
||||||
|
for i in range(stufe):
|
||||||
|
Game.completed.append("stufe_%d" % i)
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
var netz: Netz = DreamDB.NETZ
|
||||||
|
var keys: Array = []
|
||||||
|
for key in DreamDB.PLACES:
|
||||||
|
if float(DreamDB.PLACES[key]["size"][0]) > 0.0:
|
||||||
|
keys.append(key)
|
||||||
|
|
||||||
|
# nichts steht ineinander
|
||||||
|
for a in range(keys.size()):
|
||||||
|
for b in range(a + 1, keys.size()):
|
||||||
|
var ca := Orte.center(DreamDB.PLACES, keys[a])
|
||||||
|
var cb := Orte.center(DreamDB.PLACES, keys[b])
|
||||||
|
var sa: Array = DreamDB.PLACES[keys[a]]["size"]
|
||||||
|
var sb: Array = DreamDB.PLACES[keys[b]]["size"]
|
||||||
|
if absf(ca.x - cb.x) < (float(sa[0]) + float(sb[0])) * 0.5 \
|
||||||
|
and absf(ca.z - cb.z) < (float(sa[1]) + float(sb[1])) * 0.5 \
|
||||||
|
and ueberlappung == "":
|
||||||
|
ueberlappung = ("%s und %s bei %d Häusern"
|
||||||
|
% [keys[a], keys[b], stufe])
|
||||||
|
|
||||||
|
# kein Weg führt durch ein Gebäude
|
||||||
|
for k in netz.kanten:
|
||||||
|
for key in keys:
|
||||||
|
var s2: Array = DreamDB.PLACES[key]["size"]
|
||||||
|
if Netz.trifft_rechteck(netz.knoten[k[0]], netz.knoten[k[1]],
|
||||||
|
Orte.center(DreamDB.PLACES, key),
|
||||||
|
Vector2(float(s2[0]), float(s2[1])), 0.0) and wegtreffer == "":
|
||||||
|
wegtreffer = "%s bei %d Häusern" % [key, stufe]
|
||||||
|
|
||||||
|
if not netz.zusammenhaengend() and lose == "":
|
||||||
|
lose = "bei %d Häusern" % stufe
|
||||||
|
|
||||||
|
# jedes Haus hängt am Netz und ist vom Dorfplatz aus zu erreichen
|
||||||
|
for i in range(stufe):
|
||||||
|
var key3 := DreamDB.haus_key(i)
|
||||||
|
if not DreamDB.ANKER.has(key3):
|
||||||
|
if ohne_anker == "":
|
||||||
|
ohne_anker = "%s bei %d Häusern" % [key3, stufe]
|
||||||
|
continue
|
||||||
|
if netz.weglaenge(int(DreamDB.ANKER["lichtung"]), int(DreamDB.ANKER[key3])) == INF \
|
||||||
|
and unerreichbar == "":
|
||||||
|
unerreichbar = "%s bei %d Häusern" % [key3, stufe]
|
||||||
|
|
||||||
|
if stufe == 12:
|
||||||
|
var sekt: Dictionary = {}
|
||||||
|
for i in range(stufe):
|
||||||
|
var c := Orte.center(DreamDB.PLACES, DreamDB.haus_key(i))
|
||||||
|
sekt[int(floor((atan2(c.z, c.x) + PI) / (TAU / 8.0))) % 8] = true
|
||||||
|
sektoren_12 = sekt.size()
|
||||||
|
if stufe == stufens_probe():
|
||||||
|
var flaeche := PI * pow(Game.insel_radius(), 2.0)
|
||||||
|
if flaeche / float(stufe) > 2200.0:
|
||||||
|
dichte = "%.0f m² je Haus" % (flaeche / float(stufe))
|
||||||
|
|
||||||
|
p(ueberlappung == "", "Über %d Ausbaustufen steht kein Gebäude im anderen%s" %
|
||||||
|
[stufen, "" if ueberlappung == "" else " — " + ueberlappung])
|
||||||
|
p(wegtreffer == "", "Und kein Weg führt durch ein Gebäude%s" %
|
||||||
|
["" if wegtreffer == "" else " — " + wegtreffer])
|
||||||
|
p(lose == "", "Das Wegenetz hängt immer zusammen%s" %
|
||||||
|
["" if lose == "" else " — " + lose])
|
||||||
|
p(ohne_anker == "", "Jedes Haus hängt am Netz%s" %
|
||||||
|
["" if ohne_anker == "" else " — " + ohne_anker])
|
||||||
|
p(unerreichbar == "", "Und ist vom Dorfplatz aus erreichbar%s" %
|
||||||
|
["" if unerreichbar == "" else " — " + unerreichbar])
|
||||||
|
p(sektoren_12 >= 6,
|
||||||
|
"Bei 12 Häusern ist das Dorf rundum gewachsen (%d von 8 Richtungen)" % sektoren_12)
|
||||||
|
p(dichte == "", "Die Insel bleibt dicht besiedelt%s" %
|
||||||
|
["" if dichte == "" else " — " + dichte])
|
||||||
|
|
||||||
|
# Verwinkelt heißt: Gabeln und Schleifen, nicht Speichen zur Mitte
|
||||||
|
Game.completed.clear()
|
||||||
|
for i in range(18):
|
||||||
|
Game.completed.append("stufe_%d" % i)
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
var netz2: Netz = DreamDB.NETZ
|
||||||
|
var gabeln := 0
|
||||||
|
for i in range(netz2.knoten.size()):
|
||||||
|
if netz2.nachbarn(i).size() >= 3:
|
||||||
|
gabeln += 1
|
||||||
|
var schleifen: int = netz2.kanten.size() - netz2.knoten.size() + 1
|
||||||
|
p(gabeln >= 6, "Bei 18 Häusern gabelt sich das Netz %d-mal" % gabeln)
|
||||||
|
p(schleifen >= 2, "Und trägt %d Schleifen statt lauter Sackgassen" % schleifen)
|
||||||
|
|
||||||
|
# Was einmal steht, bleibt stehen
|
||||||
|
var vorher: Array = []
|
||||||
|
for i in range(18):
|
||||||
|
vorher.append(Orte.center(DreamDB.PLACES, DreamDB.haus_key(i)))
|
||||||
|
Game.completed.append("stufe_18")
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
var verrutscht := 0
|
||||||
|
for i in range(18):
|
||||||
|
if vorher[i].distance_to(Orte.center(DreamDB.PLACES, DreamDB.haus_key(i))) > 0.01:
|
||||||
|
verrutscht += 1
|
||||||
|
p(verrutscht == 0, "Das 19. Haus verschiebt keines der 18 alten (%d)" % verrutscht)
|
||||||
|
|
||||||
|
Game.completed.assign(merker)
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
|
||||||
|
|
||||||
|
func stufens_probe() -> int:
|
||||||
|
return 24
|
||||||
|
|
||||||
|
|
||||||
|
## Die Erde ist ein erzeugter Bezirk. Also muss gelten: Parzellen liegen
|
||||||
|
## nebeneinander statt ineinander, jede hat Straßenanschluss, das Netz hängt
|
||||||
|
## zusammen -- und anders als im Dorf wächst hier nichts.
|
||||||
|
func test_bezirk() -> void:
|
||||||
|
abschnitt("Die Erde ist ein Bezirk")
|
||||||
|
var netz: Netz = HumanDB.NETZ
|
||||||
|
p(HumanDB.PARZELLEN.size() >= 60,
|
||||||
|
"Der Bezirk ist in %d Parzellen geteilt" % HumanDB.PARZELLEN.size())
|
||||||
|
|
||||||
|
var ueber := 0
|
||||||
|
for i in range(HumanDB.PARZELLEN.size()):
|
||||||
|
for j in range(i + 1, HumanDB.PARZELLEN.size()):
|
||||||
|
var a: Dictionary = HumanDB.PARZELLEN[i]
|
||||||
|
var b: Dictionary = HumanDB.PARZELLEN[j]
|
||||||
|
if absf(a["pos"].x - b["pos"].x) < (a["size"].x + b["size"].x) * 0.5 - 0.05 \
|
||||||
|
and absf(a["pos"].z - b["pos"].z) < (a["size"].y + b["size"].y) * 0.5 - 0.05:
|
||||||
|
ueber += 1
|
||||||
|
p(ueber == 0, "Keine Parzelle steht in einer anderen (%d)" % ueber)
|
||||||
|
|
||||||
|
var ohne := 0
|
||||||
|
var weiteste := 0.0
|
||||||
|
for pz in HumanDB.PARZELLEN:
|
||||||
|
var d := INF
|
||||||
|
for st in HumanDB.STRASSEN:
|
||||||
|
var f := Geometry3D.get_closest_point_to_segment(pz["pos"], st["a"], st["b"])
|
||||||
|
d = minf(d, f.distance_to(pz["pos"]) - maxf(pz["size"].x, pz["size"].y) * 0.5
|
||||||
|
- float(st["breite"]) * 0.5)
|
||||||
|
weiteste = maxf(weiteste, d)
|
||||||
|
if d > 2.0:
|
||||||
|
ohne += 1
|
||||||
|
p(ohne == 0, "Jede Parzelle liegt an einer Straße (weiteste Lücke %.1f m)" % weiteste)
|
||||||
|
|
||||||
|
p(netz.zusammenhaengend(),
|
||||||
|
"Das Straßennetz hängt zusammen (%d Knoten, %d Kanten)" %
|
||||||
|
[netz.knoten.size(), netz.kanten.size()])
|
||||||
|
var unerreichbar := 0
|
||||||
|
var keys: Array = HumanDB.PLACES.keys()
|
||||||
|
for i in range(keys.size()):
|
||||||
|
for j in range(i + 1, keys.size()):
|
||||||
|
if netz.weglaenge(int(HumanDB.ANKER[keys[i]]), int(HumanDB.ANKER[keys[j]])) == INF:
|
||||||
|
unerreichbar += 1
|
||||||
|
p(unerreichbar == 0, "Jeder Ort ist von jedem anderen aus zu erreichen (%d Lücken)" %
|
||||||
|
unerreichbar)
|
||||||
|
|
||||||
|
# Arterien im Raster, roji schmal und als Sackgasse ins Blockinnere
|
||||||
|
var arterien := 0
|
||||||
|
var gassen := 0
|
||||||
|
for st in HumanDB.STRASSEN:
|
||||||
|
if float(st["breite"]) >= HumanDB.ARTERIE:
|
||||||
|
arterien += 1
|
||||||
|
else:
|
||||||
|
gassen += 1
|
||||||
|
p(arterien == HumanDB.ACHSEN.size() * 2,
|
||||||
|
"%d Arterien spannen das Raster auf" % arterien)
|
||||||
|
p(gassen >= 4, "Und %d roji führen ins Blockinnere" % gassen)
|
||||||
|
p(HumanDB.ROJI < HumanDB.ARTERIE * 0.6,
|
||||||
|
"Eine roji ist mit %.1f m deutlich schmaler als eine Arterie (%.1f m)" %
|
||||||
|
[HumanDB.ROJI, HumanDB.ARTERIE])
|
||||||
|
var sackgassen := 0
|
||||||
|
for st in HumanDB.STRASSEN:
|
||||||
|
if float(st["breite"]) < HumanDB.ARTERIE:
|
||||||
|
var ende: Vector3 = st["b"]
|
||||||
|
if netz.nachbarn(netz.fuege_knoten(ende)).size() <= 1:
|
||||||
|
sackgassen += 1
|
||||||
|
p(sackgassen >= gassen / 2, "Die meisten roji enden blind (%d von %d)" %
|
||||||
|
[sackgassen, gassen])
|
||||||
|
|
||||||
|
# Alle Menschen sind da, und sie werden nicht mehr
|
||||||
|
var vorher := HumanDB.LIST.size()
|
||||||
|
p(vorher == HumanDB.EINWOHNER, "Alle %d Menschen sind von Anfang an da" % vorher)
|
||||||
|
var merker: Array = Game.completed.duplicate()
|
||||||
|
for i in range(6):
|
||||||
|
Game.completed.append("stufe_%d" % i)
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
p(HumanDB.LIST.size() == vorher,
|
||||||
|
"Sechs erfüllte Träume ändern daran nichts (%d)" % HumanDB.LIST.size())
|
||||||
|
Game.completed.assign(merker)
|
||||||
|
Game.welt_erzeugen()
|
||||||
|
|
||||||
|
# Jeder wohnt in einer eigenen Wohnung
|
||||||
|
var heime: Dictionary = {}
|
||||||
|
for m in HumanDB.LIST:
|
||||||
|
heime[str(m["heim"])] = true
|
||||||
|
p(heime.size() == HumanDB.LIST.size(),
|
||||||
|
"Jeder Mensch hat seine eigene Wohnung (%d)" % heime.size())
|
||||||
|
|
||||||
|
# Der mittlere Block bleibt frei
|
||||||
|
var im_park := 0
|
||||||
|
var halb: float = (float(HumanDB.ACHSEN[2]) - float(HumanDB.ACHSEN[1])) * 0.5 \
|
||||||
|
- HumanDB.ARTERIE
|
||||||
|
for pz in HumanDB.PARZELLEN:
|
||||||
|
var q: Vector3 = pz["pos"]
|
||||||
|
if absf(q.x) < halb and absf(q.z) < halb:
|
||||||
|
im_park += 1
|
||||||
|
p(im_park == 0, "Im mittleren Block steht kein Haus — er ist der Park (%d)" % im_park)
|
||||||
|
|||||||
Reference in New Issue
Block a user