update
This commit is contained in:
85
README.md
85
README.md
@@ -12,24 +12,56 @@ sie allein nicht bewegt bekommen. Du wirst zu ihnen geschickt.
|
||||
|
||||
Steuerung: **WASD** bewegen · **Maus** umsehen · **E** interagieren ·
|
||||
**J** Journal · **Esc** Maus freigeben.
|
||||
Auf der Erde zusätzlich **Leertaste** steigen und **Umschalt** sinken.
|
||||
In der Traumwelt springt **Leertaste**; auf der Erde steigt sie und **Umschalt**
|
||||
lässt sinken.
|
||||
|
||||
## Spielschleife
|
||||
|
||||
**Journal** (Taste **J**, in beiden Welten dabei)
|
||||
|
||||
* **Techniken** — zwölf echte Lösungswege. Jede nennt *Was* sie tut, *Wann* sie
|
||||
passt und *Warum* sie hilft. Lernen kostet 1 Einsicht. Anwenden verbessert eine
|
||||
Technik von allein; Vertiefen für 1 Einsicht geht zusätzlich.
|
||||
* **Missionen** — vier Menschen mit ihrem Traum und dem, was im Weg steht.
|
||||
Das Journal führt Buch — es lehrt und vergibt nichts.
|
||||
|
||||
**Traumwelt** — eine schwebende Insel mit einem Dorf
|
||||
* **Techniken** — was du gelernt hast, mit Stufe und Erfahrung.
|
||||
* **Missionen** — die Menschen, für die du dich entschieden hast, jeweils mit
|
||||
ihrem **Regelwerk**.
|
||||
|
||||
* Vier Wesen wohnen hier: Ailen die Traumweberin, Morr der Erinnerungssammler,
|
||||
Ivo der Bote, Sela die Wächterin des Tors.
|
||||
* Jedes hat ein eigenes Haus und einen eigenen Tagesablauf. Die Häuser lassen
|
||||
sich betreten, auf der Lichtung treffen sich die Wesen.
|
||||
* **Tor zur Erde** — öffnet sich nur mit angenommener Mission.
|
||||
**Regelwerk** — der Kern des Spiels
|
||||
|
||||
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
|
||||
allein, solange du auf der Erde bist. Neben jeder Wahl steht die Erfolgschance
|
||||
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
|
||||
sie vom alten weg. Mehr Momente abzudecken heißt also, mehr Techniken zu
|
||||
kennen, und die gibt es nur auf der gewachsenen Insel.
|
||||
|
||||
So laufen die zwei Schleifen:
|
||||
|
||||
Techniken lernen → Regelwerk anpassen → Probleme lösen
|
||||
Träume erfüllen → Dorf wächst → neue Wege entdecken
|
||||
|
||||
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.
|
||||
|
||||
Schlafende sind unerreichbar, dieselbe Technik wirkt bei Wiederholung
|
||||
schwächer, ein Fehlversuch macht kurz dicht. Von Hand geht es weiter: nah
|
||||
herangehen und **E** wirkt sofort.
|
||||
|
||||
**Traumwelt** — eine schwebende Insel mit einem Dorf, das erzeugt wird
|
||||
|
||||
* Fest sind nur drei Dinge: der **Hörsaal der Wege**, der **Dorfplatz** und das
|
||||
**Tor zur Erde**. Alles andere entsteht beim Wachsen.
|
||||
* **Die Insel beginnt kahl.** Ein Gebäude, kein Bewohner, ein einziger
|
||||
gelehrter Weg.
|
||||
* Jeder erfüllte Traum bringt einen Bewohner mit seinem Haus — Lage, Größe,
|
||||
Farbe, Türrichtung, Einrichtung und Tagesablauf werden erzeugt, ringförmig
|
||||
nach außen. **In jedem Haus wird ein Weg gelehrt, den es vorher nirgends
|
||||
gab.** Ein Mensch, ein Haus, eine Technik.
|
||||
* Die Insel dehnt sich mit, damit jedes Haus festen Grund hat. Das Dorf hört
|
||||
nicht auf zu wachsen: die Kleinstadt schickt immer neue Menschen.
|
||||
* **Schwarzes Brett** am Dorfplatz — hier hängen die Menschen aus, die nicht
|
||||
weiterkommen. Mission annehmen.
|
||||
|
||||
**Erde** — eine Kleinstadt, ein Tag dauert 6 Minuten
|
||||
|
||||
@@ -57,26 +89,37 @@ bringen 3 Einsicht zurück.
|
||||
|
||||
project.godot Projekt, Autoload `Game`
|
||||
scenes/main.tscn Einstieg
|
||||
scripts/game.gd Zustand, Regeln, Uhr (Autoload)
|
||||
scripts/game.gd Zustand, Regelwerk, wachsende Welt, Uhr (Autoload)
|
||||
scripts/technique_db.gd die zwölf Techniken mit ihren Hinweisen
|
||||
scripts/human_db.gd Kleinstadt und Menschen
|
||||
scripts/dream_db.gd Traumdorf und Traumwesen
|
||||
scripts/human_db.gd Kleinstadt, erzeugte Wohnhäuser und Menschen
|
||||
scripts/dream_db.gd Traumdorf: erzeugt Häuser, Bewohner und Lehrorte
|
||||
scripts/orte.gd Orte, Türen, Wege, Tätigkeiten (beide Welten)
|
||||
scripts/bewohner.gd Bewohner mit Tagesablauf und Reaktionen
|
||||
scripts/bewohner.gd Bewohner mit Tagesablauf, löst Regeln aus
|
||||
scripts/main.gd Weltenwechsel, Fokus, Interaktion
|
||||
scripts/player.gd Läufer in der Traumwelt, Geist auf der Erde
|
||||
scripts/dream_world.gd Insel mit Dorf
|
||||
scripts/earth_world.gd Stadt mit Tag-Nacht-Wechsel
|
||||
scripts/ui.gd HUD, Journal und alle Panels
|
||||
scripts/ui.gd HUD, Journal, Regelwerk, Hörsaal, Brett, Panels
|
||||
scripts/prims.gd Bausteine aus Primitiven
|
||||
|
||||
## Prüfen
|
||||
|
||||
godot --headless --path . tests/verify.tscn # Prüfungen der Spiellogik
|
||||
godot --path . tests/shots.tscn # 15 Screenshots nach user://shots/
|
||||
godot --path . tests/shots.tscn # 28 Screenshots nach user://shots/
|
||||
|
||||
`verify` fährt das echte Spiel: Traumdorf mit vier Wesen und ihren Tagesabläufen,
|
||||
Häuser betreten, Journal in beiden Welten, Technik lernen und vertiefen,
|
||||
Verbessern allein durchs Anwenden, Mission annehmen, zur Erde reisen,
|
||||
`verify` fährt das echte Spiel: leeres Traumdorf, einziehende Wesen mit ihren
|
||||
Tagesabläufen,
|
||||
Häuser betreten, Journal in beiden Welten, im Hörsaal lernen und vertiefen,
|
||||
Verbessern allein durchs Anwenden, Mission am Schwarzen Brett annehmen,
|
||||
Regelwerk bauen und einen Tag unbeaufsichtigt laufen lassen, zur Erde reisen,
|
||||
Tagesabläufe über einen ganzen Tag, Erfolgschancen, Mission abschließen,
|
||||
Tastensteuerung. Beendet sich mit Code 1, wenn etwas fehlt.
|
||||
Insel wachsen lassen, Tastensteuerung. Beendet sich mit Code 1, wenn etwas
|
||||
fehlt.
|
||||
|
||||
Drei 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
|
||||
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
|
||||
muss mit dem einen Startweg allein zu schaffen sein: über fünf Zufallsläufe
|
||||
vier bis acht Tage, ohne dass der Spieler eingreift. Ohne das hinge Loop 2
|
||||
fest, denn ohne ersten Traum gibt es keine zweite Technik.
|
||||
|
||||
@@ -2,23 +2,14 @@ Kern
|
||||
- Wesen aus einer Traumwelt werden auf die Erde geschickt,
|
||||
um Menschen zu helfen, ihre Träume zu erfüllen.
|
||||
|
||||
Journal (immer dabei)
|
||||
- Techniken lernen
|
||||
- Techniken verbessern
|
||||
- Missionen annehmen
|
||||
Loops
|
||||
- Techniken lernen -> Systeme anpassen -> Probleme lösen
|
||||
- Träume erfüllen -> Welt vergrößeren -> Techniken entdecken
|
||||
|
||||
Traumwelt
|
||||
- Bewegen
|
||||
- Orte betreten
|
||||
- Traumwesen leben ihren Tag
|
||||
|
||||
Erde
|
||||
- Bewegen als unsichtbarer Geist
|
||||
- Menschen leben ihren Tag
|
||||
- Menschen lesen
|
||||
- Menschen beeinflussen
|
||||
|
||||
Übergreifend
|
||||
- Zwischen den Welten reisen
|
||||
- Zeit vergeht
|
||||
- Missionen gelingen oder scheitern
|
||||
Funktionen
|
||||
- Bewegung: Bewegen, Springen, Geist
|
||||
- Journal: Gelernte Techniken, Akzetierte Missionen
|
||||
- Techniken: Lernen, Verbessern
|
||||
- Missionen: Pipeline
|
||||
- Menschen: Tagesablauf
|
||||
- Prozedurale Generierung: Dorf, Erde
|
||||
|
||||
@@ -14,9 +14,11 @@ Game="*res://scripts/game.gd"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1280
|
||||
window/size/viewport_height=720
|
||||
window/size/viewport_width=1920
|
||||
window/size/viewport_height=1080
|
||||
window/size/mode=2
|
||||
window/stretch/mode="canvas_items"
|
||||
window/stretch/aspect="expand"
|
||||
|
||||
[rendering]
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ var _kopf: MeshInstance3D
|
||||
var _aura: MeshInstance3D
|
||||
var _strahl: MeshInstance3D
|
||||
var _path: Array = []
|
||||
var _letzte_aktivitaet: String = ""
|
||||
var _bob: float = 0.0
|
||||
var _offset: Vector3
|
||||
|
||||
@@ -59,6 +60,7 @@ func setup(d: Dictionary, index: int, orte: Dictionary) -> void:
|
||||
var start := _routine_at(Game.time_h)
|
||||
place = start[0]
|
||||
activity = start[1]
|
||||
_letzte_aktivitaet = activity
|
||||
global_position = Orte.center(places, place) + _offset
|
||||
|
||||
|
||||
@@ -108,6 +110,10 @@ func _process(delta: float) -> void:
|
||||
_visual.rotation.z = lerp_angle(_visual.rotation.z, PI * 0.5 if activity == "schlaeft" else 0.0,
|
||||
1.0 - pow(0.01, delta))
|
||||
|
||||
if activity != _letzte_aktivitaet:
|
||||
_letzte_aktivitaet = activity
|
||||
_regel_pruefen()
|
||||
|
||||
var erledigt: bool = mit_mission and Game.completed.has(data["id"])
|
||||
var aktiv: bool = mit_mission and Game.active_mission == data["id"]
|
||||
_aura.visible = aktiv or erledigt
|
||||
@@ -138,6 +144,20 @@ func beschriftung() -> String:
|
||||
return s
|
||||
|
||||
|
||||
## Wechselt der Mensch die Tätigkeit, greift das Regelwerk der Mission von allein.
|
||||
func _regel_pruefen() -> void:
|
||||
if not mit_mission:
|
||||
return
|
||||
var res := Game.regel_ausloesen(data["id"], activity, resist_left > 0.0)
|
||||
if res.is_empty():
|
||||
return
|
||||
var t := TechDB.by_id(str(res["tech"]))
|
||||
if res["success"]:
|
||||
reagiere(true, "%s\n+%d %%" % [t["name"], int(round(float(res["gain"])))])
|
||||
else:
|
||||
reagiere(false, "%s prallt ab" % t["name"])
|
||||
|
||||
|
||||
## Sichtbare Reaktion auf eine angewandte Technik.
|
||||
func reagiere(success: bool, text: String) -> void:
|
||||
var l := Prims.label(self, Vector3(0, 3.5, 0), text, 0.34,
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
class_name DreamDB
|
||||
## Die Traumwelt: ein Dorf. Jedes Wesen hat ein Haus und einen Tagesablauf.
|
||||
## 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
|
||||
## Bewohner werden erzeugt — der Reihe nach, immer weiter nach außen.
|
||||
|
||||
static var PLACES: Dictionary = {
|
||||
"haus_ailen": {
|
||||
"name": "Ailens Webstube", "pos": [-28.0, -22.0], "size": [12.0, 10.0],
|
||||
"dir": "back", "color": Color(0.72, 0.62, 0.92),
|
||||
},
|
||||
"haus_morr": {
|
||||
"name": "Morrs Kammer", "pos": [28.0, -22.0], "size": [12.0, 10.0],
|
||||
"dir": "back", "color": Color(0.58, 0.72, 0.90),
|
||||
},
|
||||
"haus_ivo": {
|
||||
"name": "Ivos Unterstand", "pos": [-28.0, 22.0], "size": [12.0, 10.0],
|
||||
"dir": "forward", "color": Color(0.88, 0.66, 0.80),
|
||||
},
|
||||
"haus_sela": {
|
||||
"name": "Selas Turmstube", "pos": [28.0, 22.0], "size": [12.0, 10.0],
|
||||
"dir": "forward", "color": Color(0.62, 0.82, 0.88),
|
||||
const HAEUSER_JE_RING := 6
|
||||
const RING_ERSTER := 30.0
|
||||
const RING_ABSTAND := 24.0
|
||||
|
||||
## Häuser sitzen auf Achtelrichtungen. Die Achsen 0 (Hörsaal) und 4 (Tor)
|
||||
## bleiben frei, damit der Weg vom Dorfplatz zu beiden offen bleibt.
|
||||
const SLOTS := [1, 2, 3, 5, 6, 7]
|
||||
|
||||
static var FEST: Dictionary = {
|
||||
"hoersaal": {
|
||||
"name": "Hörsaal der Wege", "pos": [0.0, 34.0], "size": [16.0, 12.0],
|
||||
"dir": "forward", "color": Color(0.66, 0.72, 0.94),
|
||||
},
|
||||
"lichtung": {
|
||||
"name": "Die Lichtung", "pos": [0.0, 2.0], "size": [0.0, 0.0],
|
||||
"name": "Der Dorfplatz", "pos": [0.0, 2.0], "size": [0.0, 0.0],
|
||||
"dir": "none", "color": Color(0.60, 0.50, 0.85),
|
||||
},
|
||||
"tor": {
|
||||
@@ -28,73 +26,145 @@ static var PLACES: Dictionary = {
|
||||
},
|
||||
}
|
||||
|
||||
static var LIST: Array = [
|
||||
{
|
||||
"id": "ailen",
|
||||
"name": "Ailen",
|
||||
"rolle": "Traumweberin",
|
||||
"leuchtet": true,
|
||||
"farbe": Color(0.78, 0.55, 0.95),
|
||||
"routine": [
|
||||
[0, "haus_ailen", "webt"],
|
||||
[6, "lichtung", "ruht"],
|
||||
[8, "haus_ailen", "webt"],
|
||||
[14, "lichtung", "singt"],
|
||||
[16, "haus_ailen", "webt"],
|
||||
[20, "haus_ailen", "traeumt"],
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "morr",
|
||||
"name": "Morr",
|
||||
"rolle": "Erinnerungssammler",
|
||||
"leuchtet": true,
|
||||
"farbe": Color(0.45, 0.70, 0.95),
|
||||
"routine": [
|
||||
[0, "haus_morr", "traeumt"],
|
||||
[5, "lichtung", "sammelt"],
|
||||
[9, "haus_morr", "sortiert"],
|
||||
[15, "lichtung", "sammelt"],
|
||||
[19, "haus_morr", "sortiert"],
|
||||
[22, "haus_morr", "traeumt"],
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "ivo",
|
||||
"name": "Ivo",
|
||||
"rolle": "Bote",
|
||||
"leuchtet": true,
|
||||
"farbe": Color(0.95, 0.60, 0.75),
|
||||
"routine": [
|
||||
[0, "haus_ivo", "traeumt"],
|
||||
[4, "tor", "beobachtet"],
|
||||
[8, "lichtung", "wandert"],
|
||||
[10, "haus_ivo", "ruht"],
|
||||
[13, "tor", "beobachtet"],
|
||||
[18, "lichtung", "singt"],
|
||||
[21, "haus_ivo", "traeumt"],
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "sela",
|
||||
"name": "Sela",
|
||||
"rolle": "Wächterin des Tors",
|
||||
"leuchtet": true,
|
||||
"farbe": Color(0.55, 0.95, 0.90),
|
||||
"routine": [
|
||||
[0, "tor", "wacht"],
|
||||
[7, "lichtung", "ruht"],
|
||||
[9, "tor", "wacht"],
|
||||
[17, "haus_sela", "ruht"],
|
||||
[19, "lichtung", "singt"],
|
||||
[21, "haus_sela", "traeumt"],
|
||||
],
|
||||
},
|
||||
## Wird von erzeuge() gefüllt und wächst mit dem Dorf.
|
||||
static var PLACES: Dictionary = FEST.duplicate(true)
|
||||
static var LIST: Array = []
|
||||
|
||||
static var FESTE_NAMEN: Array = ["Ailen", "Morr", "Ivo", "Sela"]
|
||||
static var FESTE_ROLLEN: Array = ["Traumweberin", "Erinnerungssammler", "Bote",
|
||||
"Wächterin des Tors"]
|
||||
static var SILBE_A: Array = ["Nu", "Ta", "Es", "Wre", "O", "Ma", "Si", "Te"]
|
||||
static var SILBE_B: Array = ["un", "lia", "kar", "n", "vin", "lu", "rin", "nne"]
|
||||
static var ROLLEN: Array = ["Lichtgießer", "Kartenzeichnerin", "Schwellenhüter",
|
||||
"Fadensucherin", "Stillenzähler", "Wegbereiterin", "Klangsammler", "Nachtgärtnerin"]
|
||||
|
||||
## Vier Tagesabläufe, alle mit reichlich Zeit zwischen den Stationen —
|
||||
## auch ein Haus am äußeren Ring ist so bequem zu erreichen.
|
||||
static var ABLAEUFE: Array = [
|
||||
[[0, "haus", "traeumt"], [6, "lichtung", "ruht"], [10, "haus", "webt"],
|
||||
[14, "lichtung", "singt"], [18, "haus", "sortiert"], [21, "haus", "traeumt"]],
|
||||
[[0, "haus", "traeumt"], [6, "lichtung", "sammelt"], [10, "haus", "sortiert"],
|
||||
[15, "tor", "beobachtet"], [19, "haus", "webt"], [21, "haus", "traeumt"]],
|
||||
[[0, "tor", "wacht"], [7, "lichtung", "ruht"], [11, "haus", "sortiert"],
|
||||
[15, "tor", "wacht"], [19, "lichtung", "singt"], [21, "haus", "traeumt"]],
|
||||
[[0, "haus", "traeumt"], [4, "tor", "beobachtet"], [8, "lichtung", "wandert"],
|
||||
[12, "haus", "ruht"], [16, "lichtung", "singt"], [20, "haus", "traeumt"]],
|
||||
]
|
||||
|
||||
|
||||
static func haus_key(i: int) -> String:
|
||||
return "haus_%d" % i
|
||||
|
||||
|
||||
## Baut Orte und Bewohner für ein Dorf mit so vielen Häusern neu auf.
|
||||
static func erzeuge(haeuser: int) -> void:
|
||||
PLACES = FEST.duplicate(true)
|
||||
LIST = []
|
||||
for i in range(maxi(haeuser, 0)):
|
||||
PLACES[haus_key(i)] = _haus(i)
|
||||
LIST.append(_bewohner(i))
|
||||
|
||||
|
||||
## Wie weit reicht das Dorf hinaus?
|
||||
static func aeusserster_ring(haeuser: int) -> float:
|
||||
if haeuser <= 0:
|
||||
return 0.0
|
||||
var ring := (haeuser - 1) / HAEUSER_JE_RING
|
||||
return RING_ERSTER + float(ring) * RING_ABSTAND
|
||||
|
||||
|
||||
static func _haus(i: int) -> Dictionary:
|
||||
var ring := i / HAEUSER_JE_RING
|
||||
var winkel := float(SLOTS[i % HAEUSER_JE_RING]) * TAU / 8.0
|
||||
var r := RING_ERSTER + float(ring) * RING_ABSTAND
|
||||
var x := sin(winkel) * r
|
||||
var z := cos(winkel) * r
|
||||
var breite := 12.0 + float(i % 2) * 2.0
|
||||
var tiefe := 10.0 + float(i % 3)
|
||||
return {
|
||||
"name": "%s Haus" % _besitz(_name(i)),
|
||||
"pos": [x, z],
|
||||
"size": [breite, tiefe],
|
||||
"dir": _tuer_zur_mitte(x, z),
|
||||
"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.
|
||||
static func _tuer_zur_mitte(x: float, z: float) -> String:
|
||||
if absf(x) > absf(z):
|
||||
return "left" if x > 0.0 else "right"
|
||||
return "forward" if z > 0.0 else "back"
|
||||
|
||||
|
||||
static func _bewohner(i: int) -> Dictionary:
|
||||
var haus := haus_key(i)
|
||||
var ablauf: Array = []
|
||||
for e in ABLAEUFE[i % ABLAEUFE.size()]:
|
||||
ablauf.append([e[0], haus if e[1] == "haus" else e[1], e[2]])
|
||||
return {
|
||||
"id": "wesen_%d" % i,
|
||||
"name": _name(i),
|
||||
"rolle": _rolle(i),
|
||||
"haus": haus,
|
||||
"leuchtet": true,
|
||||
"farbe": Color.from_hsv(fmod(0.72 + float(i) * 0.161, 1.0), 0.55, 0.95),
|
||||
"routine": ablauf,
|
||||
}
|
||||
|
||||
|
||||
static func _name(i: int) -> String:
|
||||
if i < FESTE_NAMEN.size():
|
||||
return str(FESTE_NAMEN[i])
|
||||
var j := i - FESTE_NAMEN.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.
|
||||
static func _besitz(n: String) -> String:
|
||||
var letzter := n.substr(n.length() - 1).to_lower()
|
||||
return n + "'" if ["s", "z", "x", "ß"].has(letzter) else n + "s"
|
||||
|
||||
|
||||
static func _rolle(i: int) -> String:
|
||||
if i < FESTE_ROLLEN.size():
|
||||
return str(FESTE_ROLLEN[i])
|
||||
return str(ROLLEN[(i - FESTE_ROLLEN.size()) % ROLLEN.size()])
|
||||
|
||||
|
||||
static func by_id(id: String) -> Dictionary:
|
||||
for w in LIST:
|
||||
if w["id"] == id:
|
||||
return w
|
||||
return {}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ Lehrorte
|
||||
|
||||
## Der Hörsaal lehrt den ersten Weg. Jedes Haus bringt einen weiteren mit.
|
||||
static func techniken_von(place_key: String) -> Array:
|
||||
var nr := -1
|
||||
if place_key == "hoersaal":
|
||||
nr = 0
|
||||
elif place_key.begins_with("haus_"):
|
||||
nr = int(place_key.substr(5)) + 1
|
||||
if nr < 0 or nr >= TechDB.LIST.size():
|
||||
return []
|
||||
return [TechDB.LIST[nr]["id"]]
|
||||
|
||||
|
||||
## An welchem Ort wird diese Technik gelehrt? "" wenn nirgends.
|
||||
static func ort_von_technik(tech_id: String) -> String:
|
||||
for i in range(TechDB.LIST.size()):
|
||||
if TechDB.LIST[i]["id"] == tech_id:
|
||||
return "hoersaal" if i == 0 else haus_key(i - 1)
|
||||
return ""
|
||||
|
||||
|
||||
## Alle Orte, an denen im aktuellen Dorf gelehrt wird.
|
||||
static func lehrorte() -> Array:
|
||||
var out: Array = ["hoersaal"]
|
||||
for i in range(LIST.size()):
|
||||
if not techniken_von(haus_key(i)).is_empty():
|
||||
out.append(haus_key(i))
|
||||
return out
|
||||
|
||||
@@ -13,6 +13,8 @@ func _ready() -> void:
|
||||
_environment()
|
||||
_insel()
|
||||
_haeuser()
|
||||
_lehrpulte()
|
||||
_schwarzes_brett()
|
||||
_lichtung()
|
||||
_tor_zur_erde()
|
||||
_wesen()
|
||||
@@ -52,9 +54,10 @@ func _environment() -> void:
|
||||
|
||||
|
||||
func _insel() -> void:
|
||||
Prims.cyl(self, Vector3(0, -1.03, 0), 52.0, 2.0, Color(0.48, 0.40, 0.64))
|
||||
Prims.cyl(self, Vector3(0, -4.0, 0), 40.0, 4.0, Color(0.34, 0.26, 0.50), false)
|
||||
Prims.cyl(self, Vector3(0, -9.0, 0), 22.0, 6.0, Color(0.24, 0.17, 0.38), false)
|
||||
var radius := Game.insel_radius()
|
||||
Prims.cyl(self, Vector3(0, -1.03, 0), radius, 2.0, Color(0.48, 0.40, 0.64))
|
||||
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)
|
||||
|
||||
# Ringweg, auf dem die Wesen gehen
|
||||
for i in range(36):
|
||||
@@ -67,7 +70,7 @@ func _insel() -> void:
|
||||
rng.seed = 7
|
||||
for i in range(34):
|
||||
var a := rng.randf() * TAU
|
||||
var r := rng.randf_range(18.0, 48.0)
|
||||
var r := rng.randf_range(18.0, Game.insel_radius() - 4.0)
|
||||
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 c := Color(0.6 + rng.randf() * 0.3, 0.5 + rng.randf() * 0.4, 1.0)
|
||||
@@ -80,7 +83,7 @@ func _haeuser() -> void:
|
||||
for key in DreamDB.PLACES:
|
||||
var pl: Dictionary = DreamDB.PLACES[key]
|
||||
var s: Array = pl["size"]
|
||||
if s[0] <= 0.0:
|
||||
if s[0] <= 0.0 or not Game.ort_offen(key):
|
||||
continue
|
||||
var c := Orte.center(DreamDB.PLACES, key)
|
||||
var groesse := Vector3(s[0], 3.2, s[1])
|
||||
@@ -106,33 +109,103 @@ func _haeuser() -> void:
|
||||
|
||||
|
||||
func _einrichtung(c: Vector3, groesse: Vector3, farbe: Color, key: String) -> void:
|
||||
if key == "hoersaal":
|
||||
_hoersaal_einrichten(c, groesse, farbe, key)
|
||||
return
|
||||
var w := groesse.x * 0.5 - 1.8
|
||||
var h := groesse.z * 0.5 - 1.8
|
||||
# Schlafnest hat jedes Haus
|
||||
Prims.cyl(self, c + Vector3(-w, 0.3, -h), 1.4, 0.5, farbe.darkened(0.2), false)
|
||||
match key:
|
||||
"haus_ailen": # Webstuhl
|
||||
match int(key.substr(5)) % 4:
|
||||
0: # Webstuhl
|
||||
Prims.box(self, c + Vector3(w, 1.1, 0), Vector3(0.3, 2.2, 3.0), farbe.darkened(0.45))
|
||||
for i in range(5):
|
||||
Prims.box(self, c + Vector3(w, 1.1, -1.2 + i * 0.6), Vector3(0.1, 1.8, 0.06),
|
||||
Color(0.9, 0.85, 1.0), false, 1.4)
|
||||
"haus_morr": # Regale voller Fundstücke
|
||||
1: # Regale voller Fundstücke
|
||||
for i in range(3):
|
||||
Prims.box(self, c + Vector3(w, 0.6 + i * 0.7, 0), Vector3(1.2, 0.12, 4.0),
|
||||
farbe.darkened(0.45))
|
||||
for j in range(4):
|
||||
Prims.box(self, c + Vector3(w, 0.85 + i * 0.7, -1.5 + j * 1.0),
|
||||
Vector3(0.4, 0.4, 0.4), Color(0.8, 0.9, 1.0), false, 0.8)
|
||||
"haus_ivo": # Karten und ein Fernrohr
|
||||
2: # Karten und ein Fernrohr
|
||||
Prims.box(self, c + Vector3(w, 0.75, 0), Vector3(1.6, 0.12, 2.6), farbe.darkened(0.45))
|
||||
var rohr := Prims.cyl(self, c + Vector3(w, 1.6, 0), 0.18, 2.0,
|
||||
Color(0.85, 0.9, 1.0), false, 0.6)
|
||||
rohr.rotation.x = 0.9
|
||||
"haus_sela": # Schale mit Licht
|
||||
_: # Schale mit Licht
|
||||
Prims.cyl(self, c + Vector3(w, 0.5, 0), 1.0, 1.0, farbe.darkened(0.4), false)
|
||||
Prims.sphere(self, c + Vector3(w, 1.4, 0), 0.55, Color(0.8, 1.0, 1.0), 2.0)
|
||||
|
||||
|
||||
## Bankreihen mit Mittelgang, Tafel an der Rückwand.
|
||||
func _hoersaal_einrichten(c: Vector3, groesse: Vector3, farbe: Color, key: String) -> void:
|
||||
var richtung: Vector3 = Orte.DIR_VEC[DreamDB.PLACES[key]["dir"]] * -1.0
|
||||
var quer := absf(richtung.z) > 0.0
|
||||
var bank := ((groesse.x if quer else groesse.z) - 5.0) * 0.5
|
||||
var laengs: Vector3 = Vector3(1, 0, 0) if quer else Vector3(0, 0, 1)
|
||||
for i in range(3):
|
||||
var tief := -3.6 + float(i) * 1.8
|
||||
for sx: float in [-1.0, 1.0]:
|
||||
var mitte: Vector3 = c + richtung * tief + laengs * (sx * (bank * 0.5 + 1.0))
|
||||
var sitz: Vector3 = Vector3(bank, 0.25, 0.9) if quer else Vector3(0.9, 0.25, bank)
|
||||
var lehne: Vector3 = Vector3(bank, 0.9, 0.15) if quer else Vector3(0.15, 0.9, bank)
|
||||
Prims.box(self, mitte + Vector3(0, 0.5, 0), sitz, farbe.darkened(0.35))
|
||||
Prims.box(self, mitte + richtung * 0.7 + Vector3(0, 0.9, 0), lehne,
|
||||
farbe.darkened(0.45))
|
||||
var tafel: Vector3 = Vector3(groesse.x - 5.0, 2.0, 0.15) if quer \
|
||||
else Vector3(0.15, 2.0, groesse.z - 5.0)
|
||||
Prims.box(self, c + richtung * 4.4 + Vector3(0, 1.4, 0), tafel, Color(0.16, 0.18, 0.30))
|
||||
|
||||
|
||||
## Vorn in jedem offenen Lehrort steht ein Pult.
|
||||
func _lehrpulte() -> void:
|
||||
for key in DreamDB.lehrorte():
|
||||
if not Game.ort_offen(key) or DreamDB.techniken_von(key).is_empty():
|
||||
continue
|
||||
var pl: Dictionary = DreamDB.PLACES[key]
|
||||
var tief: float = float(pl["size"][1])
|
||||
var innen: Vector3 = Orte.DIR_VEC[pl["dir"]] * -1.0
|
||||
var c := Orte.center(DreamDB.PLACES, key)
|
||||
var pos := c + innen * (tief * 0.5 - 2.6)
|
||||
var farbe: Color = pl["color"]
|
||||
Prims.box(self, pos + Vector3(0, 0.55, 0), Vector3(2.4, 1.1, 0.9), farbe.darkened(0.35))
|
||||
Prims.box(self, pos + Vector3(0, 1.18, 0), Vector3(2.8, 0.16, 1.2), farbe.darkened(0.2))
|
||||
Prims.sphere(self, pos + Vector3(0, 1.7, 0), 0.34, Color(0.85, 0.92, 1.0), 2.0)
|
||||
var lehrt: Array = DreamDB.techniken_von(key)
|
||||
Prims.label(self, pos + Vector3(0, 2.6, 0),
|
||||
TechDB.by_id(str(lehrt[0]))["name"], 0.42, Color(0.85, 0.92, 1))
|
||||
var n := _marke(pos - innen * 1.8 + Vector3(0, 1.4, 0), "lehre", str(pl["name"]))
|
||||
n.set_meta("arg", key)
|
||||
|
||||
|
||||
## An der Lichtung: hier hängen die Menschen aus, die Hilfe brauchen.
|
||||
func _schwarzes_brett() -> void:
|
||||
var pos := Orte.center(DreamDB.PLACES, "lichtung") + Vector3(-6.0, 0, 9.0)
|
||||
for s: float in [-1.4, 1.4]:
|
||||
Prims.cyl(self, pos + Vector3(s, 1.3, 0), 0.16, 2.6, Color(0.45, 0.38, 0.60))
|
||||
Prims.box(self, pos + Vector3(0, 2.2, 0), Vector3(3.6, 2.4, 0.18), Color(0.18, 0.16, 0.26))
|
||||
for i in range(HumanDB.LIST.size()):
|
||||
var x := -1.2 + float(i % 2) * 2.4
|
||||
var y := 2.8 - float(i / 2) * 1.1
|
||||
Prims.box(self, pos + Vector3(x, y, 0.12), Vector3(1.1, 0.8, 0.04),
|
||||
Color(0.92, 0.90, 0.80), false, 0.5)
|
||||
Prims.label(self, pos + Vector3(0, 4.2, 0), "Schwarzes Brett", 0.5, Color(1, 0.95, 0.85))
|
||||
_marke(pos + Vector3(0, 1.6, 1.2), "brett", "Schwarzes Brett")
|
||||
|
||||
|
||||
## Unsichtbarer Punkt, an dem die Interaktion hängt.
|
||||
func _marke(pos: Vector3, kind: String, titel: String) -> Node3D:
|
||||
var n := Node3D.new()
|
||||
n.position = pos
|
||||
add_child(n)
|
||||
n.add_to_group("interactable")
|
||||
n.set_meta("kind", kind)
|
||||
n.set_meta("arg", "")
|
||||
n.set_meta("titel", titel)
|
||||
return n
|
||||
|
||||
|
||||
func _lichtung() -> void:
|
||||
var c := Orte.center(DreamDB.PLACES, "lichtung")
|
||||
Prims.cyl(self, c + Vector3(0, 0.04, 0), 11.0, 0.12, Color(0.60, 0.52, 0.78), false)
|
||||
@@ -142,13 +215,15 @@ func _lichtung() -> void:
|
||||
var p := c + Vector3(cos(a) * 8.5, 0, sin(a) * 8.5)
|
||||
Prims.cyl(self, p + Vector3(0, 1.6, 0), 0.28, 3.2, Color(0.72, 0.64, 0.88))
|
||||
Prims.sphere(self, p + Vector3(0, 3.6, 0), 0.42, Color(0.75, 0.85, 1.0), 2.0)
|
||||
Prims.label(self, c + Vector3(0, 5.2, 0), DreamDB.PLACES["lichtung"]["name"], 0.6,
|
||||
Prims.label(self, c + Vector3(0, 5.2, 0), str(DreamDB.PLACES["lichtung"]["name"]), 0.6,
|
||||
Color(1, 0.95, 0.85))
|
||||
|
||||
|
||||
func _tor_zur_erde() -> void:
|
||||
var pos := Orte.center(DreamDB.PLACES, "tor")
|
||||
Prims.cyl(self, pos + Vector3(0, 0.3, 0), 5.5, 0.6, Color(0.35, 0.30, 0.50))
|
||||
# flach und ohne Kollision wie die Lichtung — eine 0,6 hohe Stufe hätte den
|
||||
# Spieler vor dem Tor stehen lassen
|
||||
Prims.cyl(self, pos + Vector3(0, 0.05, 0), 5.5, 0.1, Color(0.35, 0.30, 0.50), false)
|
||||
for i in range(10):
|
||||
var a := PI * float(i) / 9.0
|
||||
Prims.box(self, pos + Vector3(cos(a) * 4.2, sin(a) * 4.2 + 0.6, 0),
|
||||
@@ -158,17 +233,14 @@ func _tor_zur_erde() -> void:
|
||||
scheibe.rotation.x = PI * 0.5
|
||||
Prims.label(self, pos + Vector3(0, 6.4, 0), "Tor zur Erde", 0.8, Color(0.7, 0.95, 1))
|
||||
|
||||
var n := Node3D.new()
|
||||
n.position = pos + Vector3(0, 1.6, 0)
|
||||
add_child(n)
|
||||
n.add_to_group("interactable")
|
||||
n.set_meta("kind", "erde")
|
||||
n.set_meta("arg", "")
|
||||
n.set_meta("titel", "Tor zur Erde")
|
||||
_marke(pos + Vector3(0, 1.6, 0), "erde", "Tor zur Erde")
|
||||
|
||||
|
||||
## Am Anfang ist das Dorf leer; mit jedem erfüllten Traum zieht ein Wesen ein.
|
||||
func _wesen() -> void:
|
||||
for i in range(DreamDB.LIST.size()):
|
||||
if not Game.wesen_eingezogen(i):
|
||||
continue
|
||||
var w := Bewohner.new()
|
||||
add_child(w)
|
||||
w.setup(DreamDB.LIST[i], i, DreamDB.PLACES)
|
||||
|
||||
@@ -44,7 +44,14 @@ func _environment() -> void:
|
||||
|
||||
|
||||
func _boden() -> void:
|
||||
Prims.box(self, Vector3(0, -0.53, 0), Vector3(240, 1, 240), Color(0.36, 0.52, 0.30))
|
||||
var weite := 240.0
|
||||
for key in HumanDB.PLACES:
|
||||
var pl: Dictionary = HumanDB.PLACES[key]
|
||||
var p: Array = pl["pos"]
|
||||
var s2: Array = pl["size"]
|
||||
weite = maxf(weite, (absf(float(p[0])) + float(s2[0])) * 2.6)
|
||||
weite = maxf(weite, (absf(float(p[1])) + float(s2[1])) * 2.6)
|
||||
Prims.box(self, Vector3(0, -0.53, 0), Vector3(weite, 1, weite), Color(0.36, 0.52, 0.30))
|
||||
# Ringweg, auf dem die Menschen gehen
|
||||
for i in range(36):
|
||||
var a := TAU * float(i) / 36.0
|
||||
@@ -54,9 +61,9 @@ func _boden() -> void:
|
||||
|
||||
func _orte() -> void:
|
||||
for key in HumanDB.PLACES:
|
||||
if key == "park":
|
||||
continue
|
||||
var pl: Dictionary = HumanDB.PLACES[key]
|
||||
if float(pl["size"][0]) <= 0.0:
|
||||
continue
|
||||
var c := Orte.center(HumanDB.PLACES, key)
|
||||
var s: Array = pl["size"]
|
||||
var groesse := Vector3(s[0], 2.3, s[1])
|
||||
@@ -84,7 +91,7 @@ func _orte() -> void:
|
||||
func _einrichtung(c: Vector3, groesse: Vector3, farbe: Color, key: String) -> void:
|
||||
var h := groesse.z * 0.5 - 1.6
|
||||
var w := groesse.x * 0.5 - 1.6
|
||||
if key.begins_with("haus") or key == "wohnheim":
|
||||
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),
|
||||
Color(0.85, 0.85, 0.90)) # Bett
|
||||
Prims.box(self, c + Vector3(w - 1.0, 0.75, 0), Vector3(1.6, 0.15, 2.6),
|
||||
@@ -110,6 +117,12 @@ func _einrichtung(c: Vector3, groesse: Vector3, farbe: Color, key: String) -> vo
|
||||
Prims.box(self, c + Vector3(0, 0.5, -2.0 + i * 2.2), Vector3(groesse.x - 4.0, 0.2, 0.8),
|
||||
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))
|
||||
elif key == "werkstatt":
|
||||
Prims.box(self, c + Vector3(0, 0.55, 0), Vector3(groesse.x - 4.0, 1.1, 1.4),
|
||||
Color(0.55, 0.50, 0.48))
|
||||
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)
|
||||
|
||||
|
||||
func _park() -> void:
|
||||
|
||||
161
scripts/game.gd
161
scripts/game.gd
@@ -15,6 +15,7 @@ var known: Dictionary = {} # tech_id -> xp
|
||||
var active_mission: String = "" # human_id oder ""
|
||||
var progress: Dictionary = {} # human_id -> 0..100
|
||||
var used: Dictionary = {} # "human:tech" -> Anzahl
|
||||
var pipeline: Dictionary = {} # human_id -> {activity -> tech_id}
|
||||
var completed: Array = []
|
||||
var time_h: float = 7.0
|
||||
var day: int = 1
|
||||
@@ -23,6 +24,7 @@ var teleport_lock: float = 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
_setup_input()
|
||||
welt_erzeugen()
|
||||
|
||||
|
||||
func _setup_input() -> void:
|
||||
@@ -50,6 +52,15 @@ func advance_time(delta: float) -> void:
|
||||
while time_h >= 24.0:
|
||||
time_h -= 24.0
|
||||
day += 1
|
||||
_gewoehnung_klingt_ab()
|
||||
|
||||
|
||||
## Abstand macht eine Technik wieder wirksam: über Nacht verfällt die Hälfte
|
||||
## der Gewöhnung. Wer täglich denselben Weg geht, hält ihn brauchbar — wer ihn
|
||||
## an einem Tag immer wieder auffährt, stumpft ihn ab.
|
||||
func _gewoehnung_klingt_ab() -> void:
|
||||
for key in used:
|
||||
used[key] = int(used[key]) / 2
|
||||
|
||||
|
||||
func time_string() -> String:
|
||||
@@ -58,6 +69,54 @@ func time_string() -> String:
|
||||
return "Tag %d · %02d:%02d" % [day, h, m]
|
||||
|
||||
|
||||
# ------------------------------------------------------------ Wachsende Welt
|
||||
# Das Traumdorf wird erzeugt. Fest sind nur Hörsaal, Dorfplatz und Tor.
|
||||
# Jeder erfüllte Traum bringt einen Bewohner mit seinem Haus, und jedes Haus
|
||||
# lehrt eine Technik, die es vorher nirgends gab.
|
||||
|
||||
const INSEL_GRUND := 46.0
|
||||
const OFFENE_MISSIONEN := 3
|
||||
|
||||
|
||||
func haeuser() -> int:
|
||||
return completed.size()
|
||||
|
||||
|
||||
func insel_radius() -> float:
|
||||
var ring := DreamDB.aeusserster_ring(haeuser())
|
||||
return maxf(INSEL_GRUND, ring + 14.0)
|
||||
|
||||
|
||||
## Baut Traumdorf und Menschheit auf den Stand der erfüllten Träume.
|
||||
func welt_erzeugen() -> void:
|
||||
DreamDB.erzeuge(haeuser())
|
||||
HumanDB.erzeuge(completed.size() + OFFENE_MISSIONEN)
|
||||
|
||||
|
||||
## Das Traumdorf beginnt leer. Mit jedem erfüllten Traum zieht ein Wesen ein.
|
||||
func wesen_eingezogen(index: int) -> bool:
|
||||
return index < haeuser()
|
||||
|
||||
|
||||
## Steht dieser Ort schon auf der Insel? Erzeugt wird nur, was schon da ist.
|
||||
func ort_offen(place_key: String) -> bool:
|
||||
return DreamDB.PLACES.has(place_key)
|
||||
|
||||
|
||||
## Ist die Technik überhaupt schon irgendwo zu finden?
|
||||
func technik_offen(tech_id: String) -> bool:
|
||||
var ort := DreamDB.ort_von_technik(tech_id)
|
||||
return ort != "" and ort_offen(ort)
|
||||
|
||||
|
||||
func offene_techniken() -> int:
|
||||
var n := 0
|
||||
for t in TechDB.LIST:
|
||||
if technik_offen(t["id"]):
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Fertigkeiten
|
||||
|
||||
func knows(tech_id: String) -> bool:
|
||||
@@ -86,6 +145,10 @@ func xp_to_next(tech_id: String) -> String:
|
||||
func learn(tech_id: String) -> bool:
|
||||
if known.has(tech_id):
|
||||
return false
|
||||
if not technik_offen(tech_id):
|
||||
notified.emit("Diesen Weg kennt im Dorf noch niemand. Erfülle einen Traum.",
|
||||
Color(1, 0.8, 0.5))
|
||||
return false
|
||||
if einsicht < 1:
|
||||
notified.emit("Dafür fehlt dir Einsicht. Hilf erst einem Menschen.", Color(1, 0.6, 0.4))
|
||||
return false
|
||||
@@ -136,12 +199,107 @@ func accept_mission(human_id: String) -> bool:
|
||||
return true
|
||||
|
||||
|
||||
## Angenommen ist, wer schon einmal im Auftragsbuch stand.
|
||||
func angenommen(human_id: String) -> bool:
|
||||
return progress.has(human_id)
|
||||
|
||||
|
||||
func mission_progress(human_id: String) -> float:
|
||||
return progress.get(human_id, 0.0)
|
||||
|
||||
|
||||
## Ein erfüllter Traum lässt das Dorf wachsen: ein Wesen zieht mit seinem
|
||||
## Haus ein, und in diesem Haus wird ein neuer Weg gelehrt.
|
||||
func _insel_waechst() -> void:
|
||||
welt_erzeugen()
|
||||
var i := haeuser() - 1
|
||||
if i < 0 or i >= DreamDB.LIST.size():
|
||||
return
|
||||
var w: Dictionary = DreamDB.LIST[i]
|
||||
notified.emit("%s, %s, ist ins Traumdorf gezogen." % [w["name"], w["rolle"]],
|
||||
Color(0.85, 0.75, 1))
|
||||
var neu: Array = DreamDB.techniken_von(DreamDB.haus_key(i))
|
||||
if neu.is_empty():
|
||||
return
|
||||
notified.emit("In %s wird ein neuer Weg gelehrt: %s." %
|
||||
[DreamDB.PLACES[DreamDB.haus_key(i)]["name"], TechDB.by_id(str(neu[0]))["name"]],
|
||||
Color(0.75, 0.95, 1))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- Regelwerk
|
||||
# Eine Regel ist ein Wenn-Dann: tut der Mensch etwas Bestimmtes, greift die
|
||||
# zugeordnete Technik von allein. Damit automatisiert der Spieler den Fortschritt.
|
||||
|
||||
func regeln(human_id: String) -> Dictionary:
|
||||
return pipeline.get(human_id, {})
|
||||
|
||||
|
||||
func regel_fuer(human_id: String, activity: String) -> String:
|
||||
return regeln(human_id).get(activity, "")
|
||||
|
||||
|
||||
func regel_anzahl(human_id: String) -> int:
|
||||
return regeln(human_id).size()
|
||||
|
||||
|
||||
## Wo hängt diese Technik im Regelwerk gerade? "" wenn nirgends.
|
||||
func regel_moment(human_id: String, tech_id: String) -> String:
|
||||
for activity in regeln(human_id):
|
||||
if regeln(human_id)[activity] == tech_id:
|
||||
return activity
|
||||
return ""
|
||||
|
||||
|
||||
## Leere tech_id löscht die Regel. Eine Technik hält immer nur einen Posten —
|
||||
## sie an einen neuen Moment zu hängen nimmt sie vom alten weg. Das zwingt zur
|
||||
## Wahl und macht das Lernen weiterer Techniken erst wertvoll.
|
||||
func regel_setzen(human_id: String, activity: String, tech_id: String) -> void:
|
||||
if not pipeline.has(human_id):
|
||||
pipeline[human_id] = {}
|
||||
var r: Dictionary = pipeline[human_id]
|
||||
if tech_id == "":
|
||||
r.erase(activity)
|
||||
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
|
||||
state_changed.emit()
|
||||
|
||||
|
||||
## Der Mensch wechselt in eine Tätigkeit: greift dafür eine Regel?
|
||||
func regel_ausloesen(human_id: String, activity: String, resisting: bool) -> Dictionary:
|
||||
if human_id != active_mission:
|
||||
return {}
|
||||
var tech_id := regel_fuer(human_id, activity)
|
||||
if tech_id == "" or not known.has(tech_id):
|
||||
return {}
|
||||
var res := apply_technique(tech_id, human_id, activity, resisting)
|
||||
if not res["ok"]:
|
||||
return {}
|
||||
res["tech"] = tech_id
|
||||
var t := TechDB.by_id(tech_id)
|
||||
if res["success"]:
|
||||
notified.emit("Regel greift — %s bei »%s« (+%d %%)" %
|
||||
[t["name"], Orte.text(activity), int(round(float(res["gain"])))],
|
||||
Color(0.6, 1, 0.7))
|
||||
else:
|
||||
notified.emit("Regel läuft ins Leere — %s bei »%s« (%d %% Chance)" %
|
||||
[t["name"], Orte.text(activity), int(round(float(res["chance"]) * 100.0))],
|
||||
Color(1, 0.7, 0.5))
|
||||
return res
|
||||
|
||||
|
||||
# ----------------------------------------------------- Technik auf Mensch anwenden
|
||||
|
||||
## Schlafende erreicht keine Technik — weder von Hand noch über eine Regel.
|
||||
func erreichbar(activity: String) -> bool:
|
||||
return activity != "schlaeft"
|
||||
|
||||
|
||||
## Bewertet eine Technik in genau dieser Situation, ohne sie anzuwenden.
|
||||
func evaluate(tech_id: String, human_id: String, activity: String, resisting: bool) -> Dictionary:
|
||||
var t := TechDB.by_id(tech_id)
|
||||
@@ -151,7 +309,7 @@ func evaluate(tech_id: String, human_id: String, activity: String, resisting: bo
|
||||
"chance": 0.0, "tag": false, "moment": false, "blocked": false,
|
||||
"grund": "", "skill": skill,
|
||||
}
|
||||
if activity == "schlaeft":
|
||||
if not erreichbar(activity):
|
||||
res["blocked"] = true
|
||||
res["grund"] = "Sie oder er schläft. Techniken erreichen nur wache Menschen."
|
||||
return res
|
||||
@@ -214,6 +372,7 @@ func apply_technique(tech_id: String, human_id: String, activity: String, resist
|
||||
mission_done.emit(human_id)
|
||||
notified.emit("%s hat es geschafft. (+3 Einsicht)" % HumanDB.by_id(human_id)["name"],
|
||||
Color(1, 0.85, 0.4))
|
||||
_insel_waechst()
|
||||
state_changed.emit()
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
class_name HumanDB
|
||||
## Die Kleinstadt auf der Erde und die Menschen mit ihren Tagesabläufen.
|
||||
## Die Kleinstadt auf der Erde. Die Gebäude im Ring um den Park stehen fest;
|
||||
## Wohnhäuser und ihre Bewohner kommen dazu, solange Menschen Hilfe brauchen.
|
||||
|
||||
const HAEUSER_JE_RING := 8
|
||||
const RING_ERSTER := 62.0
|
||||
const RING_ABSTAND := 16.0
|
||||
|
||||
## Gebäude liegen im Ring um den Park. Alle Türen zeigen zur Mitte.
|
||||
## pos = Standplatz drinnen, size = Grundfläche, dir = Richtung der Türlücke.
|
||||
static var PLACES: Dictionary = {
|
||||
static var FEST: Dictionary = {
|
||||
"uni": {
|
||||
"name": "Hochschule", "pos": [-28.0, -27.0], "size": [14.0, 10.0],
|
||||
"dir": "back", "color": Color(0.72, 0.70, 0.66),
|
||||
@@ -20,125 +24,184 @@ static var PLACES: Dictionary = {
|
||||
"name": "Bäckerei Keller", "pos": [-31.0, 0.0], "size": [10.0, 12.0],
|
||||
"dir": "right", "color": Color(0.85, 0.72, 0.50),
|
||||
},
|
||||
"haus_tobias": {
|
||||
"name": "Wohnung Frank", "pos": [31.0, 0.0], "size": [10.0, 12.0],
|
||||
"werkstatt": {
|
||||
"name": "Werkstatt Loos", "pos": [31.0, 0.0], "size": [10.0, 12.0],
|
||||
"dir": "left", "color": Color(0.70, 0.58, 0.55),
|
||||
},
|
||||
"haus_lena": {
|
||||
"name": "Atelier Vogt", "pos": [-28.0, 27.0], "size": [12.0, 10.0],
|
||||
"dir": "forward", "color": Color(0.66, 0.58, 0.72),
|
||||
},
|
||||
"haus_mira": {
|
||||
"name": "Wohnheim", "pos": [0.0, 27.0], "size": [12.0, 10.0],
|
||||
"dir": "forward", "color": Color(0.60, 0.68, 0.62),
|
||||
},
|
||||
"haus_bruno": {
|
||||
"name": "Haus Keller", "pos": [28.0, 27.0], "size": [12.0, 10.0],
|
||||
"dir": "forward", "color": Color(0.78, 0.66, 0.58),
|
||||
},
|
||||
"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 LIST: Array = [
|
||||
static var PLACES: Dictionary = FEST.duplicate(true)
|
||||
static var LIST: Array = []
|
||||
|
||||
## Arbeitsplätze, die ein erzeugter Mensch bekommen kann.
|
||||
static var ARBEIT: Array = ["uni", "cafe", "buero", "baeckerei", "werkstatt"]
|
||||
|
||||
static var VORNAMEN: Array = ["Lena", "Bruno", "Mira", "Tobias", "Halim", "Jorunn",
|
||||
"Pia", "Anselm", "Ceyda", "Rune", "Malte", "Ida", "Vito", "Nour", "Greta", "Kasper"]
|
||||
static var NACHNAMEN: Array = ["Vogt", "Keller", "Sahin", "Frank", "Berg", "Ostermann",
|
||||
"Ruiz", "Lind", "Aslan", "Weiss", "Petit", "Novak"]
|
||||
static var BERUFE: Array = ["Illustratorin", "Bäcker", "Studentin", "Sachbearbeiter",
|
||||
"Pfleger", "Tischlerin", "Lehrer", "Programmiererin", "Fahrer", "Archivarin",
|
||||
"Köchin", "Musiker"]
|
||||
|
||||
## Anliegen sind echte Klemmen, keine erzeugten Sätze — sie tragen den Sinn
|
||||
## des Spiels. Erzeugt werden Person, Wohnort und Tagesablauf drumherum.
|
||||
static var ANLIEGEN: Array = [
|
||||
{
|
||||
"id": "lena",
|
||||
"name": "Lena Vogt",
|
||||
"alter": 29,
|
||||
"beruf": "Illustratorin",
|
||||
"traum": "Ihr Bilderbuch endlich zu Ende zeichnen.",
|
||||
"problem": "Sie schiebt die letzten zwölf Seiten seit acht Monaten vor sich her. Jeder Entwurf kommt ihr zu schlecht vor, also fängt sie lieber gar nicht erst an.",
|
||||
"tags": ["prokrastination", "selbstzweifel", "ablenkung"],
|
||||
"mission": true,
|
||||
"farbe": Color(0.85, 0.45, 0.55),
|
||||
"routine": [
|
||||
[0, "haus_lena", "schlaeft"],
|
||||
[7, "haus_lena", "morgenroutine"],
|
||||
[8, "cafe", "essen"],
|
||||
[10, "haus_lena", "zoegert"],
|
||||
[12, "haus_lena", "arbeitet"],
|
||||
[13, "park", "pause"],
|
||||
[15, "haus_lena", "arbeitet"],
|
||||
[18, "park", "unterwegs"],
|
||||
[20, "haus_lena", "aufraeumen"],
|
||||
[21, "haus_lena", "gruebelt"],
|
||||
[22, "haus_lena", "abendroutine"],
|
||||
[23, "haus_lena", "schlaeft"],
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "bruno",
|
||||
"name": "Bruno Keller",
|
||||
"alter": 54,
|
||||
"beruf": "Bäcker",
|
||||
"traum": "Wieder Klavier spielen wie mit zwanzig.",
|
||||
"problem": "Das Klavier steht zugestellt im Flur. Nach der Frühschicht ist er zu müde, um auch nur den Deckel zu öffnen.",
|
||||
"tags": ["gewohnheit", "erschoepfung", "ablenkung"],
|
||||
"mission": true,
|
||||
"farbe": Color(0.55, 0.45, 0.30),
|
||||
"routine": [
|
||||
[0, "haus_bruno", "schlaeft"],
|
||||
[3, "baeckerei", "arbeitet"],
|
||||
[10, "cafe", "pause"],
|
||||
[12, "haus_bruno", "essen"],
|
||||
[13, "haus_bruno", "ruht"],
|
||||
[15, "haus_bruno", "zoegert"],
|
||||
[16, "park", "unterwegs"],
|
||||
[18, "haus_bruno", "muede"],
|
||||
[20, "haus_bruno", "abendroutine"],
|
||||
[21, "haus_bruno", "schlaeft"],
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "mira",
|
||||
"name": "Mira Sahin",
|
||||
"alter": 22,
|
||||
"beruf": "Studentin",
|
||||
"traum": "Ihre Abschlussarbeit vortragen, ohne zu erstarren.",
|
||||
"traum": "Die Abschlussarbeit vortragen, ohne zu erstarren.",
|
||||
"problem": "Die Arbeit ist längst fertig. Aber vor jedem Vortrag wird ihr schwarz vor Augen, und sie meldet sich wieder ab.",
|
||||
"tags": ["angst", "ueberforderung", "selbstzweifel"],
|
||||
"mission": true,
|
||||
"farbe": Color(0.35, 0.60, 0.85),
|
||||
"routine": [
|
||||
[0, "haus_mira", "schlaeft"],
|
||||
[8, "haus_mira", "morgenroutine"],
|
||||
[9, "uni", "arbeitet"],
|
||||
[13, "cafe", "angespannt"],
|
||||
[15, "uni", "arbeitet"],
|
||||
[17, "uni", "wartet"],
|
||||
[18, "haus_mira", "gruebelt"],
|
||||
[20, "park", "unterwegs"],
|
||||
[21, "haus_mira", "ruht"],
|
||||
[22, "haus_mira", "abendroutine"],
|
||||
[23, "haus_mira", "schlaeft"],
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "tobias",
|
||||
"name": "Tobias Frank",
|
||||
"alter": 41,
|
||||
"beruf": "Sachbearbeiter",
|
||||
"traum": "Abends wieder Zeit für seine Tochter haben.",
|
||||
"traum": "Abends wieder Zeit für die Tochter haben.",
|
||||
"problem": "Er sagt zu jeder Bitte ja und sitzt dafür jeden Abend bis acht im Büro. Zu Hause ist er nur noch müde.",
|
||||
"tags": ["stress", "grenzen", "erschoepfung"],
|
||||
"mission": true,
|
||||
"farbe": Color(0.40, 0.55, 0.45),
|
||||
"routine": [
|
||||
[0, "haus_tobias", "schlaeft"],
|
||||
[6, "haus_tobias", "morgenroutine"],
|
||||
[7, "buero", "arbeitet"],
|
||||
[12, "cafe", "essen"],
|
||||
[14, "buero", "arbeitet"],
|
||||
[19, "buero", "angespannt"],
|
||||
[20, "haus_tobias", "essen"],
|
||||
[21, "haus_tobias", "muede"],
|
||||
[23, "haus_tobias", "abendroutine"],
|
||||
],
|
||||
},
|
||||
{
|
||||
"traum": "Endlich wieder durchschlafen.",
|
||||
"problem": "Der Feierabend franst aus, das Handy leuchtet bis zwei. Morgens steht er zerschlagen auf und trinkt sich wach.",
|
||||
"tags": ["erschoepfung", "gesundheit", "gewohnheit"],
|
||||
},
|
||||
{
|
||||
"traum": "Den alten Freundeskreis zurückholen.",
|
||||
"problem": "Sie wartet seit Monaten, dass sich jemand meldet, und meldet sich selbst nie. Jede Einladung erscheint ihr aufdringlich.",
|
||||
"tags": ["einsamkeit", "selbstzweifel"],
|
||||
},
|
||||
{
|
||||
"traum": "Die Werkstatt in der Garage wieder aufmachen.",
|
||||
"problem": "Alles liegt bereit, aber der erste Handgriff wirkt jedes Mal wie ein Berg. Am Ende räumt er lieber auf.",
|
||||
"tags": ["prokrastination", "ueberforderung"],
|
||||
},
|
||||
{
|
||||
"traum": "Vor Publikum sprechen, ohne dass die Hände zittern.",
|
||||
"problem": "Schon Tage vorher liegt ihr der Termin im Magen. Kurz davor wird die Luft knapp und der Kopf leer.",
|
||||
"tags": ["angst", "stress"],
|
||||
},
|
||||
{
|
||||
"traum": "Nach dem Rückschlag noch einmal anfangen.",
|
||||
"problem": "Seit die Prüfung schiefging, redet sie mit sich, wie sie mit niemandem reden würde. Jeder neue Versuch fühlt sich sinnlos an.",
|
||||
"tags": ["rueckschlag", "selbstzweifel"],
|
||||
},
|
||||
{
|
||||
"traum": "Drei Abende in der Woche für sich behalten.",
|
||||
"problem": "Der Kalender füllt sich mit fremden Anliegen, weil ein Nein unhöflich wirkt. Am Ende bleibt nichts übrig.",
|
||||
"tags": ["grenzen", "stress", "ueberforderung"],
|
||||
},
|
||||
]
|
||||
|
||||
## Tagesabläufe: Platzhalter werden durch Wohnung und Arbeitsplatz ersetzt.
|
||||
## Tagesabläufe: Platzhalter werden durch Wohnung und Arbeitsplatz ersetzt.
|
||||
## Die Reihenfolge passt zu den Anliegen — wer aufschiebt, arbeitet zu Hause.
|
||||
static var ABLAEUFE: Array = [
|
||||
[[0, "heim", "schlaeft"], [7, "heim", "zoegert"], [10, "heim", "arbeitet"],
|
||||
[13, "park", "pause"], [15, "heim", "arbeitet"], [18, "cafe", "wartet"],
|
||||
[20, "heim", "zoegert"], [22, "heim", "gruebelt"], [23, "heim", "abendroutine"]],
|
||||
[[0, "heim", "schlaeft"], [4, "arbeit", "arbeitet"], [10, "cafe", "pause"],
|
||||
[12, "heim", "essen"], [14, "heim", "ruht"], [16, "heim", "zoegert"],
|
||||
[18, "park", "unterwegs"], [20, "heim", "muede"], [22, "heim", "abendroutine"]],
|
||||
[[0, "heim", "schlaeft"], [8, "heim", "morgenroutine"], [10, "arbeit", "arbeitet"],
|
||||
[13, "cafe", "angespannt"], [15, "arbeit", "wartet"], [18, "heim", "gruebelt"],
|
||||
[20, "park", "unterwegs"], [22, "heim", "abendroutine"]],
|
||||
[[0, "heim", "schlaeft"], [6, "heim", "morgenroutine"], [8, "arbeit", "arbeitet"],
|
||||
[12, "cafe", "essen"], [14, "arbeit", "arbeitet"], [19, "arbeit", "angespannt"],
|
||||
[21, "heim", "essen"], [23, "heim", "muede"]],
|
||||
[[0, "heim", "schlaeft"], [7, "heim", "morgenroutine"], [9, "arbeit", "arbeitet"],
|
||||
[12, "cafe", "essen"], [14, "arbeit", "arbeitet"], [17, "park", "unterwegs"],
|
||||
[19, "heim", "aufraeumen"], [21, "heim", "gruebelt"], [23, "heim", "abendroutine"]],
|
||||
]
|
||||
|
||||
|
||||
static func heim_key(i: int) -> String:
|
||||
return "heim_%d" % i
|
||||
|
||||
|
||||
## Baut Wohnhäuser und Menschen für so viele Bewohner neu auf.
|
||||
static func erzeuge(menschen: int) -> void:
|
||||
PLACES = FEST.duplicate(true)
|
||||
LIST = []
|
||||
for i in range(maxi(menschen, 0)):
|
||||
PLACES[heim_key(i)] = _heim(i)
|
||||
LIST.append(_mensch(i))
|
||||
|
||||
|
||||
static func _heim(i: int) -> Dictionary:
|
||||
var ring := i / HAEUSER_JE_RING
|
||||
var winkel := float(i % HAEUSER_JE_RING) * TAU / float(HAEUSER_JE_RING) + 0.39
|
||||
var r := RING_ERSTER + float(ring) * RING_ABSTAND
|
||||
var x := sin(winkel) * r
|
||||
var z := cos(winkel) * r
|
||||
return {
|
||||
"name": "%s %s" % [_nachname(i), "Weg %d" % (i + 1)],
|
||||
"pos": [x, z],
|
||||
"size": [12.0 + float(i % 3) * 2.0, 10.0],
|
||||
"dir": _tuer_zur_mitte(x, z),
|
||||
"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:
|
||||
if absf(x) > absf(z):
|
||||
return "left" if x > 0.0 else "right"
|
||||
return "forward" if z > 0.0 else "back"
|
||||
|
||||
|
||||
static func _mensch(i: int) -> Dictionary:
|
||||
var anliegen: Dictionary = ANLIEGEN[i % ANLIEGEN.size()]
|
||||
var arbeit: String = str(ARBEIT[i % ARBEIT.size()])
|
||||
var heim := heim_key(i)
|
||||
var ablauf: Array = []
|
||||
for e in ABLAEUFE[i % ABLAEUFE.size()]:
|
||||
var ort: String = str(e[1])
|
||||
if ort == "heim":
|
||||
ort = heim
|
||||
elif ort == "arbeit":
|
||||
ort = arbeit
|
||||
ablauf.append([e[0], ort, e[2]])
|
||||
return {
|
||||
"id": "mensch_%d" % i,
|
||||
"name": "%s %s" % [_vorname(i), _nachname(i)],
|
||||
"alter": 22 + (i * 7) % 38,
|
||||
"beruf": str(BERUFE[i % BERUFE.size()]),
|
||||
"traum": str(anliegen["traum"]),
|
||||
"problem": str(anliegen["problem"]),
|
||||
"tags": (anliegen["tags"] as Array).duplicate(),
|
||||
"mission": true,
|
||||
"heim": heim,
|
||||
"farbe": Color.from_hsv(fmod(0.02 + float(i) * 0.191, 1.0), 0.5, 0.8),
|
||||
"routine": ablauf,
|
||||
}
|
||||
|
||||
|
||||
static func _vorname(i: int) -> String:
|
||||
return str(VORNAMEN[i % VORNAMEN.size()])
|
||||
|
||||
|
||||
static func _nachname(i: int) -> String:
|
||||
return str(NACHNAMEN[(i / VORNAMEN.size() + i) % NACHNAMEN.size()])
|
||||
|
||||
|
||||
## Die Tätigkeiten eines Menschen in der Reihenfolge seines Tages, ohne Dopplung.
|
||||
static func aktivitaeten(id: String) -> Array:
|
||||
var out: Array = []
|
||||
for e in by_id(id)["routine"]:
|
||||
if not out.has(e[2]):
|
||||
out.append(e[2])
|
||||
return out
|
||||
|
||||
|
||||
static func by_id(id: String) -> Dictionary:
|
||||
for h in LIST:
|
||||
|
||||
@@ -31,7 +31,7 @@ func _ready() -> void:
|
||||
Color(0.8, 0.95, 1)))
|
||||
|
||||
_traumwelt()
|
||||
ui.intro_panel(func() -> void: pass)
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
|
||||
func _traumwelt() -> void:
|
||||
@@ -109,6 +109,10 @@ func _hinweis_setzen() -> void:
|
||||
ui.setze_hinweis("[E] Zur Erde reisen")
|
||||
"traum":
|
||||
ui.setze_hinweis("[E] Zurück in die Traumwelt")
|
||||
"lehre":
|
||||
ui.setze_hinweis("[E] %s — Techniken lernen" % titel)
|
||||
"brett":
|
||||
ui.setze_hinweis("[E] Schwarzes Brett — Mission annehmen")
|
||||
_:
|
||||
ui.setze_hinweis("[E] %s" % titel)
|
||||
else:
|
||||
@@ -148,8 +152,13 @@ func _interagieren() -> void:
|
||||
"erde":
|
||||
if Game.active_mission == "" and Game.completed.size() < HumanDB.LIST.size():
|
||||
Game.notified.emit(
|
||||
"Das Tor öffnet sich nur mit einer angenommenen Mission.", Color(1, 0.8, 0.5))
|
||||
"Das Tor öffnet sich nur mit einer Mission. Sieh am Schwarzen Brett nach.",
|
||||
Color(1, 0.8, 0.5))
|
||||
else:
|
||||
_erde()
|
||||
"traum":
|
||||
_traumwelt()
|
||||
"lehre":
|
||||
ui.lehre_panel(str(_fokus.get_meta("arg")))
|
||||
"brett":
|
||||
ui.brett_panel()
|
||||
|
||||
236
scripts/ui.gd
236
scripts/ui.gd
@@ -133,6 +133,7 @@ func _process(_delta: float) -> void:
|
||||
var wunsch := _content.get_combined_minimum_size().y
|
||||
var platz := get_viewport().get_visible_rect().size.y - 220.0
|
||||
_scroll.custom_minimum_size.y = clampf(wunsch, 120.0, platz)
|
||||
_panel.reset_size() # sonst behält der Rahmen die Höhe des Panels davor
|
||||
if _scroll_frames > 0:
|
||||
_scroll.scroll_vertical = _scroll_wunsch
|
||||
_scroll_frames -= 1
|
||||
@@ -148,13 +149,14 @@ func _hud_aktualisieren() -> void:
|
||||
[Game.einsicht, Game.known.size(), TechDB.LIST.size()])
|
||||
if Game.active_mission != "":
|
||||
var h := HumanDB.by_id(Game.active_mission)
|
||||
zeilen.append("Mission: [color=#ffdd77]%s[/color] — %d %%" %
|
||||
[h["name"], int(Game.mission_progress(Game.active_mission))])
|
||||
zeilen.append("Mission: [color=#ffdd77]%s[/color] — %d %% · %d Regeln" %
|
||||
[h["name"], int(Game.mission_progress(Game.active_mission)),
|
||||
Game.regel_anzahl(Game.active_mission)])
|
||||
zeilen.append("[color=#aaaaaa]%s[/color]" % h["traum"])
|
||||
elif Game.completed.size() >= HumanDB.LIST.size():
|
||||
zeilen.append("[color=#88ff99]Alle Träume erfüllt.[/color]")
|
||||
else:
|
||||
zeilen.append("[color=#aaaaaa]Keine Mission. Journal öffnen mit J.[/color]")
|
||||
zeilen.append("[color=#aaaaaa]Keine Mission. Schwarzes Brett am Dorfplatz.[/color]")
|
||||
_hud.text = "\n".join(zeilen)
|
||||
|
||||
|
||||
@@ -250,8 +252,9 @@ func _hinweis_block(t: Dictionary) -> String:
|
||||
|
||||
# -------------------------------------------------------- Journal
|
||||
|
||||
## Das Journal ist immer dabei. Hier werden Techniken gelernt und vertieft
|
||||
## und Missionen angenommen.
|
||||
## Das Journal ist immer dabei und führt Buch: welche Techniken du gelernt hast
|
||||
## und welche Missionen du angenommen hast. Gelehrt wird im Hörsaal,
|
||||
## vergeben werden die Missionen am Schwarzen Brett.
|
||||
func journal_panel(seite: String = "") -> void:
|
||||
if seite != "":
|
||||
_journal_seite = seite
|
||||
@@ -271,7 +274,11 @@ func _reiter() -> void:
|
||||
var hb := HBoxContainer.new()
|
||||
hb.add_theme_constant_override("separation", 8)
|
||||
_content.add_child(hb)
|
||||
for eintrag in [["techniken", "Techniken"], ["missionen", "Missionen"]]:
|
||||
var seiten := [
|
||||
["techniken", "Techniken (%d)" % Game.known.size()],
|
||||
["missionen", "Missionen (%d)" % Game.progress.size()],
|
||||
]
|
||||
for eintrag in seiten:
|
||||
var b := Button.new()
|
||||
b.text = eintrag[1]
|
||||
b.custom_minimum_size = Vector2(0, 36)
|
||||
@@ -293,10 +300,153 @@ func _reiter() -> void:
|
||||
|
||||
|
||||
func _journal_techniken() -> void:
|
||||
_text("Zwölf Wege, die Menschen wirklich weiterbringen. Jede Anwendung " +
|
||||
"draußen macht eine Technik von allein besser. Vertiefen geht auch hier.", 15)
|
||||
if Game.known.is_empty():
|
||||
_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)
|
||||
return
|
||||
_text("Was du gelernt hast. Jede Anwendung draußen macht eine Technik von allein " +
|
||||
"besser; vertieft wird im Hörsaal.", 15)
|
||||
_trenner()
|
||||
for id in TechDB.ids():
|
||||
if not Game.knows(id):
|
||||
continue
|
||||
var t := TechDB.by_id(id)
|
||||
_text("[b][color=#ffe3a0]%s[/color][/b] · Stufe %d (%s)" %
|
||||
[t["name"], Game.skill_of(id), Game.xp_to_next(id)], 18)
|
||||
_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()
|
||||
|
||||
|
||||
func _journal_missionen() -> void:
|
||||
if Game.progress.is_empty():
|
||||
_text("[color=#ffcc66]Du hast noch keine Mission angenommen.[/color]\n" +
|
||||
"Am [b]Schwarzen Brett[/b] am Dorfplatz hängen die Menschen aus, " +
|
||||
"die nicht weiterkommen.", 16)
|
||||
return
|
||||
_text("Die Menschen, für die du dich entschieden hast.", 15)
|
||||
_trenner()
|
||||
for h in HumanDB.LIST:
|
||||
var id: String = h["id"]
|
||||
if not Game.angenommen(id):
|
||||
continue
|
||||
_text("[b][color=#ffe3a0]%s[/color][/b], %d — %s" %
|
||||
[h["name"], h["alter"], h["beruf"]], 18)
|
||||
_text("[b]Traum:[/b] %s" % h["traum"], 16)
|
||||
_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):
|
||||
_text("[color=#88ff99]★ Der Traum ist erfüllt.[/color]", 15)
|
||||
elif Game.active_mission == id:
|
||||
_text("[color=#ffdd77]Aktive Mission — %d %%[/color]" %
|
||||
int(Game.mission_progress(id)), 15)
|
||||
else:
|
||||
_text("[color=#aaaaaa]Zurückgestellt — %d %%[/color]" %
|
||||
int(Game.mission_progress(id)), 15)
|
||||
if not Game.completed.has(id):
|
||||
_knopf("Regelwerk — %d Regeln" % Game.regel_anzahl(id),
|
||||
func() -> void: pipeline_panel(id))
|
||||
_trenner()
|
||||
|
||||
|
||||
# -------------------------------------------------------- Regelwerk einer Mission
|
||||
|
||||
## Wenn-Dann-Regeln: tut der Mensch etwas Bestimmtes, greift die zugeordnete
|
||||
## Technik von allein. So automatisiert der Spieler den Fortschritt.
|
||||
func pipeline_panel(human_id: String) -> void:
|
||||
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "pipeline" else 0
|
||||
var h := HumanDB.by_id(human_id)
|
||||
_leeren("Regelwerk — %s" % 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)
|
||||
_text("[color=#aaaaaa]Fortschritt: [b]%d %%[/b] · Regeln: [b]%d[/b][/color]" %
|
||||
[int(Game.mission_progress(human_id)), Game.regel_anzahl(human_id)], 15)
|
||||
_trenner()
|
||||
for activity in HumanDB.aktivitaeten(human_id):
|
||||
var tat := Orte.text(activity)
|
||||
if not Game.erreichbar(activity):
|
||||
_knopf("%s → unerreichbar" % tat, func() -> void: pass, false)
|
||||
continue
|
||||
var tech: String = Game.regel_fuer(human_id, activity)
|
||||
var beschriftung := "%s → keine Regel" % tat
|
||||
if tech != "":
|
||||
var ev := Game.evaluate(tech, human_id, activity, false)
|
||||
beschriftung = "%s → %s · %d %%" % [tat, TechDB.by_id(tech)["name"],
|
||||
int(round(float(ev["chance"]) * 100.0))]
|
||||
_knopf(beschriftung, func() -> void: regel_panel(human_id, activity))
|
||||
_trenner()
|
||||
_knopf("Zurück zum Journal", func() -> void: journal_panel("missionen"))
|
||||
_zeigen("pipeline")
|
||||
_scroll_wunsch = scroll_alt
|
||||
_scroll_frames = 4
|
||||
|
||||
|
||||
## Welche Technik soll bei dieser Tätigkeit greifen?
|
||||
func regel_panel(human_id: String, activity: String) -> void:
|
||||
var h := HumanDB.by_id(human_id)
|
||||
_leeren("Regel für »%s«" % Orte.text(activity))
|
||||
if Game.known.is_empty():
|
||||
_text("[color=#ffcc66]Du kennst keine Technik. Im Hörsaal der Wege " +
|
||||
"werden sie gelehrt.[/color]", 16)
|
||||
_knopf("Zurück", func() -> void: pipeline_panel(human_id))
|
||||
_zeigen("regel")
|
||||
return
|
||||
_text("Was soll greifen, wenn %s %s? Die Erfolgschance gilt für genau diesen Moment. " %
|
||||
[h["name"], Orte.text(activity)] +
|
||||
"Jede Technik hält nur einen Posten — sie hier einzusetzen nimmt sie anderswo weg.", 15)
|
||||
_trenner()
|
||||
for id in TechDB.ids():
|
||||
if not Game.knows(id):
|
||||
continue
|
||||
var t := TechDB.by_id(id)
|
||||
var ev := Game.evaluate(id, human_id, activity, false)
|
||||
var marker: Array = []
|
||||
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 haengt := Game.regel_moment(human_id, id)
|
||||
if haengt == activity:
|
||||
gewaehlt = " [color=#ffdd77](hier eingestellt)[/color]"
|
||||
elif haengt != "":
|
||||
gewaehlt = " [color=#ffaa66](hängt an »%s«)[/color]" % Orte.text(haengt)
|
||||
_text("[b]%s[/b] · Stufe %d%s\n[color=#cccccc]%s[/color]\n%s" %
|
||||
[t["name"], Game.skill_of(id), gewaehlt, t["wann"], " · ".join(marker)], 16)
|
||||
var beschriftung := "Blockiert" if ev["blocked"] else \
|
||||
"Diese Regel — %d %% Erfolgschance" % int(round(float(ev["chance"]) * 100.0))
|
||||
_knopf(beschriftung, func() -> void:
|
||||
Game.regel_setzen(human_id, activity, id)
|
||||
pipeline_panel(human_id), not ev["blocked"])
|
||||
_trenner()
|
||||
_knopf("Keine Regel für diese Tätigkeit", func() -> void:
|
||||
Game.regel_setzen(human_id, activity, "")
|
||||
pipeline_panel(human_id))
|
||||
_zeigen("regel")
|
||||
|
||||
|
||||
# -------------------------------------------------------- Lehrorte
|
||||
|
||||
## Jeder Lehrort unterrichtet genau einen Weg: der Hörsaal den ersten, jedes
|
||||
## Haus den, den sein Bewohner mitgebracht hat.
|
||||
func lehre_panel(ort: String = "hoersaal") -> void:
|
||||
if not DreamDB.PLACES.has(ort):
|
||||
return
|
||||
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "lehre" else 0
|
||||
var pl: Dictionary = DreamDB.PLACES[ort]
|
||||
var ids: Array = DreamDB.techniken_von(ort)
|
||||
_leeren(str(pl["name"]))
|
||||
_text("[color=#aaaaaa]Einsicht: [b]%d[/b] · Gelernt: [b]%d / %d[/b] von %d entdeckten[/color]" %
|
||||
[Game.einsicht, Game.known.size(), TechDB.LIST.size(), Game.offene_techniken()], 15)
|
||||
_trenner()
|
||||
if ids.is_empty():
|
||||
_text("[color=#ffcc66]Hier wird nichts gelehrt.[/color]", 16)
|
||||
for id in ids:
|
||||
var t := TechDB.by_id(id)
|
||||
if Game.knows(id):
|
||||
_text("[b][color=#ffe3a0]%s[/color][/b] · Stufe %d (%s)" %
|
||||
@@ -309,16 +459,38 @@ func _journal_techniken() -> void:
|
||||
if Game.knows(id):
|
||||
_knopf("Vertiefen (1 Einsicht)", func() -> void:
|
||||
if Game.deepen(id):
|
||||
journal_panel(), Game.einsicht >= 1)
|
||||
lehre_panel(ort), Game.einsicht >= 1)
|
||||
else:
|
||||
_knopf("Lernen (1 Einsicht)", func() -> void:
|
||||
if Game.learn(id):
|
||||
journal_panel(), Game.einsicht >= 1)
|
||||
lehre_panel(ort), Game.einsicht >= 1)
|
||||
_trenner()
|
||||
_naechster_weg()
|
||||
_zeigen("lehre")
|
||||
_scroll_wunsch = scroll_alt
|
||||
_scroll_frames = 4
|
||||
|
||||
|
||||
func _journal_missionen() -> void:
|
||||
_text("Menschen, deren Traum feststeckt.", 15)
|
||||
## Was als Nächstes ins Dorf käme.
|
||||
func _naechster_weg() -> void:
|
||||
var offen_zahl := Game.offene_techniken()
|
||||
if offen_zahl >= TechDB.LIST.size():
|
||||
_text("[color=#88ff99]Alle zwölf Wege sind entdeckt. Das Dorf wächst weiter.[/color]", 15)
|
||||
return
|
||||
var naechste: Dictionary = TechDB.LIST[offen_zahl]
|
||||
_text("[color=#aaaaaa]Noch unentdeckt: [b]%s[/b] und %d weitere. Jeder erfüllte Traum " %
|
||||
[naechste["name"], TechDB.LIST.size() - offen_zahl - 1] +
|
||||
"bringt einen Bewohner ins Dorf, und er lehrt einen neuen Weg.[/color]", 15)
|
||||
|
||||
|
||||
# -------------------------------------------------------- Schwarzes Brett
|
||||
|
||||
## Hier hängen die Menschen aus, deren Traum feststeckt.
|
||||
func brett_panel() -> void:
|
||||
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "brett" else 0
|
||||
_leeren("Schwarzes Brett")
|
||||
_text("Menschen, deren Traum feststeckt. Nimm einen an — das Tor zur Erde " +
|
||||
"öffnet sich nur mit einer Mission.", 15)
|
||||
_trenner()
|
||||
for h in HumanDB.LIST:
|
||||
var id: String = h["id"]
|
||||
@@ -333,11 +505,16 @@ func _journal_missionen() -> void:
|
||||
_text("[color=#ffdd77]Aktive Mission — %d %%[/color]" %
|
||||
int(Game.mission_progress(id)), 15)
|
||||
else:
|
||||
_text("Fortschritt: %d %%" % int(Game.mission_progress(id)), 15)
|
||||
if Game.angenommen(id):
|
||||
_text("[color=#aaaaaa]Schon einmal angenommen — %d %%[/color]" %
|
||||
int(Game.mission_progress(id)), 15)
|
||||
_knopf("Mission annehmen", func() -> void:
|
||||
Game.accept_mission(id)
|
||||
journal_panel())
|
||||
brett_panel())
|
||||
_trenner()
|
||||
_zeigen("brett")
|
||||
_scroll_wunsch = scroll_alt
|
||||
_scroll_frames = 4
|
||||
|
||||
|
||||
# -------------------------------------------------------- Anwenden auf der Erde
|
||||
@@ -411,37 +588,6 @@ func _anwenden(id: String) -> void:
|
||||
[t["name"], int(round(float(res["chance"]) * 100.0))], Color(1, 0.6, 0.5))
|
||||
|
||||
|
||||
# -------------------------------------------------------- Intro
|
||||
|
||||
func intro_panel(on_close: Callable) -> void:
|
||||
_leeren("Traumwandler")
|
||||
_text("Du bist ein Wesen aus der Traumwelt. Menschen tragen Träume mit sich herum, " +
|
||||
"die sie allein nicht bewegt bekommen. Du wirst zu ihnen geschickt.")
|
||||
_trenner()
|
||||
_text("[b]Dein Journal — Taste J[/b]\n" +
|
||||
"· Es ist immer dabei, in beiden Welten.\n" +
|
||||
"· [b]Techniken[/b] — lerne echte Lösungswege. Jede sagt dir, was sie tut, " +
|
||||
"wann sie passt und warum sie hilft. Anwenden macht sie von allein besser.\n" +
|
||||
"· [b]Missionen[/b] — nimm einen Menschen an, dem du helfen willst.")
|
||||
_text("[b]In der Traumwelt[/b]\n" +
|
||||
"· Ein Dorf auf einer schwebenden Insel. Vier Wesen wohnen hier, jedes in " +
|
||||
"seinem Haus, jedes mit eigenem Tagesablauf. Die Häuser stehen offen.\n" +
|
||||
"· [b]Tor zur Erde[/b] — reise hinab, sobald du eine Mission hast.")
|
||||
_text("[b]Auf der Erde[/b]\n" +
|
||||
"· Du bist ein unsichtbarer Geist und schwebst durch Wände. Leertaste hoch, Umschalt runter.\n" +
|
||||
"· Die Menschen leben ihren Tagesablauf: schlafen, arbeiten, grübeln, spazieren.\n" +
|
||||
"· Geh nah heran und drücke [b]E[/b]. Deine Erfolgschance hängt von deiner Stufe ab — " +
|
||||
"und davon, ob Technik und Moment zum Menschen passen.\n" +
|
||||
"· Schlafende erreichst du nicht. Wer abblockt, braucht eine Weile Ruhe.")
|
||||
_trenner()
|
||||
_text("[color=#aaaaaa]Du beginnst mit 3 Einsicht. Jede Technik kostet 1. " +
|
||||
"Ein erfüllter Traum bringt 3 zurück.[/color]", 15)
|
||||
_knopf("Losgehen", func() -> void:
|
||||
schliessen()
|
||||
on_close.call())
|
||||
_zeigen("intro")
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if offen and event.is_action_pressed("ui_cancel"):
|
||||
schliessen()
|
||||
|
||||
202
tests/shots.gd
202
tests/shots.gd
@@ -13,66 +13,90 @@ func _process(_d: float) -> void:
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
seed(20240)
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(OUT))
|
||||
main = load("res://scenes/main.tscn").instantiate()
|
||||
add_child(main)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
await knipse("01_intro")
|
||||
await knipse("01_start")
|
||||
|
||||
main.ui.schliessen()
|
||||
main.player.global_position = Vector3(0, 4.0, 34.0)
|
||||
main.player.global_position = Vector3(0, 6.0, 24.0) # vor dem Hörsaal, Blick ins Dorf
|
||||
main.player.yaw = 0.0
|
||||
main.player.pitch = -0.14
|
||||
main.player.pitch = -0.35
|
||||
Game.time_h = 10.0
|
||||
await warte(30)
|
||||
await knipse("02_traumwelt")
|
||||
|
||||
main.player.global_position = Vector3(0, 12.0, 30.0)
|
||||
main.player.pitch = -0.5
|
||||
main.player.global_position = Vector3(0, 32.0, 14.0) # über der Lichtung
|
||||
main.player.yaw = 0.0
|
||||
main.player.pitch = -0.78
|
||||
await warte(20)
|
||||
await knipse("03_traumdorf_ueberblick")
|
||||
|
||||
var haus := Orte.center(DreamDB.PLACES, "haus_ailen")
|
||||
main.player.global_position = haus + Vector3(0, 2.0, 12.0)
|
||||
main.player.yaw = 0.0
|
||||
main.player.pitch = -0.18
|
||||
# Der Hörsaal ist zu Beginn das einzige Gebäude auf der Insel
|
||||
var saal_aussen := Orte.center(DreamDB.PLACES, "hoersaal")
|
||||
main.player.global_position = saal_aussen + Vector3(0, 2.0, -14.0)
|
||||
main.player.yaw = PI
|
||||
main.player.pitch = -0.12
|
||||
await warte(20)
|
||||
await knipse("04_haus_aussen")
|
||||
await knipse("04_hoersaal_aussen")
|
||||
|
||||
# ein Wesen in seinem Haus
|
||||
var ailen: Bewohner = null
|
||||
for w in main.welt.bewohner:
|
||||
if w.data["id"] == "ailen":
|
||||
ailen = w
|
||||
for i in range(900):
|
||||
main._process(0.05)
|
||||
for k in main.welt.get_children():
|
||||
if k is Bewohner:
|
||||
k._process(0.05)
|
||||
Game.time_h = 10.0
|
||||
if ailen.place == "haus_ailen" and ailen._path.is_empty():
|
||||
break
|
||||
main.player.global_position = ailen.global_position + Vector3(3.0, 0.6, 1.2)
|
||||
main.player.yaw = -PI * 0.32
|
||||
main.player.pitch = -0.06
|
||||
# Wo später die Häuser stehen, ist noch nichts
|
||||
main.player.global_position = Vector3(-18.0, 8.0, -6.0)
|
||||
main.player.yaw = -0.9
|
||||
main.player.pitch = -0.30
|
||||
await warte(20)
|
||||
await knipse("05_haus_innen")
|
||||
await knipse("05_insel_ohne_haeuser")
|
||||
|
||||
# Hörsaal der Wege: hier werden die Techniken gelehrt
|
||||
var saal := Orte.center(DreamDB.PLACES, "hoersaal")
|
||||
main.player.global_position = saal + Vector3(0, 1.0, -1.0)
|
||||
main.player.yaw = PI
|
||||
main.player.pitch = -0.05
|
||||
await warte(20)
|
||||
await knipse("06_hoersaal")
|
||||
|
||||
main.ui.lehre_panel()
|
||||
await warte(6)
|
||||
await knipse("07_techniken_lernen")
|
||||
main.ui.schliessen()
|
||||
|
||||
Game.learn(str(DreamDB.techniken_von("hoersaal")[0]))
|
||||
|
||||
main.ui.journal_panel("techniken")
|
||||
await warte(6)
|
||||
await knipse("06_journal_techniken")
|
||||
await knipse("08_journal_techniken")
|
||||
main.ui.schliessen()
|
||||
|
||||
Game.learn("pomodoro")
|
||||
Game.learn("zerlegen")
|
||||
Game.learn("box_atmung")
|
||||
# Schwarzes Brett an der Lichtung
|
||||
var brett := Orte.center(DreamDB.PLACES, "lichtung") + Vector3(-6.0, 0, 9.0)
|
||||
main.player.global_position = brett + Vector3(0, 1.0, 4.0)
|
||||
main.player.yaw = 0.0
|
||||
main.player.pitch = -0.02
|
||||
await warte(20)
|
||||
await knipse("09_schwarzes_brett")
|
||||
|
||||
main.ui.brett_panel()
|
||||
await warte(6)
|
||||
await knipse("10_missionen")
|
||||
main.ui.schliessen()
|
||||
Game.accept_mission("mensch_0")
|
||||
|
||||
main.ui.journal_panel("missionen")
|
||||
await warte(6)
|
||||
await knipse("07_journal_missionen")
|
||||
await knipse("11_journal_missionen")
|
||||
|
||||
# Regelwerk: Wenn-Dann-Ketten, die den Fortschritt automatisieren
|
||||
Game.regel_setzen("mensch_0", "zoegert", str(DreamDB.techniken_von("hoersaal")[0]))
|
||||
main.ui.pipeline_panel("mensch_0")
|
||||
await warte(6)
|
||||
await knipse("12_regelwerk")
|
||||
|
||||
main.ui.regel_panel("mensch_0", "arbeitet")
|
||||
await warte(6)
|
||||
await knipse("13_regel_waehlen")
|
||||
main.ui.schliessen()
|
||||
Game.accept_mission("lena")
|
||||
|
||||
# Lichtung, wenn sich die Wesen dort treffen
|
||||
for i in range(1200):
|
||||
@@ -85,14 +109,14 @@ func _ready() -> void:
|
||||
main.player.yaw = 0.0
|
||||
main.player.pitch = -0.20
|
||||
await warte(20)
|
||||
await knipse("08_lichtung")
|
||||
await knipse("14_dorfplatz_leer")
|
||||
|
||||
var tor := Orte.center(DreamDB.PLACES, "tor")
|
||||
main.player.global_position = tor + Vector3(0, 2.5, 14.0)
|
||||
main.player.yaw = 0.0
|
||||
main.player.pitch = -0.12
|
||||
await warte(20)
|
||||
await knipse("09_tor_zur_erde")
|
||||
await knipse("15_tor_zur_erde")
|
||||
|
||||
main._erde()
|
||||
Game.time_h = 10.5
|
||||
@@ -101,11 +125,11 @@ func _ready() -> void:
|
||||
main.player.pitch = -0.62
|
||||
main.player.yaw = 0.0
|
||||
await warte(20)
|
||||
await knipse("10_erde_uebersicht")
|
||||
await knipse("16_erde_uebersicht")
|
||||
|
||||
var lena: Bewohner = null
|
||||
for m in main.welt.humans:
|
||||
if m.data["id"] == "lena":
|
||||
if m.data["id"] == "mensch_0":
|
||||
lena = m
|
||||
for i in range(900):
|
||||
main._process(0.05)
|
||||
@@ -119,14 +143,30 @@ func _ready() -> void:
|
||||
main.player.yaw = PI
|
||||
main.player.pitch = -0.15
|
||||
await warte(20)
|
||||
await knipse("11_lena_zoegert")
|
||||
await knipse("17_lena_zoegert")
|
||||
|
||||
# Das Regelwerk greift ohne Zutun, sobald Lena die Tätigkeit wechselt
|
||||
var vor := Game.mission_progress("mensch_0")
|
||||
for i in range(20000):
|
||||
Game.time_h += 0.004
|
||||
main._process(0.02)
|
||||
for k in main.welt.get_children():
|
||||
if k is Bewohner:
|
||||
k._process(0.05)
|
||||
if Game.mission_progress("mensch_0") > vor:
|
||||
break
|
||||
main.player.global_position = lena.global_position + Vector3(0, 1.6, -5.5)
|
||||
main.player.yaw = PI
|
||||
main.player.pitch = -0.10
|
||||
await warte(4)
|
||||
await knipse("18_regel_greift")
|
||||
|
||||
main.ui.anwenden_panel(lena)
|
||||
await warte(6)
|
||||
await knipse("12_anwenden")
|
||||
main.ui._anwenden("pomodoro")
|
||||
await knipse("19_anwenden")
|
||||
main.ui._anwenden(str(DreamDB.techniken_von("hoersaal")[0]))
|
||||
await warte(25)
|
||||
await knipse("13_reaktion")
|
||||
await knipse("20_reaktion")
|
||||
|
||||
Game.time_h = 13.5
|
||||
for i in range(1200):
|
||||
@@ -139,7 +179,7 @@ func _ready() -> void:
|
||||
main.player.yaw = PI
|
||||
main.player.pitch = -0.5
|
||||
await warte(20)
|
||||
await knipse("14_cafe_mittag")
|
||||
await knipse("21_cafe_mittag")
|
||||
|
||||
Game.time_h = 22.5
|
||||
await warte(40)
|
||||
@@ -147,7 +187,81 @@ func _ready() -> void:
|
||||
main.player.yaw = 0.0
|
||||
main.player.pitch = -0.5
|
||||
await warte(20)
|
||||
await knipse("15_nacht")
|
||||
await knipse("22_nacht")
|
||||
|
||||
# Loop 2: ein erfüllter Traum lässt die Insel wachsen
|
||||
if not Game.completed.has("mensch_0"):
|
||||
Game.completed.append("mensch_0")
|
||||
Game.active_mission = ""
|
||||
Game.einsicht = 3
|
||||
Game.welt_erzeugen()
|
||||
main._traumwelt()
|
||||
await warte(20)
|
||||
main.player.global_position = Vector3(-30.0, 26.0, 30.0)
|
||||
main.player.yaw = 0.7
|
||||
main.player.pitch = -0.55
|
||||
Game.time_h = 11.0
|
||||
await warte(20)
|
||||
await knipse("23_insel_gewachsen")
|
||||
|
||||
var erstes := Orte.center(DreamDB.PLACES, DreamDB.haus_key(0))
|
||||
var tuer := Orte.door(DreamDB.PLACES, DreamDB.haus_key(0))
|
||||
var hinein := (erstes - tuer).normalized()
|
||||
main.player.global_position = erstes - hinein * 2.0 + Vector3(0, 1.0, 0)
|
||||
main.player.yaw = atan2(-hinein.x, -hinein.z)
|
||||
main.player.pitch = -0.05
|
||||
await warte(20)
|
||||
await knipse("24_erstes_haus")
|
||||
|
||||
main.ui.lehre_panel(DreamDB.haus_key(0))
|
||||
await warte(6)
|
||||
await knipse("25_neuer_weg")
|
||||
main.ui.schliessen()
|
||||
|
||||
# Neun erfüllte Träume: das Dorf ist über den ersten Ring hinausgewachsen
|
||||
while Game.completed.size() < 9:
|
||||
Game.completed.append("traum_%d" % Game.completed.size())
|
||||
Game.welt_erzeugen()
|
||||
main._traumwelt()
|
||||
await warte(20)
|
||||
main.player.global_position = Vector3(0, 110.0, 70.0)
|
||||
main.player.yaw = 0.0
|
||||
main.player.pitch = -0.85
|
||||
Game.time_h = 12.0
|
||||
await warte(20)
|
||||
await knipse("26_insel_vollstaendig")
|
||||
|
||||
# Jetzt wohnt jemand hier: die Wesen treffen sich auf der Lichtung
|
||||
for i in range(1500):
|
||||
main._process(0.05)
|
||||
for k in main.welt.get_children():
|
||||
if k is Bewohner:
|
||||
k._process(0.05)
|
||||
Game.time_h = 19.5
|
||||
main.player.global_position = Vector3(0.0, 1.2, 20.0)
|
||||
main.player.yaw = 0.0
|
||||
main.player.pitch = -0.14
|
||||
await warte(20)
|
||||
await knipse("27_wesen_am_dorfplatz")
|
||||
|
||||
var ailen: Bewohner = null
|
||||
for w in main.welt.bewohner:
|
||||
if w.data["id"] == "wesen_0":
|
||||
ailen = w
|
||||
for i in range(1200):
|
||||
main._process(0.05)
|
||||
for k in main.welt.get_children():
|
||||
if k is Bewohner:
|
||||
k._process(0.05)
|
||||
Game.time_h = 10.0
|
||||
if ailen != null and ailen.place == DreamDB.haus_key(0) and ailen._path.is_empty():
|
||||
break
|
||||
if ailen != null:
|
||||
main.player.global_position = ailen.global_position + Vector3(3.0, 0.6, 1.2)
|
||||
main.player.yaw = -PI * 0.32
|
||||
main.player.pitch = -0.06
|
||||
await warte(20)
|
||||
await knipse("28_wesen_im_haus")
|
||||
|
||||
print("Screenshots: %d" % n)
|
||||
print("Ordner: %s" % ProjectSettings.globalize_path(OUT))
|
||||
|
||||
759
tests/verify.gd
759
tests/verify.gd
@@ -26,16 +26,20 @@ func _ready() -> void:
|
||||
await test_journal()
|
||||
await test_traumdorf()
|
||||
await test_haus_betreten()
|
||||
await test_hoersaal()
|
||||
await test_mission_annehmen()
|
||||
await test_reise_zur_erde()
|
||||
await test_tagesablauf()
|
||||
await test_wege_machbar()
|
||||
await test_geist()
|
||||
await test_technik_anwenden()
|
||||
await test_pipeline()
|
||||
await test_mission_abschliessen()
|
||||
await test_welt_waechst()
|
||||
await test_techniken_verbessern_sich()
|
||||
await test_journal_ueberall()
|
||||
await test_steuerung()
|
||||
await test_erster_traum_schaffbar()
|
||||
|
||||
print("\n=========================================")
|
||||
print("Fehlende Prüfungen: %d" % fehler)
|
||||
@@ -63,6 +67,77 @@ func schritte(n: int, dt: float = 0.05, zeit: float = -1.0) -> void:
|
||||
kind._process(dt)
|
||||
|
||||
|
||||
func _panel_text() -> String:
|
||||
var out: Array = []
|
||||
for c in ui()._content.get_children():
|
||||
if c is RichTextLabel:
|
||||
out.append((c as RichTextLabel).get_parsed_text())
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
func _journal_text() -> String:
|
||||
return _panel_text()
|
||||
|
||||
|
||||
## Welche Gebäude stehen wirklich in der Traumwelt? Erkennbar an ihren Schildern.
|
||||
## Was der Hörsaal von Anfang an lehrt.
|
||||
func _basiswege() -> Array:
|
||||
return DreamDB.techniken_von("hoersaal")
|
||||
|
||||
|
||||
func _gebaute_orte() -> Array:
|
||||
var namen: Array = []
|
||||
for key in DreamDB.PLACES:
|
||||
if float(DreamDB.PLACES[key]["size"][0]) <= 0.0:
|
||||
continue
|
||||
namen.append(str(DreamDB.PLACES[key]["name"]))
|
||||
var da: Array = []
|
||||
for n in welt().get_children():
|
||||
if n is Label3D and namen.has((n as Label3D).text):
|
||||
da.append((n as Label3D).text)
|
||||
return da
|
||||
|
||||
|
||||
func _marke(kind: String) -> Node3D:
|
||||
for n in get_tree().get_nodes_in_group("interactable"):
|
||||
if n.get_meta("kind") == kind:
|
||||
return n
|
||||
return null
|
||||
|
||||
|
||||
## Stellt den Spieler vor eine Marke und lässt den Fokus neu suchen.
|
||||
func _hingehen(ziel: Node3D) -> void:
|
||||
main.player.set_mode(Player.Mode.TRAUM)
|
||||
main.player.global_position = ziel.global_position + Vector3(0, 0, 1.2)
|
||||
main.player.velocity = Vector3.ZERO
|
||||
ui().schliessen()
|
||||
main._process(0.05)
|
||||
|
||||
|
||||
## Drückt den ersten aktiven Knopf, dessen Beschriftung so beginnt.
|
||||
func _knopf_texte() -> String:
|
||||
var out: Array = []
|
||||
for c in ui()._content.get_children():
|
||||
if c is Button:
|
||||
out.append((c as Button).text)
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
func _knopf_da(anfang: String) -> bool:
|
||||
for c in ui()._content.get_children():
|
||||
if c is Button and (c as Button).text.begins_with(anfang):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _druecke(anfang: String) -> bool:
|
||||
for c in ui()._content.get_children():
|
||||
if c is Button and not (c as Button).disabled and (c as Button).text.begins_with(anfang):
|
||||
(c as Button).pressed.emit()
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func laufe(von: Vector3, yaw: float, frames: int) -> Vector3:
|
||||
main.player.set_mode(Player.Mode.TRAUM)
|
||||
main.player.look_enabled = true
|
||||
@@ -80,16 +155,28 @@ func laufe(von: Vector3, yaw: float, frames: int) -> Vector3:
|
||||
|
||||
func test_start() -> void:
|
||||
abschnitt("Start")
|
||||
p(ui().offen, "Intro-Panel ist offen")
|
||||
ui().schliessen()
|
||||
p(not ui().offen, "Intro lässt sich schließen")
|
||||
p(not ui().offen, "Kein Startbildschirm — das Spiel läuft sofort")
|
||||
p(welt() is DreamWorld, "Startwelt ist die Traumwelt")
|
||||
p(main.player != null and main.player.mode == Player.Mode.TRAUM, "Spieler läuft (kein Geist)")
|
||||
var inter := get_tree().get_nodes_in_group("interactable")
|
||||
p(inter.size() == 1 and inter[0].get_meta("kind") == "erde",
|
||||
"Einzige Interaktion in der Traumwelt ist das Tor zur Erde (%d)" % inter.size())
|
||||
var arten: Dictionary = {}
|
||||
for n in get_tree().get_nodes_in_group("interactable"):
|
||||
arten[n.get_meta("kind")] = true
|
||||
p(arten.has("erde") and arten.has("lehre") and arten.has("brett"),
|
||||
"Traumwelt bietet Tor, Hörsaal und Schwarzes Brett (%s)" % ", ".join(arten.keys()))
|
||||
p(Game.einsicht == 3, "Startkapital 3 Einsicht")
|
||||
p(Game.known.is_empty(), "Noch keine Technik gelernt")
|
||||
p((welt() as DreamWorld).bewohner.is_empty(),
|
||||
"Das Traumdorf steht am Anfang leer (%d Wesen)" %
|
||||
(welt() as DreamWorld).bewohner.size())
|
||||
var lebt := false
|
||||
for n in welt().get_children():
|
||||
if n is Bewohner:
|
||||
lebt = true
|
||||
p(not lebt, "Auch kein Wesen im Szenenbaum")
|
||||
|
||||
var gebaut := _gebaute_orte()
|
||||
p(gebaut.size() == 1 and gebaut.has("Hörsaal der Wege"),
|
||||
"Nur der Hörsaal steht als Gebäude (%s)" % ", ".join(gebaut))
|
||||
|
||||
|
||||
func test_journal() -> void:
|
||||
@@ -102,45 +189,71 @@ func test_journal() -> void:
|
||||
|
||||
ui().journal_panel("techniken")
|
||||
p(ui().offen and ui()._kind == "journal", "Journal öffnet sich")
|
||||
var knoepfe := 0
|
||||
for c in ui()._content.get_children():
|
||||
if c is Button:
|
||||
knoepfe += 1
|
||||
p(knoepfe >= TechDB.LIST.size(), "Jede der %d Techniken hat einen Knopf (%d)" %
|
||||
[TechDB.LIST.size(), knoepfe])
|
||||
p(_journal_text().contains("Hörsaal"),
|
||||
"Ohne gelernte Technik verweist das Journal auf den Hörsaal")
|
||||
p(not _knopf_da("Lernen") and not _knopf_da("Vertiefen"),
|
||||
"Das Journal lehrt und vertieft nicht selbst")
|
||||
ui().journal_panel("missionen")
|
||||
p(ui()._journal_seite == "missionen" and ui()._content.get_child_count() > 0,
|
||||
"Missionsseite baut auf")
|
||||
p(_journal_text().contains("Schwarzen Brett"),
|
||||
"Ohne Mission verweist das Journal auf das Schwarze Brett")
|
||||
p(not _knopf_da("Mission annehmen"), "Das Journal vergibt keine Missionen")
|
||||
ui().schliessen()
|
||||
|
||||
p(Game.learn("pomodoro"), "Pomodoro gelernt")
|
||||
p(Game.learn("zerlegen"), "Aufgabe zerlegen gelernt")
|
||||
p(Game.learn("umgebung"), "Umgebung gestalten gelernt")
|
||||
p(Game.einsicht == 0, "Einsicht auf 0 verbraucht")
|
||||
p(not Game.learn("reframing"), "Ohne Einsicht keine weitere Technik")
|
||||
p(Game.skill_of("pomodoro") == 1, "Frisch gelernte Technik ist Stufe 1")
|
||||
Game.add_xp("pomodoro", 4)
|
||||
p(Game.skill_of("pomodoro") == 2, "4 EP heben auf Stufe 2")
|
||||
Game.add_xp("pomodoro", 24)
|
||||
p(Game.skill_of("pomodoro") == 5, "28 EP erreichen Stufe 5")
|
||||
Game.known["pomodoro"] = 0
|
||||
var erster := str(_basiswege()[0])
|
||||
p(Game.offene_techniken() == 1,
|
||||
"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.einsicht == 2, "Das kostet 1 von 3 Einsicht (%d)" % Game.einsicht)
|
||||
p(not Game.learn(str(TechDB.LIST[1]["id"])),
|
||||
"Der zweite Weg ist trotz Einsicht nirgends zu lernen")
|
||||
p(Game.skill_of(erster) == 1, "Frisch gelernte Technik ist Stufe 1")
|
||||
Game.add_xp(erster, 4)
|
||||
p(Game.skill_of(erster) == 2, "4 EP heben auf Stufe 2")
|
||||
Game.add_xp(erster, 24)
|
||||
p(Game.skill_of(erster) == 5, "28 EP erreichen Stufe 5")
|
||||
Game.known[erster] = 0
|
||||
|
||||
ui().journal_panel("techniken")
|
||||
var buch := _journal_text()
|
||||
var alle_gelernten := true
|
||||
for id in Game.known:
|
||||
if not buch.contains(str(TechDB.by_id(id)["name"])):
|
||||
alle_gelernten = false
|
||||
p(alle_gelernten, "Das Journal führt alle %d gelernten Techniken" % Game.known.size())
|
||||
var fremde := 0
|
||||
for t in TechDB.LIST:
|
||||
if not Game.knows(t["id"]) and buch.contains(str(t["name"])):
|
||||
fremde += 1
|
||||
p(fremde == 0, "Ungelernte Techniken stehen nicht im Journal (%d)" % fremde)
|
||||
ui().schliessen()
|
||||
|
||||
|
||||
func test_traumdorf() -> void:
|
||||
abschnitt("Traumwesen leben ihren Tag")
|
||||
abschnitt("Traumwesen ziehen ein und leben ihren Tag")
|
||||
p(welt().bewohner.is_empty(), "Vorher wohnt niemand hier")
|
||||
for i in range(4):
|
||||
Game.completed.append("probe_%d" % i)
|
||||
Game.welt_erzeugen()
|
||||
main._traumwelt()
|
||||
await get_tree().process_frame
|
||||
if welt().bewohner.size() != i + 1:
|
||||
p(false, "Nach %d Träumen wohnen %d Wesen hier" %
|
||||
[i + 1, welt().bewohner.size()])
|
||||
p(welt().bewohner.size() == 4,
|
||||
"Je erfülltem Traum zieht eines ein, hier sind es vier (%d)" %
|
||||
welt().bewohner.size())
|
||||
var wesen: Array = welt().bewohner
|
||||
p(wesen.size() == 4, "Vier Wesen wohnen in der Traumwelt (%d)" % wesen.size())
|
||||
|
||||
var haeuser := 0
|
||||
for key in DreamDB.PLACES:
|
||||
var s: Array = DreamDB.PLACES[key]["size"]
|
||||
if s[0] > 0.0:
|
||||
if key.begins_with("haus_"):
|
||||
haeuser += 1
|
||||
p(haeuser == 4, "Es gibt vier Häuser (%d)" % haeuser)
|
||||
p(haeuser == 4, "Vier Häuser sind erzeugt (%d)" % haeuser)
|
||||
p(float(DreamDB.PLACES["hoersaal"]["size"][0]) > 0.0, "Dazu steht der Hörsaal")
|
||||
|
||||
var eigene: Dictionary = {}
|
||||
for w in wesen:
|
||||
var haus := "haus_" + str(w.data["id"])
|
||||
var haus: String = str(w.data["haus"])
|
||||
p(DreamDB.PLACES.has(haus), "%s hat ein eigenes Haus (%s)" % [w.data["name"], haus])
|
||||
eigene[haus] = true
|
||||
var kommt_vor := false
|
||||
@@ -190,35 +303,143 @@ func test_traumdorf() -> void:
|
||||
zuhause += 1
|
||||
p(zuhause == 4, "Um 01:00 sind alle vier an ihrem Nachtplatz (%d)" % zuhause)
|
||||
|
||||
# Ausgangslage wiederherstellen: das Dorf ist wieder leer
|
||||
Game.completed.clear()
|
||||
Game.welt_erzeugen()
|
||||
main._traumwelt()
|
||||
await get_tree().process_frame
|
||||
|
||||
|
||||
func test_haus_betreten() -> void:
|
||||
abschnitt("Orte betreten")
|
||||
var haus := Orte.center(DreamDB.PLACES, "haus_ailen")
|
||||
var tuer := Orte.door(DreamDB.PLACES, "haus_ailen")
|
||||
p(not Game.ort_offen(DreamDB.haus_key(0)), "Ohne Bewohner steht kein Haus")
|
||||
Game.completed.append("probe_haus")
|
||||
Game.welt_erzeugen()
|
||||
main._traumwelt()
|
||||
await get_tree().process_frame
|
||||
var haus_name := str(DreamDB.PLACES[DreamDB.haus_key(0)]["name"])
|
||||
p(_gebaute_orte().has(haus_name), "Mit dem ersten Bewohner steht sein Haus: %s" % haus_name)
|
||||
var haus := Orte.center(DreamDB.PLACES, DreamDB.haus_key(0))
|
||||
var tuer := Orte.door(DreamDB.PLACES, DreamDB.haus_key(0))
|
||||
var hinein: Vector3 = (haus - tuer).normalized()
|
||||
var yaw_hinein := atan2(-hinein.x, -hinein.z)
|
||||
|
||||
var ziel: Vector3 = await laufe(tuer + Vector3(0, 1.0, 1.5), 0.0, 80)
|
||||
var ziel: Vector3 = await laufe(tuer - hinein * 1.5 + Vector3(0, 1.0, 0), yaw_hinein, 80)
|
||||
var drin: bool = absf(ziel.x - haus.x) < 6.0 and absf(ziel.z - haus.z) < 5.0
|
||||
p(drin, "Durch die Tür läuft man ins Haus (Ziel %.1f / %.1f, Haus %.1f / %.1f)" %
|
||||
[ziel.x, ziel.z, haus.x, haus.z])
|
||||
|
||||
# Die Wand hält
|
||||
var vor_wand := haus + Vector3(-11.0, 1.0, 0)
|
||||
var gestoppt: Vector3 = await laufe(vor_wand, -PI * 0.5, 80)
|
||||
p(gestoppt.x < haus.x - 5.5,
|
||||
"Die Seitenwand hält den Spieler auf (x = %.1f, Wand bei %.1f)" %
|
||||
[gestoppt.x, haus.x - 6.0])
|
||||
# Die Wand hält: von der türabgewandten Seite kommt niemand hinein
|
||||
var rueck := haus + hinein * 11.0 + Vector3(0, 1.0, 0)
|
||||
var gestoppt: Vector3 = await laufe(rueck, atan2(hinein.x, hinein.z), 80)
|
||||
var drin2: bool = absf(gestoppt.x - haus.x) < 6.0 and absf(gestoppt.z - haus.z) < 5.0
|
||||
p(not drin2, "Die Rückwand hält den Spieler auf (%.1f / %.1f)" % [gestoppt.x, gestoppt.z])
|
||||
|
||||
main.player.set_mode(Player.Mode.TRAUM)
|
||||
main.player.global_position = (welt() as DreamWorld).spawn
|
||||
Game.completed.clear()
|
||||
Game.welt_erzeugen()
|
||||
main._traumwelt()
|
||||
await get_tree().process_frame
|
||||
|
||||
|
||||
func test_hoersaal() -> void:
|
||||
abschnitt("Techniken werden im Hörsaal gelehrt")
|
||||
var pult := _marke("lehre")
|
||||
p(pult != null, "Der Hörsaal hat ein Lehrpult")
|
||||
var raum := Orte.center(DreamDB.PLACES, "hoersaal")
|
||||
p(absf(pult.global_position.x - raum.x) < 8.0 and absf(pult.global_position.z - raum.z) < 6.0,
|
||||
"Das Pult steht im Hörsaal (%.1f / %.1f)" % [pult.global_position.x, pult.global_position.z])
|
||||
|
||||
var im_saal: Vector3 = await laufe(Vector3(0, 1.0, 24.0), PI, 150)
|
||||
p(im_saal.z > 33.0,
|
||||
"Vom Eingang führt ein freier Mittelgang zum Pult (z = %.1f, Pult bei %.1f)" %
|
||||
[im_saal.z, raum.z + 3.4])
|
||||
p(absf(im_saal.x) < 2.0, "Der Gang hält den Spieler in der Mitte (x = %.1f)" % im_saal.x)
|
||||
|
||||
_hingehen(pult)
|
||||
p(main._fokus == pult, "Das Pult wird als Fokus erkannt")
|
||||
main._interagieren()
|
||||
p(ui().offen and ui()._kind == "lehre", "Der Hörsaal öffnet sich")
|
||||
var seite := _panel_text()
|
||||
var basis_da := true
|
||||
for id in _basiswege():
|
||||
if not seite.contains(str(TechDB.by_id(id)["name"])):
|
||||
basis_da = false
|
||||
p(basis_da, "Die %d Basiswege werden gelehrt" % 1)
|
||||
var lernbar := 0
|
||||
for c in ui()._content.get_children():
|
||||
if c is Button and (c as Button).text.begins_with("Lernen"):
|
||||
lernbar += 1
|
||||
p(lernbar <= 1, "Und höchstens einer davon ist zu lernen (%d)" % lernbar)
|
||||
p(seite.contains(str(TechDB.LIST[1]["name"])),
|
||||
"Der Hörsaal nennt den nächsten noch unentdeckten Weg")
|
||||
p(not Game.learn("box_atmung"), "Eine unentdeckte Technik lässt sich nicht lernen")
|
||||
|
||||
Game.einsicht = 1
|
||||
Game.known.clear()
|
||||
ui().lehre_panel()
|
||||
var vorher := Game.known.size()
|
||||
p(_druecke("Lernen"), "Ein Lernen-Knopf steht bereit")
|
||||
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
|
||||
ui().lehre_panel()
|
||||
var xp_summe := 0
|
||||
for id in Game.known:
|
||||
xp_summe += int(Game.known[id])
|
||||
p(_druecke("Vertiefen"), "Ein Vertiefen-Knopf steht bereit")
|
||||
var xp_neu := 0
|
||||
for id in Game.known:
|
||||
xp_neu += int(Game.known[id])
|
||||
p(xp_neu == xp_summe + 3, "Vertiefen bringt 3 EP (%d -> %d)" % [xp_summe, xp_neu])
|
||||
p(Game.einsicht == 0, "Vertiefen kostet 1 Einsicht")
|
||||
ui().schliessen()
|
||||
|
||||
# Ausgangslage für die folgenden Prüfungen wiederherstellen
|
||||
Game.known = {"pomodoro": 0, "zerlegen": 0, "umgebung": 0}
|
||||
Game.einsicht = 0
|
||||
|
||||
|
||||
func test_mission_annehmen() -> void:
|
||||
abschnitt("Mission annehmen")
|
||||
ui().journal_panel("missionen")
|
||||
p(ui().offen and ui()._journal_seite == "missionen", "Missionsseite des Journals offen")
|
||||
abschnitt("Mission am Schwarzen Brett annehmen")
|
||||
var brett := _marke("brett")
|
||||
p(brett != null, "Das Schwarze Brett steht in der Traumwelt")
|
||||
var spawn: Vector3 = (welt() as DreamWorld).spawn
|
||||
p(absf(brett.global_position.x) > 2.5,
|
||||
"Es steht neben der Achse Start–Tor, versperrt sie also nicht (x = %.1f)" %
|
||||
brett.global_position.x)
|
||||
p(brett.global_position.distance_to(spawn) < 14.0,
|
||||
"Vom Start aus in Sichtweite (%.1f)" % brett.global_position.distance_to(spawn))
|
||||
var hin: Vector3 = await laufe(Vector3(brett.global_position.x, 1.0, spawn.z), 0.0, 150)
|
||||
p(hin.z < 14.0, "Man läuft vom Start ungehindert bis davor (z = %.1f)" % hin.z)
|
||||
main._process(0.05)
|
||||
p(main._fokus == brett, "Davor steht man in Reichweite")
|
||||
ui().schliessen()
|
||||
main._interagieren()
|
||||
p(ui().offen and ui()._kind == "brett", "Das Brett öffnet sich")
|
||||
var alle_aus := true
|
||||
for h in HumanDB.LIST:
|
||||
if not _panel_text().contains(str(h["name"])):
|
||||
alle_aus = false
|
||||
p(alle_aus, "Alle %d Menschen hängen aus" % HumanDB.LIST.size())
|
||||
|
||||
p(Game.active_mission == "", "Vorher keine Mission")
|
||||
var gedrueckt := _druecke("Mission annehmen")
|
||||
p(gedrueckt, "Ein Aushang lässt sich annehmen")
|
||||
p(Game.active_mission == "mensch_0", "Mission Lena ist aktiv (%s)" % Game.active_mission)
|
||||
p(Game.angenommen("mensch_0"), "Lena steht als angenommen im Buch")
|
||||
ui().schliessen()
|
||||
|
||||
ui().journal_panel("missionen")
|
||||
var buch := _panel_text()
|
||||
p(buch.contains(str(HumanDB.by_id("mensch_0")["name"])),
|
||||
"Das Journal führt die angenommene Mission")
|
||||
p(not buch.contains(str(HumanDB.by_id("mensch_1")["name"])),
|
||||
"Nicht angenommene Menschen fehlen im Journal")
|
||||
p(not _knopf_da("Mission annehmen"), "Im Journal lässt sich keine Mission annehmen")
|
||||
ui().schliessen()
|
||||
p(Game.accept_mission("lena"), "Mission Lena angenommen")
|
||||
p(Game.active_mission == "lena", "Mission ist aktiv")
|
||||
|
||||
|
||||
func test_reise_zur_erde() -> void:
|
||||
@@ -228,6 +449,28 @@ func test_reise_zur_erde() -> void:
|
||||
if n.get_meta("kind") == "erde":
|
||||
tor = n
|
||||
p(tor != null, "Tor zur Erde existiert")
|
||||
|
||||
# Zu Fuß hin: die Torplattform darf keine unüberwindliche Stufe sein
|
||||
ui().schliessen()
|
||||
main.player.set_mode(Player.Mode.TRAUM)
|
||||
main.player.look_enabled = true
|
||||
main.player.global_position = Vector3(0, 1.0, -20.0)
|
||||
main.player.velocity = Vector3.ZERO
|
||||
main.player.yaw = 0.0
|
||||
Input.action_press("vor")
|
||||
var erreicht := false
|
||||
var weit: float = main.player.global_position.z
|
||||
for i in range(200):
|
||||
await get_tree().physics_frame
|
||||
main._process(0.02)
|
||||
weit = main.player.global_position.z
|
||||
if main._fokus == tor:
|
||||
erreicht = true
|
||||
break
|
||||
Input.action_release("vor")
|
||||
p(erreicht, "Zu Fuß kommt man ans Tor — keine Stufe im Weg (z = %.1f, Tor bei %.1f)" %
|
||||
[weit, tor.global_position.z])
|
||||
|
||||
Game.teleport_lock = 1.0
|
||||
main.player.global_position = tor.global_position + Vector3(0, 0, 1.5)
|
||||
main._process(0.05)
|
||||
@@ -235,39 +478,39 @@ func test_reise_zur_erde() -> void:
|
||||
main._interagieren()
|
||||
await get_tree().process_frame
|
||||
p(main.auf_erde and welt() is EarthWorld, "Spieler ist auf der Erde")
|
||||
p(welt().humans.size() == 4, "Vier Menschen leben in der Stadt")
|
||||
p(welt().humans.size() == HumanDB.LIST.size() and welt().humans.size() >= 3,
|
||||
"%d Menschen leben in der Stadt" % welt().humans.size())
|
||||
|
||||
|
||||
func test_tagesablauf() -> void:
|
||||
abschnitt("Tagesablauf der Menschen")
|
||||
var lena: Bewohner = null
|
||||
var mensch: Bewohner = null
|
||||
for m in welt().humans:
|
||||
if m.data["id"] == "lena":
|
||||
lena = m
|
||||
p(lena != null, "Lena existiert auf der Erde")
|
||||
if m.data["id"] == "mensch_0":
|
||||
mensch = m
|
||||
p(mensch != null, "Der erste Mensch lebt auf der Erde: %s" % mensch.data["name"])
|
||||
|
||||
schritte(30, 0.05, 2.0)
|
||||
p(lena.activity == "schlaeft", "Um 02:00 schläft Lena (%s)" % lena.activity)
|
||||
p(lena.global_position.distance_to(Orte.center(HumanDB.PLACES, "haus_lena")) < 4.0,
|
||||
"Sie schläft in ihrem Atelier")
|
||||
p(mensch.activity == "schlaeft", "Um 02:00 schläft er (%s)" % mensch.activity)
|
||||
p(mensch.global_position.distance_to(
|
||||
Orte.center(HumanDB.PLACES, str(mensch.data["heim"]))) < 5.0,
|
||||
"Und zwar zu Hause")
|
||||
|
||||
var gewandert := false
|
||||
for i in range(600):
|
||||
schritte(1, 0.05, 8.1)
|
||||
if lena.place == "cafe" and lena._path.is_empty():
|
||||
gewandert = true
|
||||
break
|
||||
p(gewandert, "Um 08:00 geht Lena ins Café und kommt dort an")
|
||||
p(lena.activity == "essen", "Im Café isst sie (%s)" % lena.activity)
|
||||
|
||||
var daheim := false
|
||||
for i in range(600):
|
||||
schritte(1, 0.05, 10.2)
|
||||
if lena.place == "haus_lena" and lena._path.is_empty():
|
||||
daheim = true
|
||||
break
|
||||
p(daheim, "Um 10:00 ist sie zurück im Atelier")
|
||||
p(lena.activity == "zoegert", "Dort zögert sie vor der Arbeit (%s)" % lena.activity)
|
||||
# Jede Station des eigenen Tagesablaufs wird pünktlich erreicht
|
||||
var verfehlt: Array = []
|
||||
for e in mensch.data["routine"]:
|
||||
var stunde: float = float(e[0]) + 0.5
|
||||
var da := false
|
||||
for i in range(1200):
|
||||
schritte(1, 0.05, stunde)
|
||||
if mensch.place == e[1] and mensch._path.is_empty():
|
||||
da = true
|
||||
break
|
||||
if not da or mensch.activity != e[2]:
|
||||
verfehlt.append("%s um %d Uhr" % [e[1], int(e[0])])
|
||||
p(verfehlt.is_empty(), "Alle %d Stationen seines Tages werden erreicht (%s)" %
|
||||
[(mensch.data["routine"] as Array).size(),
|
||||
"lückenlos" if verfehlt.is_empty() else ", ".join(verfehlt)])
|
||||
|
||||
var luecken := 0
|
||||
for m in welt().humans:
|
||||
@@ -275,7 +518,8 @@ func test_tagesablauf() -> void:
|
||||
var r: Array = m._routine_at(float(h) + 0.5)
|
||||
if not HumanDB.PLACES.has(r[0]) or not Orte.ACTIVITY_TEXT.has(r[1]):
|
||||
luecken += 1
|
||||
p(luecken == 0, "Alle 4 Menschen haben rund um die Uhr Ort und Tätigkeit")
|
||||
p(luecken == 0, "Alle %d Menschen haben rund um die Uhr Ort und Tätigkeit" %
|
||||
welt().humans.size())
|
||||
|
||||
Game.time_h = 0.0
|
||||
Game.day = 1
|
||||
@@ -294,10 +538,10 @@ func test_tagesablauf() -> void:
|
||||
var soll: Dictionary = {}
|
||||
for e in m.data["routine"]:
|
||||
soll[e[1]] = true
|
||||
var ist: int = besucht[m.data["id"]].size()
|
||||
if ist < soll.size():
|
||||
if besucht[m.data["id"]].size() < soll.size():
|
||||
alle_ok = false
|
||||
p(false, "%s erreichte nur %d von %d Stationen" % [m.data["name"], ist, soll.size()])
|
||||
p(false, "%s erreichte nur %d von %d Stationen" %
|
||||
[m.data["name"], besucht[m.data["id"]].size(), soll.size()])
|
||||
p(alle_ok, "Über einen ganzen Tag erreicht jeder Mensch jede Station seiner Routine")
|
||||
|
||||
|
||||
@@ -368,14 +612,20 @@ func test_technik_anwenden() -> void:
|
||||
abschnitt("Techniken anwenden — Erfolgschance")
|
||||
var lena: Bewohner = null
|
||||
for m in welt().humans:
|
||||
if m.data["id"] == "lena":
|
||||
if m.data["id"] == "mensch_0":
|
||||
lena = m
|
||||
|
||||
for i in range(900):
|
||||
schritte(1, 0.05, 10.2)
|
||||
if lena.place == "haus_lena" and lena._path.is_empty():
|
||||
# Die Stunde suchen, in der dieser Mensch zögert — sie steht in seiner Routine
|
||||
var zoegert_um := -1.0
|
||||
for e in lena.data["routine"]:
|
||||
if e[2] == "zoegert":
|
||||
zoegert_um = float(e[0]) + 0.5
|
||||
for i in range(1200):
|
||||
schritte(1, 0.05, zoegert_um)
|
||||
if lena.activity == "zoegert" and lena._path.is_empty():
|
||||
break
|
||||
p(lena.activity == "zoegert", "Lena zögert gerade")
|
||||
p(lena.activity == "zoegert", "%s zögert um %.0f Uhr (%s)" %
|
||||
[lena.data["name"], zoegert_um, lena.activity])
|
||||
|
||||
main.player.set_mode(Player.Mode.GEIST)
|
||||
main.player.global_position = lena.global_position + Vector3(0, 1.0, 1.5)
|
||||
@@ -386,91 +636,308 @@ func test_technik_anwenden() -> void:
|
||||
ui().schliessen()
|
||||
|
||||
Game.known["pomodoro"] = 0
|
||||
var s1: float = Game.evaluate("pomodoro", "lena", "zoegert", false)["chance"]
|
||||
var s1: float = Game.evaluate("pomodoro", "mensch_0", "zoegert", false)["chance"]
|
||||
Game.known["pomodoro"] = 28
|
||||
var s5: float = Game.evaluate("pomodoro", "lena", "zoegert", false)["chance"]
|
||||
var s5: float = Game.evaluate("pomodoro", "mensch_0", "zoegert", false)["chance"]
|
||||
p(s5 > s1, "Höherer Skill = höhere Erfolgschance (%.0f%% -> %.0f%%)" % [s1 * 100, s5 * 100])
|
||||
Game.known["pomodoro"] = 0
|
||||
|
||||
var passend: Dictionary = Game.evaluate("pomodoro", "lena", "zoegert", false)
|
||||
var unpassend: Dictionary = Game.evaluate("pomodoro", "lena", "essen", false)
|
||||
var passend: Dictionary = Game.evaluate("pomodoro", "mensch_0", "zoegert", false)
|
||||
var unpassend: Dictionary = Game.evaluate("pomodoro", "mensch_0", "essen", false)
|
||||
p(passend["moment"] and not unpassend["moment"], "Moment-Erkennung: zögert ja, isst nein")
|
||||
p(passend["chance"] > unpassend["chance"], "Passender Moment erhöht die Chance")
|
||||
p(passend["tag"], "Pomodoro trifft Lenas Thema Aufschieben")
|
||||
var falsches_thema: Dictionary = Game.evaluate("pomodoro", "mira", "zoegert", false)
|
||||
var falsches_thema: Dictionary = Game.evaluate("pomodoro", "mensch_2", "zoegert", false)
|
||||
p(not falsches_thema["tag"], "Bei Mira trifft Pomodoro das Thema nicht")
|
||||
p(passend["chance"] > falsches_thema["chance"], "Passendes Thema erhöht die Chance")
|
||||
p(Game.evaluate("pomodoro", "lena", "schlaeft", false)["blocked"],
|
||||
p(Game.evaluate("pomodoro", "mensch_0", "schlaeft", false)["blocked"],
|
||||
"Schlafende Menschen sind nicht erreichbar")
|
||||
p(Game.evaluate("pomodoro", "lena", "zoegert", true)["chance"] < passend["chance"],
|
||||
p(Game.evaluate("pomodoro", "mensch_0", "zoegert", true)["chance"] < passend["chance"],
|
||||
"Wer abblockt, ist schwerer zu erreichen")
|
||||
p(Game.evaluate("nein_sagen", "lena", "zoegert", false)["blocked"],
|
||||
p(Game.evaluate("nein_sagen", "mensch_0", "zoegert", false)["blocked"],
|
||||
"Ungelernte Techniken lassen sich nicht anwenden")
|
||||
|
||||
var vorher_p: float = Game.mission_progress("lena")
|
||||
var res := Game.apply_technique("pomodoro", "lena", "zoegert", false)
|
||||
var vorher_p: float = Game.mission_progress("mensch_0")
|
||||
var res := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false)
|
||||
p(res["ok"], "Anwendung wird ausgeführt")
|
||||
if res["success"]:
|
||||
p(Game.mission_progress("lena") > vorher_p, "Erfolg bringt Missionsfortschritt")
|
||||
p(Game.mission_progress("mensch_0") > vorher_p, "Erfolg bringt Missionsfortschritt")
|
||||
else:
|
||||
p(Game.mission_progress("lena") == vorher_p, "Misserfolg bringt keinen Fortschritt")
|
||||
p(Game.mission_progress("mensch_0") == vorher_p, "Misserfolg bringt keinen Fortschritt")
|
||||
p(Game.known["pomodoro"] > 0, "Jede Anwendung bringt Erfahrung")
|
||||
|
||||
Game.progress["lena"] = 0.0
|
||||
Game.progress["mensch_0"] = 0.0
|
||||
Game.used.clear()
|
||||
Game.known["pomodoro"] = 28
|
||||
var ertraege: Array = []
|
||||
for i in range(6):
|
||||
var r := Game.apply_technique("pomodoro", "lena", "zoegert", false)
|
||||
var r := Game.apply_technique("pomodoro", "mensch_0", "zoegert", false)
|
||||
if r["success"]:
|
||||
ertraege.append(r["gain"])
|
||||
p(ertraege.size() >= 2 and ertraege[ertraege.size() - 1] < ertraege[0],
|
||||
"Dieselbe Technik immer wieder wirkt schwächer")
|
||||
|
||||
|
||||
func test_pipeline() -> void:
|
||||
abschnitt("Regelwerk — Missionsfortschritt automatisieren")
|
||||
Game.active_mission = "mensch_0"
|
||||
Game.progress["mensch_0"] = 0.0
|
||||
Game.completed.clear()
|
||||
Game.used.clear()
|
||||
Game.pipeline.clear()
|
||||
Game.known = {"pomodoro": 28, "zerlegen": 28, "umgebung": 28}
|
||||
|
||||
var taten := HumanDB.aktivitaeten("mensch_0")
|
||||
p(taten.size() >= 5, "Lenas Tag hat %d verschiedene Tätigkeiten" % taten.size())
|
||||
p(taten.has("zoegert") and taten.has("schlaeft"),
|
||||
"Darunter zögert und schläft: %s" % ", ".join(taten))
|
||||
|
||||
# Regelwerk über das Journal aufbauen
|
||||
ui().journal_panel("missionen")
|
||||
p(_knopf_da("Regelwerk"), "Die angenommene Mission trägt ein Regelwerk")
|
||||
p(_druecke("Regelwerk"), "Das Regelwerk öffnet sich")
|
||||
p(ui()._kind == "pipeline", "Panel »pipeline« ist offen")
|
||||
var zeilen := _knopf_texte()
|
||||
var fehlt: Array = []
|
||||
for a in taten:
|
||||
if not zeilen.contains(Orte.text(a)):
|
||||
fehlt.append(a)
|
||||
p(fehlt.is_empty(), "Jede der %d Tätigkeiten hat eine Zeile (fehlt: %s)" %
|
||||
[taten.size(), ", ".join(fehlt)])
|
||||
p(zeilen.contains("schläft → unerreichbar"),
|
||||
"Schlaf ist als unerreichbar ausgewiesen, statt eine Regel anzubieten")
|
||||
var schlaf_aktiv := false
|
||||
for c in ui()._content.get_children():
|
||||
if c is Button and (c as Button).text.begins_with("schläft") and not (c as Button).disabled:
|
||||
schlaf_aktiv = true
|
||||
p(not schlaf_aktiv, "Und lässt sich gar nicht erst anklicken")
|
||||
ui().regel_panel("mensch_0", "zoegert")
|
||||
p(ui()._kind == "regel", "Die Technikwahl für »zögert« öffnet sich")
|
||||
p(_panel_text().contains("Pomodoro-Technik"), "Gelernte Techniken stehen zur Wahl")
|
||||
p(_druecke("Diese Regel"), "Eine Technik lässt sich zuordnen")
|
||||
p(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")
|
||||
ui().schliessen()
|
||||
|
||||
Game.regel_setzen("mensch_0", "gruebelt", "zerlegen")
|
||||
Game.regel_setzen("mensch_0", "aufraeumen", "umgebung")
|
||||
p(Game.regel_anzahl("mensch_0") == 3, "Drei Regeln im Werk (%d)" % Game.regel_anzahl("mensch_0"))
|
||||
|
||||
# Eine Technik hält nur einen Posten
|
||||
Game.regel_setzen("mensch_0", "arbeitet", "zerlegen")
|
||||
p(Game.regel_anzahl("mensch_0") == 3,
|
||||
"Dieselbe Technik anderswo einzusetzen fügt keine Regel hinzu (%d)" %
|
||||
Game.regel_anzahl("mensch_0"))
|
||||
p(Game.regel_fuer("mensch_0", "gruebelt") == "" and Game.regel_fuer("mensch_0", "arbeitet") == "zerlegen",
|
||||
"Sie ist umgezogen statt sich zu verdoppeln")
|
||||
Game.regel_setzen("mensch_0", "arbeitet", "")
|
||||
Game.regel_setzen("mensch_0", "gruebelt", "zerlegen")
|
||||
|
||||
# Ohne Zutun einen Tag laufen lassen
|
||||
var lena: Bewohner = null
|
||||
for m in welt().humans:
|
||||
if m.data["id"] == "mensch_0":
|
||||
lena = m
|
||||
p(lena != null, "Lena lebt in der Stadt")
|
||||
var vorher: float = Game.mission_progress("mensch_0")
|
||||
var vor_xp: int = int(Game.known["pomodoro"]) + int(Game.known["zerlegen"])
|
||||
Game.time_h = 0.0
|
||||
main.player.global_position = Vector3(0, 30.0, 60.0) # weit weg, kein Zutun
|
||||
for i in range(9000):
|
||||
schritte(1)
|
||||
if Game.time_h >= 23.5:
|
||||
break
|
||||
var nachher: float = Game.mission_progress("mensch_0")
|
||||
p(nachher > vorher, "Der Traum wächst von allein: %d %% -> %d %%" % [int(vorher), int(nachher)])
|
||||
p(int(Game.known["pomodoro"]) + int(Game.known["zerlegen"]) > vor_xp,
|
||||
"Die Regeln sammeln nebenbei Erfahrung")
|
||||
|
||||
# Ohne Regeln passiert nichts
|
||||
Game.pipeline.clear()
|
||||
Game.progress["mensch_0"] = 0.0
|
||||
var still: float = Game.mission_progress("mensch_0")
|
||||
Game.time_h = 0.0
|
||||
for i in range(9000):
|
||||
schritte(1)
|
||||
if Game.time_h >= 23.5:
|
||||
break
|
||||
p(is_equal_approx(Game.mission_progress("mensch_0"), still),
|
||||
"Ohne Regeln bewegt sich nichts (%d %%)" % int(Game.mission_progress("mensch_0")))
|
||||
|
||||
# Regeln greifen nur für die aktive Mission
|
||||
Game.regel_setzen("mensch_1", "zoegert", "zerlegen")
|
||||
Game.progress["mensch_1"] = 0.0
|
||||
Game.time_h = 0.0
|
||||
for i in range(9000):
|
||||
schritte(1)
|
||||
if Game.time_h >= 23.5:
|
||||
break
|
||||
p(is_equal_approx(Game.mission_progress("mensch_1"), 0.0),
|
||||
"Regeln fremder Missionen bleiben still (%d %%)" % int(Game.mission_progress("mensch_1")))
|
||||
|
||||
# Ausgangslage für die folgenden Prüfungen
|
||||
Game.pipeline.clear()
|
||||
Game.progress["mensch_0"] = 0.0
|
||||
Game.used.clear()
|
||||
Game.known = {"pomodoro": 0, "zerlegen": 0, "umgebung": 0}
|
||||
|
||||
|
||||
func test_mission_abschliessen() -> void:
|
||||
abschnitt("Mission abschließen")
|
||||
Game.progress["lena"] = 0.0
|
||||
Game.progress["mensch_0"] = 0.0
|
||||
Game.used.clear()
|
||||
var einsicht_vorher := Game.einsicht
|
||||
var runden := 0
|
||||
while Game.mission_progress("lena") < Game.MISSION_ZIEL and runden < 400:
|
||||
Game.apply_technique("pomodoro", "lena", "zoegert", false)
|
||||
Game.apply_technique("zerlegen", "lena", "gruebelt", false)
|
||||
Game.apply_technique("umgebung", "lena", "aufraeumen", false)
|
||||
while Game.mission_progress("mensch_0") < Game.MISSION_ZIEL and runden < 400:
|
||||
Game.apply_technique("pomodoro", "mensch_0", "zoegert", false)
|
||||
Game.apply_technique("zerlegen", "mensch_0", "gruebelt", false)
|
||||
Game.apply_technique("umgebung", "mensch_0", "aufraeumen", false)
|
||||
runden += 1
|
||||
p(Game.mission_progress("lena") >= Game.MISSION_ZIEL,
|
||||
p(Game.mission_progress("mensch_0") >= Game.MISSION_ZIEL,
|
||||
"Lenas Traum erreicht 100 %% (nach %d Runden)" % runden)
|
||||
p(Game.completed.has("lena"), "Mission gilt als abgeschlossen")
|
||||
p(Game.completed.has("mensch_0"), "Mission gilt als abgeschlossen")
|
||||
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(),
|
||||
"Der Abschluss lässt das Dorf wachsen (%d Wesen)" % DreamDB.LIST.size())
|
||||
schritte(3)
|
||||
var lena: Bewohner = welt().humans[0]
|
||||
p(lena.beschriftung().contains("erfüllt"), "Lena zeigt sichtbar den erfüllten Traum")
|
||||
|
||||
|
||||
func test_welt_waechst() -> void:
|
||||
abschnitt("Erfüllte Träume lassen das Dorf wachsen")
|
||||
var zugeordnet: Dictionary = {}
|
||||
var doppelt: Array = []
|
||||
for i in range(TechDB.LIST.size()):
|
||||
var ort := DreamDB.ort_von_technik(str(TechDB.LIST[i]["id"]))
|
||||
if zugeordnet.has(ort):
|
||||
doppelt.append(ort)
|
||||
zugeordnet[ort] = TechDB.LIST[i]["id"]
|
||||
p(zugeordnet.size() == TechDB.LIST.size() and doppelt.is_empty(),
|
||||
"Jede der %d Techniken hat einen eigenen Lehrort (%d Orte, %d doppelt)" %
|
||||
[TechDB.LIST.size(), zugeordnet.size(), doppelt.size()])
|
||||
p(DreamDB.ort_von_technik(str(TechDB.LIST[0]["id"])) == "hoersaal",
|
||||
"Den ersten Weg lehrt der Hörsaal")
|
||||
p(DreamDB.ort_von_technik(str(TechDB.LIST[1]["id"])) == DreamDB.haus_key(0),
|
||||
"Den zweiten das erste Haus")
|
||||
|
||||
p(Game.completed.size() == 1, "Ein Traum ist erfüllt (%d)" % Game.completed.size())
|
||||
main._traumwelt()
|
||||
await get_tree().process_frame
|
||||
var lehrorte: Dictionary = {}
|
||||
for n in get_tree().get_nodes_in_group("interactable"):
|
||||
if n.get_meta("kind") == "lehre":
|
||||
lehrorte[n.get_meta("arg")] = n
|
||||
p(lehrorte.has("hoersaal") and lehrorte.has(DreamDB.haus_key(0)),
|
||||
"Hörsaal und das erste Haus lehren (%s)" % ", ".join(lehrorte.keys()))
|
||||
p(not lehrorte.has(DreamDB.haus_key(1)), "Ein zweites Haus gibt es noch nicht")
|
||||
p(Game.insel_radius() >= Game.INSEL_GRUND,
|
||||
"Die Insel trägt das Dorf (%.0f)" % Game.insel_radius())
|
||||
|
||||
Game.einsicht = 3
|
||||
var neu_id := str(TechDB.LIST[1]["id"])
|
||||
ui().lehre_panel(DreamDB.haus_key(0))
|
||||
p(ui()._kind == "lehre", "Das neue Haus lehrt")
|
||||
p(_panel_text().contains(str(TechDB.by_id(neu_id)["name"])),
|
||||
"Und zwar den Weg, den sein Bewohner mitbrachte")
|
||||
p(_druecke("Lernen"), "Er lässt sich lernen")
|
||||
p(Game.knows(neu_id), "Gelernt: %s" % TechDB.by_id(neu_id)["name"])
|
||||
ui().schliessen()
|
||||
p(not Game.technik_offen(str(TechDB.LIST[2]["id"])),
|
||||
"%s bleibt unentdeckt" % TechDB.by_id(str(TechDB.LIST[2]["id"]))["name"])
|
||||
p(Game.offene_techniken() == 2, "Entdeckt sind zwei Wege (%d)" % Game.offene_techniken())
|
||||
|
||||
await _dorf_platz_pruefen()
|
||||
|
||||
# Ausgangslage für die folgenden Prüfungen
|
||||
Game.known.erase(neu_id)
|
||||
Game.einsicht = 2
|
||||
Game.welt_erzeugen()
|
||||
main._erde()
|
||||
await get_tree().process_frame
|
||||
|
||||
|
||||
## Das Dorf wird erzeugt — also muss über viele Ausbaustufen gelten:
|
||||
## nichts überlappt, nichts ragt über den Rand, jeder Weg passt ins Zeitfenster.
|
||||
func _dorf_platz_pruefen() -> void:
|
||||
var merker: Array = Game.completed.duplicate()
|
||||
var stufen := 24
|
||||
var engster := 999.0
|
||||
var engster_text := ""
|
||||
var ueberlappung := ""
|
||||
var rand_fehler := ""
|
||||
for stufe in range(stufen + 1):
|
||||
Game.completed.clear()
|
||||
for i in range(stufe):
|
||||
Game.completed.append("stufe_%d" % i)
|
||||
Game.welt_erzeugen()
|
||||
var radius := Game.insel_radius()
|
||||
var keys: Array = []
|
||||
for key in DreamDB.PLACES:
|
||||
if float(DreamDB.PLACES[key]["size"][0]) > 0.0:
|
||||
keys.append(key)
|
||||
for a in range(keys.size()):
|
||||
var ka: String = keys[a]
|
||||
var ca := Orte.center(DreamDB.PLACES, ka)
|
||||
var sa: Array = DreamDB.PLACES[ka]["size"]
|
||||
var ecke: float = Vector3(absf(ca.x) + float(sa[0]) * 0.5, 0,
|
||||
absf(ca.z) + float(sa[1]) * 0.5).length()
|
||||
if radius - ecke < engster:
|
||||
engster = radius - ecke
|
||||
engster_text = ("%s bei %d Häusern (%.1f von %.0f)"
|
||||
% [DreamDB.PLACES[ka]["name"], stufe, ecke, radius])
|
||||
if ecke >= radius and rand_fehler == "":
|
||||
rand_fehler = "%s ragt bei %d Häusern über den Rand (%.1f >= %.0f)" % \
|
||||
[DreamDB.PLACES[ka]["name"], stufe, ecke, radius]
|
||||
for b in range(a + 1, keys.size()):
|
||||
var kb: String = keys[b]
|
||||
var cb := Orte.center(DreamDB.PLACES, kb)
|
||||
var sb: Array = DreamDB.PLACES[kb]["size"]
|
||||
var dx: float = absf(ca.x - cb.x) - (float(sa[0]) + float(sb[0])) * 0.5
|
||||
var dz: float = absf(ca.z - cb.z) - (float(sa[1]) + float(sb[1])) * 0.5
|
||||
if dx < 2.0 and dz < 2.0 and ueberlappung == "":
|
||||
ueberlappung = "%s und %s stehen ineinander (%.1f / %.1f)" % \
|
||||
[DreamDB.PLACES[ka]["name"], DreamDB.PLACES[kb]["name"], dx, dz]
|
||||
p(ueberlappung == "", "Über %d Ausbaustufen berührt sich kein Gebäude (%s)" %
|
||||
[stufen, "keins" if ueberlappung == "" else ueberlappung])
|
||||
p(rand_fehler == "" and engster >= 4.0,
|
||||
"Und keins ragt über den Rand — enger Fall: %s%s" %
|
||||
[engster_text, "" if rand_fehler == "" else " — " + rand_fehler])
|
||||
|
||||
Game.completed.clear()
|
||||
for i in range(stufen):
|
||||
Game.completed.append("stufe_%d" % i)
|
||||
Game.welt_erzeugen()
|
||||
_wege_pruefen("Traumdorf mit %d Häusern" % stufen, DreamDB.PLACES, DreamDB.LIST)
|
||||
_wege_pruefen("Stadt mit %d Menschen" % HumanDB.LIST.size(), HumanDB.PLACES, HumanDB.LIST)
|
||||
Game.completed.assign(merker)
|
||||
Game.welt_erzeugen()
|
||||
|
||||
|
||||
func test_techniken_verbessern_sich() -> void:
|
||||
abschnitt("Techniken verbessern sich durch Anwendung")
|
||||
Game.einsicht = 2
|
||||
p(Game.learn("reframing"), "Technik im Journal gelernt")
|
||||
p(Game.skill_of("reframing") == 1, "Start auf Stufe 1")
|
||||
p(Game.learn("zwei_minuten"), "Technik gelernt")
|
||||
p(Game.skill_of("zwei_minuten") == 1, "Start auf Stufe 1")
|
||||
var einsicht_vorher := Game.einsicht
|
||||
var anwendungen := 0
|
||||
while Game.skill_of("reframing") < 3 and anwendungen < 60:
|
||||
Game.apply_technique("reframing", "mira", "gruebelt", false)
|
||||
while Game.skill_of("zwei_minuten") < 3 and anwendungen < 60:
|
||||
Game.apply_technique("zwei_minuten", "mensch_2", "gruebelt", false)
|
||||
anwendungen += 1
|
||||
p(Game.skill_of("reframing") >= 3,
|
||||
"Nach %d Anwendungen von allein auf Stufe %d" % [anwendungen, Game.skill_of("reframing")])
|
||||
p(Game.skill_of("zwei_minuten") >= 3,
|
||||
"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["reframing"]
|
||||
var res := Game.apply_technique("reframing", "mira", "essen", false)
|
||||
p(Game.known["reframing"] > vor_xp,
|
||||
var vor_xp: int = Game.known["zwei_minuten"]
|
||||
var res := Game.apply_technique("zwei_minuten", "mensch_2", "essen", false)
|
||||
p(Game.known["zwei_minuten"] > vor_xp,
|
||||
"Auch ein %s bringt Erfahrung" % ("Erfolg" if res["success"] else "Fehlversuch"))
|
||||
|
||||
var xp_vor: int = Game.known["reframing"]
|
||||
p(Game.deepen("reframing"), "Vertiefen im Journal funktioniert")
|
||||
p(Game.known["reframing"] == xp_vor + 3, "Vertiefen bringt 3 EP")
|
||||
var xp_vor: int = Game.known["zwei_minuten"]
|
||||
p(Game.deepen("zwei_minuten"), "Vertiefen funktioniert")
|
||||
p(Game.known["zwei_minuten"] == xp_vor + 3, "Vertiefen bringt 3 EP")
|
||||
p(Game.einsicht == einsicht_vorher - 1, "Vertiefen kostet 1 Einsicht")
|
||||
|
||||
|
||||
@@ -556,3 +1023,71 @@ func test_steuerung() -> void:
|
||||
Input.action_release("sinken")
|
||||
p(main.player.global_position.y < hoch - 1.0,
|
||||
"Umschalt lässt den Geist sinken (y = %.1f)" % main.player.global_position.y)
|
||||
|
||||
|
||||
## Die Probe auf Loop 2: ohne einen erfüllten Traum gibt es keine neuen Wege.
|
||||
## Also muss der erste Traum mit dem Startkapital allein zu schaffen sein —
|
||||
## und zwar zuverlässig, nicht nur bei gutem Würfelglück.
|
||||
func _erster_traum_tage(saat: int) -> int:
|
||||
seed(saat)
|
||||
Game.einsicht = 3
|
||||
Game.known.clear()
|
||||
Game.used.clear()
|
||||
Game.pipeline.clear()
|
||||
Game.progress.clear()
|
||||
Game.completed.clear()
|
||||
Game.active_mission = ""
|
||||
Game.welt_erzeugen()
|
||||
for id in _basiswege():
|
||||
Game.learn(id)
|
||||
Game.accept_mission("mensch_0")
|
||||
# So ordnet ein Spieler zu: jede Technik an den Moment, wo sie am besten wirkt.
|
||||
for id in Game.known:
|
||||
var bester := ""
|
||||
var beste_chance := 0.0
|
||||
for activity in HumanDB.aktivitaeten("mensch_0"):
|
||||
if not Game.erreichbar(activity) or Game.regel_fuer("mensch_0", activity) != "":
|
||||
continue
|
||||
var ev := Game.evaluate(id, "mensch_0", activity, false)
|
||||
if float(ev["chance"]) > beste_chance:
|
||||
beste_chance = float(ev["chance"])
|
||||
bester = activity
|
||||
if bester != "":
|
||||
Game.regel_setzen("mensch_0", bester, id)
|
||||
|
||||
main._erde()
|
||||
await get_tree().process_frame
|
||||
main.player.global_position = Vector3(0, 30.0, 60.0) # der Spieler tut nichts
|
||||
Game.time_h = 7.0
|
||||
Game.day = 1
|
||||
for i in range(140000):
|
||||
schritte(1)
|
||||
if Game.completed.has("mensch_0") or Game.day > 14:
|
||||
break
|
||||
return Game.day
|
||||
|
||||
|
||||
func test_erster_traum_schaffbar() -> void:
|
||||
abschnitt("Der erste Traum ist mit dem Startkapital zu schaffen")
|
||||
Game.completed.clear()
|
||||
Game.welt_erzeugen()
|
||||
p(Game.offene_techniken() == 1,
|
||||
"Am Anfang ist genau ein Weg entdeckt (%d)" % Game.offene_techniken())
|
||||
|
||||
var tage: Array = []
|
||||
for saat in [1, 7, 555, 20240, 90210]:
|
||||
tage.append(await _erster_traum_tage(saat))
|
||||
p(Game.known.size() == 1, "Man startet mit einem Weg (%d)" % Game.known.size())
|
||||
p(Game.regel_anzahl("mensch_0") == 1,
|
||||
"Der trägt genau eine Regel (%d)" % Game.regel_anzahl("mensch_0"))
|
||||
|
||||
var schlimmster: int = tage.max()
|
||||
var bester: int = tage.min()
|
||||
p(schlimmster <= 14, "Der Traum erfüllt sich in allen %d Läufen ohne Zutun (%s)" %
|
||||
[tage.size(), str(tage)])
|
||||
p(bester >= 2, "Nie geschenkt — mindestens %d Tage" % bester)
|
||||
p(schlimmster <= 8, "Nie zäh — höchstens %d Tage (von Hand geht es schneller)" %
|
||||
schlimmster)
|
||||
p(Game.einsicht >= 3, "Der erfüllte Traum bringt 3 Einsicht (%d)" % Game.einsicht)
|
||||
p(Game.offene_techniken() > 1,
|
||||
"Und öffnet neue Wege: %d entdeckt" % Game.offene_techniken())
|
||||
|
||||
Reference in New Issue
Block a user