From 124a55a03ed79e4a6ef052ac061c3dfd80509167 Mon Sep 17 00:00:00 2001 From: Team3 Date: Sat, 5 Sep 2026 10:25:55 +0200 Subject: [PATCH] update --- .godot/editor/filesystem_cache10 | 51 ++++---- .godot/editor/project_metadata.cfg | 2 +- .godot/global_script_class_cache.cfg | 8 ++ .godot/uid_cache.bin | Bin 570 -> 607 bytes README.md | 51 +++++--- anforderungen.md | 3 +- scripts/bewohner.gd | 2 + scripts/game.gd | 171 ++++++++++++++++++++++++++- scripts/human_db.gd | 22 ++-- scripts/main.gd | 12 +- scripts/probleme.gd | 102 ++++++++++++++++ scripts/probleme.gd.uid | 1 + scripts/technique_db.gd | 12 -- scripts/ui.gd | 149 +++++++++++++++++++---- scripts/ursachen.gd | 5 - tests/verify.gd | 160 +++++++++++++++++++++++++ 16 files changed, 659 insertions(+), 92 deletions(-) create mode 100644 scripts/probleme.gd create mode 100644 scripts/probleme.gd.uid diff --git a/.godot/editor/filesystem_cache10 b/.godot/editor/filesystem_cache10 index d520441..8a2fcfb 100644 --- a/.godot/editor/filesystem_cache10 +++ b/.godot/editor/filesystem_cache10 @@ -1,26 +1,27 @@ 78098b89c88a74ebe8c20117b8754620 -::res://::1788113016 -anforderungen.md::TextFile::-1::1788204851::0::1::::<><><>0<>0<><>:: -README.md::TextFile::-1::1788165734::0::1::::<><><>0<>0<><>:: -::res://scenes/::1787989294 -main.tscn::PackedScene::-1::1787989294::0::1::::<><><>0<>0<><>::res://scripts/main.gd -::res://scripts/::1788209300 -bewohner.gd::GDScript::2125278938812678811::1788209097::0::1::::Bewohner<>Node3D<><>0<>0<><>:: -dream_db.gd::GDScript::4217518896355437099::1788164505::0::1::::DreamDB<>RefCounted<><>0<>0<><>:: -dream_world.gd::GDScript::8279158278942867258::1788164873::0::1::::DreamWorld<>Node3D<><>0<>0<><>:: -earth_world.gd::GDScript::6086818627272428934::1788165412::0::1::::EarthWorld<>Node3D<><>0<>0<><>:: -game.gd::GDScript::3600922541500868814::1788209816::0::1::::<>Node<><>0<>0<><>:: -human_db.gd::GDScript::412604892032291296::1788208999::0::1::::HumanDB<>RefCounted<><>0<>0<><>:: -main.gd::GDScript::1504009331368977934::1788209245::0::1::::<>Node3D<><>0<>0<><>:: -netz.gd::GDScript::8422943321868648339::1788163667::0::1::::Netz<>RefCounted<><>0<>0<><>:: -orte.gd::GDScript::5536340784294498671::1788209635::0::1::::Orte<>RefCounted<><>0<>0<><>:: -player.gd::GDScript::8724108548430371398::1788039718::0::1::::Player<>CharacterBody3D<><>0<>0<><>:: -prims.gd::GDScript::322632462553361584::1788039797::0::1::::Prims<>RefCounted<><>0<>0<><>:: -technique_db.gd::GDScript::4543286561661639701::1788209343::0::1::::TechDB<>RefCounted<><>0<>0<><>:: -ui.gd::GDScript::843471304695966286::1788209227::0::1::::UI<>CanvasLayer<><>0<>0<><>:: -ursachen.gd::GDScript::7371116734724270900::1788208917::0::1::::Ursachen<>RefCounted<><>0<>0<><>:: -::res://tests/::1788209571 -shots.gd::GDScript::5999651346126871724::1788209816::0::1::::<>Node<><>0<>0<><>:: -shots.tscn::PackedScene::-1::1787990528::0::1::::<><><>0<>0<><>::res://tests/shots.gd -verify.gd::GDScript::4392606218392155269::1788209816::0::1::::<>Node<><>0<>0<><>:: -verify.tscn::PackedScene::-1::1787989983::0::1::::<><><>0<>0<><>::res://tests/verify.gd +::res://::1788260833 +anforderungen.md::TextFile::-1::1788584851::0::1::::<><><>0<>0<><>:: +README.md::TextFile::-1::1788471905::0::1::::<><><>0<>0<><>:: +::res://scenes/::1788012512 +main.tscn::PackedScene::-1::1788012512::0::1::::<><><>0<>0<><>::res://scripts/main.gd +::res://scripts/::1788584883 +bewohner.gd::GDScript::2125278938812678811::1788463944::0::1::::Bewohner<>Node3D<><>0<>0<><>:: +dream_db.gd::GDScript::4217518896355437099::1788260794::0::1::::DreamDB<>RefCounted<><>0<>0<><>:: +dream_world.gd::GDScript::8279158278942867258::1788260794::0::1::::DreamWorld<>Node3D<><>0<>0<><>:: +earth_world.gd::GDScript::6086818627272428934::1788260794::0::1::::EarthWorld<>Node3D<><>0<>0<><>:: +game.gd::GDScript::3600922541500868814::1788468437::0::1::::<>Node<><>0<>0<><>:: +human_db.gd::GDScript::412604892032291296::1788584883::0::1::::HumanDB<>RefCounted<><>0<>0<><>:: +main.gd::GDScript::1504009331368977934::1788471754::0::1::::<>Node3D<><>0<>0<><>:: +netz.gd::GDScript::8422943321868648339::1788260794::0::1::::Netz<>RefCounted<><>0<>0<><>:: +orte.gd::GDScript::5536340784294498671::1788260833::0::1::::Orte<>RefCounted<><>0<>0<><>:: +player.gd::GDScript::8724108548430371398::1788459499::0::1::::Player<>CharacterBody3D<><>0<>0<><>:: +prims.gd::GDScript::322632462553361584::1788088940::0::1::::Prims<>RefCounted<><>0<>0<><>:: +probleme.gd::GDScript::4928319609961045127::1788584883::0::1::::Probleme<>RefCounted<><>0<>0<><>:: +technique_db.gd::GDScript::4543286561661639701::1788260833::0::1::::TechDB<>RefCounted<><>0<>0<><>:: +ui.gd::GDScript::843471304695966286::1788471755::0::1::::UI<>CanvasLayer<><>0<>0<><>:: +ursachen.gd::GDScript::7371116734724270900::1788260833::0::1::::Ursachen<>RefCounted<><>0<>0<><>:: +::res://tests/::1788471701 +shots.gd::GDScript::5999651346126871724::1788260833::0::1::::<>Node<><>0<>0<><>:: +shots.tscn::PackedScene::-1::1788012512::0::1::::<><><>0<>0<><>::res://tests/shots.gd +verify.gd::GDScript::4392606218392155269::1788471838::0::1::::<>Node<><>0<>0<><>:: +verify.tscn::PackedScene::-1::1788012512::0::1::::<><><>0<>0<><>::res://tests/verify.gd diff --git a/.godot/editor/project_metadata.cfg b/.godot/editor/project_metadata.cfg index 0f52a37..1276ebb 100644 --- a/.godot/editor/project_metadata.cfg +++ b/.godot/editor/project_metadata.cfg @@ -4,7 +4,7 @@ select_mode=0 [editor_metadata] -executable_path="/home/arbeit/bin/godot" +executable_path="/home/arbeit/.local/bin/godot" [recent_files] diff --git a/.godot/global_script_class_cache.cfg b/.godot/global_script_class_cache.cfg index 16e42c8..1722f98 100644 --- a/.godot/global_script_class_cache.cfg +++ b/.godot/global_script_class_cache.cfg @@ -72,6 +72,14 @@ list=[{ "path": "res://scripts/prims.gd" }, { "base": &"RefCounted", +"class": &"Probleme", +"icon": "", +"is_abstract": false, +"is_tool": false, +"language": &"GDScript", +"path": "res://scripts/probleme.gd" +}, { +"base": &"RefCounted", "class": &"TechDB", "icon": "", "is_abstract": false, diff --git a/.godot/uid_cache.bin b/.godot/uid_cache.bin index 6952fad12f8fe12fa824cfc93f052875043aeb4e..485c6419cf5c2d1c35f50f9acd38ff99ad3841d3 100644 GIT binary patch delta 49 zcmdnRa-W4!a3fi{Fy1mpk! diff --git a/README.md b/README.md index f513666..2546d8c 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,25 @@ Das Journal führt Buch — es lehrt und vergibt nichts. gibt keine Wochentage; jeder Tag ist derselbe. Zu Beginn steht in jedem Abschnitt nur ein Strich — der Tag ist leer, bis du dabei warst. +**Zwei Ebenen** — Gewohnheiten und Momente + +Ein Mensch wird von zwei verschiedenen Dingen aufgehalten, und beide brauchen +eine andere Art zu spielen. + + Gewohnheiten sitzen im Tagesablauf planend laufen ohne dich weiter + Probleme sitzen in Momenten reagierend du musst dabei sein + +Eine Gewohnheit hat einen Platz im Tag: um zehn räumt sie auf, statt +anzufangen. Du findest den Platz, pflanzt eine Ersatzhandlung ein, und sie +läuft von allein. Ein psychisches Problem hat keinen Platz — es bricht in +**Situationen** auf, wann es will. Solange eine läuft, kommt der Traum nicht +voran; du kannst sie aussitzen oder mit der passenden Technik beenden. + +Dreimal die richtige Technik im richtigen Moment, und das Problem ist +überwunden — dieser Moment kommt nicht wieder. Bis dahin umgehst du ihn jedes +Mal aufs Neue. Beides steht im HUD, bewusst verschieden gezeichnet: der Tag +als 24-Felder-Streifen, die Momente als Liste mit dem, was gerade aufbricht. + **Diagnose und Systeme** — der Kern des Spiels Ein Traum scheitert an Ursachen: *kein Anfang*, *keine Zeit*, *keine Kraft*, @@ -140,29 +159,31 @@ Abwehr. Am Ausgang ändert sich dadurch nichts, nur an der Wartezeit. Technik nur, ob Thema und Moment passen — keine Zahl. * Die Säule im Park bringt dich zurück in die Traumwelt. -## Erfolgschance +## Ob es gelingt - 5 % + 9 % je Fertigkeitsstufe - + 20 % wenn die Technik das Thema des Menschen trifft - + 35 % wenn der Moment passt (z. B. Box-Atmung, während jemand angespannt ist) - − 30 % wenn der Mensch gerade abblockt +Ein System einzurichten ist kein Selbstläufer: + + 20 % + 13 % je Fertigkeitsstufe + − 25 % wenn der Mensch gerade abwehrt begrenzt auf 5 … 95 % -Schlafende sind nicht erreichbar. Erfolg bringt Fortschritt und 2 EP, ein -Fehlversuch 1 EP, −14 % Fortschritt und 18 Sekunden Abwehr. Jeder Einsatz sperrt -die Technik für 30 Sekunden. Dieselbe Technik wirkt bei Wiederholung schwächer — -es lohnt sich, mehrere zu lernen. 100 % erfüllen den Traum. +Gelingt es, steht das System im Slot. Wie gut es trägt, entscheidet nicht der +Wurf, sondern die Passung: eine Ersatzhandlung mit **derselben Belohnung** wie +der Blocker — oder eine Technik, die ihm den Auslöser nimmt — hält deutlich +besser als eine, die nur den freien Platz füllt. + +Im Moment einer Situation wird nicht gewürfelt. Entweder die Technik trifft das +Problem oder nicht. Jeder Einsatz sperrt die Technik für 30 Sekunden. **Eine Technik wird nur besser, indem du sie anwendest.** Der Lehrort gibt sie dir, mehr nicht: kein Üben, kein Trainieren, keine Währung, die man dagegen -eintauschen könnte. Wer eine Technik auf Stufe 5 will, muss sie draußen bei -Menschen einsetzen — auch Fehlversuche zählen. +eintauschen könnte. Ein Erfolg bringt 2 EP, ein Fehlversuch 1 — auch der zählt. ## Aufbau project.godot Projekt, Autoload `Game` scenes/main.tscn Einstieg - scripts/game.gd Zustand, Diagnose, Systeme, Tempo, Uhr (Autoload) + scripts/game.gd Zustand, Diagnose, Systeme, Momente, Tempo, Uhr (Autoload) scripts/technique_db.gd die zwölf Techniken mit ihren Hinweisen scripts/human_db.gd Bezirk: Arterien, Blöcke, roji, Parzellen, Menschen scripts/dream_db.gd Traumdorf: wachsende Häuser, Wege, Bewohner, Lehrorte @@ -170,10 +191,11 @@ Menschen einsetzen — auch Fehlversuche zählen. scripts/orte.gd Orte, Türen, Wege, Tätigkeiten (beide Welten) scripts/bewohner.gd Bewohner mit Tagesablauf und Slots scripts/main.gd Weltenwechsel, Fokus, Interaktion - scripts/player.gd Läufer in der Traumwelt, Geist auf der Erde + scripts/player.gd Läufer in der Traumwelt, Geist auf der Erde, Verfolgung scripts/dream_world.gd Insel mit Dorf scripts/earth_world.gd Stadt mit Tag-Nacht-Wechsel scripts/ursachen.gd Ursachen, blockierende Tätigkeiten, Belohnungen + scripts/probleme.gd Psychische Probleme und die Momente, in denen sie aufbrechen scripts/ui.gd HUD, Journal, Tagesablauf, Hörsaal, Brett, Panels scripts/prims.gd Bausteine aus Primitiven @@ -186,7 +208,8 @@ Menschen einsetzen — auch Fehlversuche zählen. Tagesabläufen, gewachsenes Dorf und erzeugter Bezirk, Häuser betreten, Journal in beiden Welten, im Hörsaal lernen, Verbessern allein durchs Anwenden, Mission am Schwarzen Brett annehmen, -Ursachen aufdecken, Systeme bauen, Tempo messen, zur Erde reisen, +Ursachen benennen, Systeme bauen, Momente abfangen, Probleme überwinden, +Tempo messen, zur Erde reisen, Tagesabläufe über einen ganzen Tag, Wirkung der Technikwahl, Cooldown, verdeckte Erfolgschance, keine Passungshinweise, Tempo und Nachtsprung, diff --git a/anforderungen.md b/anforderungen.md index 9e6b8c9..ffa3c77 100644 --- a/anforderungen.md +++ b/anforderungen.md @@ -5,7 +5,8 @@ Kern Loops - Träume erfüllen -> Welt vergrößeren -> Techniken entdecken - Techniken entdecken -> Techniken lernen -> Missionen freischalten -- Menschen beobachten -> Probleme finden -> Techniken anwenden +- Menschen beobachten -> Tagesablauf herausfinden -> Gewohnheiten anpassen +- Menschen beobachten -> Probleme erkennen -> Techniken anwenden Funktionen - Bewegung: Bewegen, Springen, Geist, Anhängen diff --git a/scripts/bewohner.gd b/scripts/bewohner.gd index 6d708f1..539577c 100644 --- a/scripts/bewohner.gd +++ b/scripts/bewohner.gd @@ -148,6 +148,8 @@ func beschriftung() -> String: if data.has("rolle"): s += ", " + str(data["rolle"]) s += "\n" + Orte.text(activity) + if mit_mission and Game.situation_bei(str(data["id"])): + s += "\n» %s «" % Probleme.SITUATIONEN[Game.situation["key"]]["name"] if resist_left > 0.0: s += " (macht dicht)" if mit_mission: diff --git a/scripts/game.gd b/scripts/game.gd index 47aad15..638de9f 100644 --- a/scripts/game.gd +++ b/scripts/game.gd @@ -13,7 +13,6 @@ const RESIST_SEKUNDEN := 18.0 ## nicht der Faktor — Stufe 2 läuft dreifach, Stufe 3 siebenfach. const TEMPO_FAKTOR := [1.0, 3.0, 7.0] const TECHNIK_COOLDOWN := 30.0 # eine Technik braucht Luft, bevor sie wieder greift -const FEHLVERSUCH_VERLUST := 14.0 # ein Fehlschlag wirft den Traum zurück # Der Verlust ist mit Absicht so hoch, dass sich nur eine Technik lohnt, die # Thema UND Moment trifft. Wer eine Regel blind auf jede Tätigkeit hängt, # verliert unterm Strich — die Wahl muss aus der Technik gelesen werden. @@ -26,6 +25,11 @@ var systeme: Dictionary = {} # human_id -> {slot -> {tech, guete}} var erkannt_slots: Dictionary = {} # human_id -> {slot -> true} var gesehen: Dictionary = {} # human_id -> {slot -> true}, wo man dabei war var notiz_sperre: float = 0.0 # nach einem Fehlgriff kurz gesperrt +var situation: Dictionary = {} # laufende Situation: mensch, key, rest +var situation_pause: float = 0.0 # bis die nächste kommen darf +var probleme_bekannt: Dictionary = {} # human_id -> {problem -> true} +var problem_stand: Dictionary = {} # "human:problem" -> gelungene Anwendungen +var ueberwunden: Dictionary = {} # human_id -> {problem -> true} var completed: Array = [] var time_h: float = 7.0 var day: int = 1 @@ -344,6 +348,24 @@ func sehen(human_id: String, nr: int) -> void: state_changed.emit() +## Der Zustand einer Stunde. HUD und Panel zeichnen denselben -- zwei +## Auslegungen würden mit der Zeit auseinanderlaufen. +enum Stunde { UNBEKANNT, GESEHEN, SCHLAF, UNGEKLAERT, BENANNT, GEBAUT } + + +func slot_zustand(human_id: String, nr: int) -> int: + if not gesehen_slot(human_id, nr): + return Stunde.UNBEKANNT + if not system_in(human_id, nr).is_empty(): + return Stunde.GEBAUT + var grund := slot_grund(human_id, nr) + if Ursachen.ist_blocker(grund): + return Stunde.BENANNT if erkannt(human_id, nr) else Stunde.UNGEKLAERT + if grund == "schlaeft": + return Stunde.SCHLAF + return Stunde.GESEHEN + + func slot_grund(human_id: String, nr: int) -> String: var r := slots(human_id) return str(r[nr][2]) if nr >= 0 and nr < r.size() else "" @@ -501,6 +523,8 @@ func system_anzahl(human_id: String) -> int: func traum_voranbringen(stunden: float) -> void: if active_mission == "": return + if situation_bei(active_mission): + return # im Moment geht nichts voran var p: float = progress.get(active_mission, 0.0) \ + traum_tempo(active_mission) * stunden / 24.0 progress[active_mission] = minf(p, MISSION_ZIEL) @@ -514,6 +538,151 @@ func traum_voranbringen(stunden: float) -> void: state_changed.emit() +# ------------------------------------------------------- Probleme und Momente +# Die zweite Ebene. Eine Gewohnheit sitzt im Tagesablauf und läuft ohne dich +# weiter, sobald ein System steht. Ein Problem sitzt in keiner Stunde -- es +# bricht in Situationen auf. Solange eine läuft, kommt der Traum nicht voran. +# Du kannst sie aussitzen oder mit der passenden Technik beenden. + +const SITUATION_DAUER := 22.0 # so lange steht der Traum still +const SITUATION_PAUSE := 40.0 # Luft zwischen zwei Situationen +const PROBLEM_UEBERWINDEN := 3 # so oft muss eine Technik greifen +const SITUATION_GEWINN := 6.0 # Prozent, wenn sie früh beendet wird + + +func probleme_von(human_id: String) -> Array: + var h := HumanDB.by_id(human_id) + return h["probleme"] if h.has("probleme") else [] + + +func problem_bekannt(human_id: String, problem: String) -> bool: + return probleme_bekannt.get(human_id, {}).has(problem) + + +func problem_ueberwunden(human_id: String, problem: String) -> bool: + return ueberwunden.get(human_id, {}).has(problem) + + +func problem_fortschritt(human_id: String, problem: String) -> int: + return int(problem_stand.get("%s:%s" % [human_id, problem], 0)) + + +func situation_laeuft() -> bool: + return not situation.is_empty() + + +func situation_bei(human_id: String) -> bool: + return situation_laeuft() and str(situation["mensch"]) == human_id + + +func situation_problem() -> String: + return str(situation["problem"]) if situation_laeuft() else "" + + +## Läuft die Uhr weiter, kommt irgendwann die nächste Situation -- aber nur +## bei einem wachen Menschen, dem man gerade hilft. +func situationen_ticken(delta: float, wach: bool) -> void: + if situation_laeuft(): + situation["rest"] = float(situation["rest"]) - delta + if float(situation["rest"]) <= 0.0: + var p := situation_problem() + notified.emit("Der Moment ist vorbei — %s bleibt." % Probleme.name_von(p), + Color(1, 0.75, 0.55)) + situation = {} + situation_pause = SITUATION_PAUSE + state_changed.emit() + return + situation_pause = maxf(0.0, situation_pause - delta) + if active_mission == "" or not wach or situation_pause > 0.0: + return + _situation_starten() + + +func _situation_starten() -> void: + var offen: Array = [] + for p in probleme_von(active_mission): + if not problem_ueberwunden(active_mission, str(p)): + offen.append(str(p)) + if offen.is_empty(): + situation_pause = SITUATION_PAUSE + return + var problem: String = str(offen[randi() % offen.size()]) + var moeglich := Probleme.situationen_von(problem) + if moeglich.is_empty(): + situation_pause = SITUATION_PAUSE + return + situation = { + "mensch": active_mission, + "key": str(moeglich[randi() % moeglich.size()]), + "problem": problem, + "rest": SITUATION_DAUER, + } + state_changed.emit() + + +## Dabeisein zeigt, welches Problem dahintersteckt. +func situation_sehen() -> void: + if not situation_laeuft(): + return + var id := str(situation["mensch"]) + var p := situation_problem() + if problem_bekannt(id, p): + return + if not probleme_bekannt.has(id): + probleme_bekannt[id] = {} + probleme_bekannt[id][p] = true + notified.emit("Dahinter steckt: %s — %s" % + [Probleme.name_von(p), Probleme.LISTE[p]["kurz"]], Color(0.8, 0.95, 1)) + state_changed.emit() + + +## Eine Technik im Moment anwenden. Passt sie zum Problem, ist die Situation +## vorbei; oft genug, und das Problem kommt nicht wieder. +func situation_anwenden(tech_id: String) -> Dictionary: + var res := {"ok": false, "grund": "", "treffer": false, "ueberwunden": false} + if not situation_laeuft(): + res["grund"] = "Gerade ist kein Moment offen." + return res + if not known.has(tech_id): + res["grund"] = "Diese Technik kennst du nicht." + return res + if not bereit(tech_id): + res["grund"] = "%s braucht noch %.0f s." % [TechDB.by_id(tech_id)["name"], + cooldown_rest(tech_id)] + return res + var id := str(situation["mensch"]) + var problem := situation_problem() + cooldown[tech_id] = TECHNIK_COOLDOWN + res["ok"] = true + if not Probleme.hilft(tech_id, problem): + add_xp(tech_id, 1) + notified.emit("%s greift hier nicht." % TechDB.by_id(tech_id)["name"], + Color(1, 0.7, 0.5)) + state_changed.emit() + return res + + res["treffer"] = true + add_xp(tech_id, 2) + var schluessel := "%s:%s" % [id, problem] + problem_stand[schluessel] = problem_fortschritt(id, problem) + 1 + situation_sehen() + situation = {} + situation_pause = SITUATION_PAUSE + if id == active_mission: + progress[id] = minf(mission_progress(id) + SITUATION_GEWINN, MISSION_ZIEL) + notified.emit("%s trägt durch den Moment. (+%d %%)" % + [TechDB.by_id(tech_id)["name"], int(SITUATION_GEWINN)], Color(0.6, 1, 0.7)) + if problem_fortschritt(id, problem) >= PROBLEM_UEBERWINDEN: + if not ueberwunden.has(id): + ueberwunden[id] = {} + ueberwunden[id][problem] = true + res["ueberwunden"] = true + notified.emit("%s ist überwunden — dieser Moment kommt nicht wieder." % + Probleme.name_von(problem), Color(1, 0.85, 0.4)) + state_changed.emit() + return res + + # --------------------------------------------------- Erreichbarkeit ## Schlafende erreicht niemand. diff --git a/scripts/human_db.gd b/scripts/human_db.gd index 8f6377e..2eebb01 100644 --- a/scripts/human_db.gd +++ b/scripts/human_db.gd @@ -391,6 +391,18 @@ static func _anker_setzen() -> void: ANKER[key] = int(NETZ.andocken(tuer)["knoten"]) +## Was diesen Menschen nicht im Tagesablauf aufhält, sondern in Momenten. +## Ein bis zwei Probleme -- die zweite Ebene neben den Ursachen. +static func probleme_von(i: int) -> Array: + var alle: Array = Probleme.LISTE.keys() + var out: Array = [str(alle[i % alle.size()])] + if i % 2 == 1: + var zweites: String = str(alle[(i + 2) % alle.size()]) + if zweites != out[0]: + out.append(zweites) + return out + + ## Woran der Traum dieses Menschen scheitert. Zwei bis drei Ursachen, nie alle. ## Die erste hängt am Weg gleicher Nummer -- so ist der neueste Mensch immer ## mit dem neuesten Weg anzugehen. @@ -462,6 +474,7 @@ static func _mensch(i: int) -> Dictionary: "problem": str(anliegen["problem"]), "tags": (anliegen["tags"] as Array).duplicate(), "ursachen": ursachen_von(i), + "probleme": probleme_von(i), "mission": true, "heim": heim, "farbe": Color.from_hsv(fmod(0.02 + float(i) * 0.191, 1.0), 0.5, 0.8), @@ -495,15 +508,6 @@ 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: if h["id"] == id: diff --git a/scripts/main.gd b/scripts/main.gd index dee7bc5..2bd6625 100644 --- a/scripts/main.gd +++ b/scripts/main.gd @@ -73,6 +73,7 @@ func _process(delta: float) -> void: ui.setze_hinweis("") return Game.notiz_sperre = maxf(0.0, Game.notiz_sperre - delta) + Game.situationen_ticken(dt, auf_erde and not Game.schlaeft_ziel()) _fokus_suchen() _dabeisein() _hinweis_setzen() @@ -131,6 +132,9 @@ func _notiz_hinweis(mensch: Bewohner) -> String: var tat := Orte.text(mensch.activity) if str(mensch.data["id"]) != Game.active_mission: return tat + if Game.situation_bei(str(mensch.data["id"])): + return "%s · [E] jetzt eine Technik" % \ + Probleme.SITUATIONEN[Game.situation["key"]]["name"] if Game.notiz_sperre > 0.0: return "%s (%.0f s)" % [tat, Game.notiz_sperre] if Game.erkannt(str(mensch.data["id"]), int(mensch.slot)): @@ -181,6 +185,8 @@ func _dabeisein() -> void: var id: String = str(_fokus_mensch.data["id"]) if id == Game.active_mission: Game.sehen(id, int(_fokus_mensch.slot)) + if Game.situation_bei(id): + Game.situation_sehen() ## Den Moment benennen: hier läuft etwas schief. Das ist der Kern des @@ -227,7 +233,11 @@ func _verfolgen() -> void: func _interagieren() -> void: if _fokus_mensch != null: - ui.anwenden_panel(_fokus_mensch) + # Der offene Moment geht vor: er ist gleich wieder vorbei + if Game.situation_bei(str(_fokus_mensch.data["id"])): + ui.situation_panel(_fokus_mensch) + else: + ui.anwenden_panel(_fokus_mensch) return if _fokus == null: return diff --git a/scripts/probleme.gd b/scripts/probleme.gd new file mode 100644 index 0000000..4a31ca4 --- /dev/null +++ b/scripts/probleme.gd @@ -0,0 +1,102 @@ +class_name Probleme +## Die zweite Ebene: was einen Menschen nicht im Tagesablauf aufhält, sondern +## in einzelnen Momenten. Eine Gewohnheit hat einen Platz im Tag -- ein +## psychisches Problem hat keinen. Es zeigt sich in Situationen, und die +## kommen, wann sie wollen. +## +## Darum die andere Mechanik: Gewohnheiten pflanzt man ein und sie laufen ohne +## dich weiter. Bei einer Situation musst du dabei sein und im Moment die +## Technik wählen, die zu diesem Problem passt. + +static var LISTE: Dictionary = { + "angst": { + "name": "Angst", "kurz": "der Körper macht dicht, bevor der Kopf mitredet", + }, + "selbstzweifel": { + "name": "Selbstzweifel", "kurz": "die innere Stimme redet es klein", + }, + "stress": { + "name": "Stress", "kurz": "alles gleichzeitig, nichts zu Ende", + }, + "ueberforderung": { + "name": "Überforderung", "kurz": "der Berg ist zu groß zum Ansehen", + }, + "erschoepfung": { + "name": "Erschöpfung", "kurz": "der Akku ist leer, bevor der Tag anfängt", + }, + "einsamkeit": { + "name": "Einsamkeit", "kurz": "hängt allein an dem, was zu zweit ginge", + }, +} + +## Situationen: was im Moment sichtbar wird. Jede gehört zu einem Problem. +static var SITUATIONEN: Dictionary = { + "blackout": { + "name": "wird starr", "was": "Ihr wird schwarz vor Augen.", + "problem": "angst", + }, + "aufschieben_angst": { + "name": "meldet sich ab", "was": "Sie sagt den Termin wieder ab.", + "problem": "angst", + }, + "kleinreden": { + "name": "redet es klein", "was": "»Das kann doch jeder besser.«", + "problem": "selbstzweifel", + }, + "vergleich": { + "name": "vergleicht sich", "was": "Sie sieht, was andere schaffen.", + "problem": "selbstzweifel", + }, + "alles_gleichzeitig": { + "name": "fängt drei Dinge an", "was": "Alles gleichzeitig, nichts zu Ende.", + "problem": "stress", + }, + "noch_ein_ja": { + "name": "sagt schon wieder ja", "was": "Noch eine Bitte, noch ein Ja.", + "problem": "stress", + }, + "erstarrt": { + "name": "erstarrt vor dem Berg", "was": "Sie sieht die Aufgabe und macht nichts.", + "problem": "ueberforderung", + }, + "ausgebrannt": { + "name": "sackt zusammen", "was": "Sie sitzt da und starrt.", + "problem": "erschoepfung", + }, + "allein": { + "name": "hängt allein fest", "was": "Sie kommt nicht weiter und fragt niemanden.", + "problem": "einsamkeit", + }, +} + + +static func problem_da(schluessel: String) -> bool: + return LISTE.has(schluessel) + + +static func name_von(schluessel: String) -> String: + return str(LISTE[schluessel]["name"]) if LISTE.has(schluessel) else schluessel + + +## Alle Situationen, in denen sich dieses Problem zeigt. +static func situationen_von(problem: String) -> Array: + var out: Array = [] + for key in SITUATIONEN: + if SITUATIONEN[key]["problem"] == problem: + out.append(key) + return out + + +## Techniken, die gegen dieses Problem etwas ausrichten -- erkennbar am Feld +## `tags` der Technik. +static func techniken_gegen(problem: String) -> Array: + var out: Array = [] + for t in TechDB.LIST: + if (t["tags"] as Array).has(problem): + out.append(str(t["id"])) + return out + + +static func hilft(tech_id: String, problem: String) -> bool: + var t := TechDB.by_id(tech_id) + return not t.is_empty() and (t["tags"] as Array).has(problem) diff --git a/scripts/probleme.gd.uid b/scripts/probleme.gd.uid new file mode 100644 index 0000000..6b23e41 --- /dev/null +++ b/scripts/probleme.gd.uid @@ -0,0 +1 @@ +uid://cchkx60v1a0e2 diff --git a/scripts/technique_db.gd b/scripts/technique_db.gd index 28bfdf0..be3a2f6 100644 --- a/scripts/technique_db.gd +++ b/scripts/technique_db.gd @@ -14,7 +14,6 @@ static var LIST: Array = [ "belohnung": "erfolg", "ersatz": "arbeitet in Blöcken", "tags": ["prokrastination", "fokus"], - "momente": ["arbeitet", "zoegert"], "farbe": Color(0.95, 0.45, 0.30), }, { @@ -28,7 +27,6 @@ static var LIST: Array = [ "belohnung": "erfolg", "ersatz": "fängt zwei Minuten an", "tags": ["prokrastination", "ueberforderung"], - "momente": ["zoegert", "aufraeumen", "arbeitet"], "farbe": Color(0.98, 0.72, 0.25), }, { @@ -42,7 +40,6 @@ static var LIST: Array = [ "belohnung": "ordnung", "ersatz": "folgt dem Wenn-Dann-Plan", "tags": ["gewohnheit", "prokrastination"], - "momente": ["morgenroutine", "pause"], "farbe": Color(0.45, 0.80, 0.55), }, { @@ -56,7 +53,6 @@ static var LIST: Array = [ "belohnung": "ordnung", "ersatz": "hängt es an die Routine", "tags": ["gewohnheit"], - "momente": ["morgenroutine", "essen", "abendroutine"], "farbe": Color(0.40, 0.70, 0.85), }, { @@ -70,7 +66,6 @@ static var LIST: Array = [ "belohnung": "ordnung", "ersatz": "zerlegt die Aufgabe", "tags": ["ueberforderung", "prokrastination", "ziel"], - "momente": ["gruebelt", "arbeitet"], "farbe": Color(0.75, 0.55, 0.95), }, { @@ -84,7 +79,6 @@ static var LIST: Array = [ "belohnung": "", "ersatz": "hat es sich leicht gemacht", "tags": ["ablenkung", "gewohnheit"], - "momente": ["aufraeumen", "arbeitet", "ruht"], "farbe": Color(0.55, 0.85, 0.40), }, { @@ -98,7 +92,6 @@ static var LIST: Array = [ "belohnung": "einordnung", "ersatz": "deutet den Gedanken um", "tags": ["selbstzweifel", "angst"], - "momente": ["gruebelt", "ruht", "angespannt"], "farbe": Color(0.95, 0.55, 0.75), }, { @@ -112,7 +105,6 @@ static var LIST: Array = [ "belohnung": "abschalten", "ersatz": "atmet sich ruhig", "tags": ["angst", "stress"], - "momente": ["angespannt", "wartet", "pause"], "farbe": Color(0.40, 0.85, 0.95), }, { @@ -126,7 +118,6 @@ static var LIST: Array = [ "belohnung": "verbundenheit", "ersatz": "ist freundlich mit sich", "tags": ["selbstzweifel", "rueckschlag"], - "momente": ["gruebelt", "ruht", "angespannt"], "farbe": Color(1.0, 0.80, 0.60), }, { @@ -140,7 +131,6 @@ static var LIST: Array = [ "belohnung": "zeit_fuer_sich", "ersatz": "hält das Schlaffenster", "tags": ["erschoepfung", "gesundheit"], - "momente": ["abendroutine", "muede", "ruht"], "farbe": Color(0.45, 0.50, 0.90), }, { @@ -154,7 +144,6 @@ static var LIST: Array = [ "belohnung": "anschluss", "ersatz": "bittet um Hilfe", "tags": ["einsamkeit", "ueberforderung"], - "momente": ["wartet", "pause", "unterwegs"], "farbe": Color(0.90, 0.90, 0.45), }, { @@ -168,7 +157,6 @@ static var LIST: Array = [ "belohnung": "anerkennung", "ersatz": "sagt Nein mit Alternative", "tags": ["stress", "grenzen"], - "momente": ["arbeitet", "angespannt", "unterwegs"], "farbe": Color(0.85, 0.35, 0.45), }, ] diff --git a/scripts/ui.gd b/scripts/ui.gd index 1a788c2..0b3caa8 100644 --- a/scripts/ui.gd +++ b/scripts/ui.gd @@ -16,6 +16,8 @@ var _ablauf: HBoxContainer var _ablauf_titel: Label var _ablauf_jetzt: Label var _ablauf_grund: ColorRect +var _probleme: RichTextLabel +var _probleme_grund: ColorRect var _zellen: Array = [] var _ticks: Array = [] var _dim: ColorRect @@ -27,7 +29,6 @@ var _apply_human: Node3D var _journal_seite := "techniken" var _scroll_wunsch := 0 var _scroll_frames := 0 -var _on_travel: Callable func _ready() -> void: @@ -54,6 +55,7 @@ func _baue_hud() -> void: add_child(_hud) _baue_ablauf() + _baue_probleme() var steuerung := Label.new() steuerung.text = "WASD Bewegen · Maus Umsehen · E Interagieren · F Folgen · " + \ @@ -151,16 +153,17 @@ func _baue_ablauf() -> void: ## Farbe je Stunde: unbekannt, gesehen, ungeklärt, benannt, gebaut. func _ablauf_farbe(human_id: String, nr: int) -> Color: - if not Game.gesehen_slot(human_id, nr): - return Color(0.22, 0.23, 0.30) - if not Game.system_in(human_id, nr).is_empty(): - return Color(0.44, 0.78, 0.50) - var grund := Game.slot_grund(human_id, nr) - if Ursachen.ist_blocker(grund): - return Color(0.85, 0.66, 0.32) if not Game.erkannt(human_id, nr) \ - else Color(0.84, 0.42, 0.35) - if grund == "schlaeft": - return Color(0.30, 0.33, 0.44) + match Game.slot_zustand(human_id, nr): + Game.Stunde.UNBEKANNT: + return Color(0.22, 0.23, 0.30) + Game.Stunde.GEBAUT: + return Color(0.44, 0.78, 0.50) + Game.Stunde.UNGEKLAERT: + return Color(0.85, 0.66, 0.32) + Game.Stunde.BENANNT: + return Color(0.84, 0.42, 0.35) + Game.Stunde.SCHLAF: + return Color(0.30, 0.33, 0.44) return Color(0.46, 0.50, 0.62) @@ -193,6 +196,60 @@ func _ablauf_aktualisieren() -> void: _ablauf_jetzt.text = "%02d Uhr %s" % [jetzt, _stunden_text(id, nr_jetzt)] +## Die zweite Ebene im HUD: bekannte Probleme und der offene Moment. +## Bewusst anders gezeichnet als der Tagesstreifen -- sie hängt nicht an Stunden. +func _baue_probleme() -> void: + _probleme_grund = ColorRect.new() + _probleme_grund.color = Color(0, 0, 0, 0.42) + _probleme_grund.set_anchors_preset(Control.PRESET_TOP_LEFT) + _probleme_grund.position = Vector2(ABLAUF_X - 8.0, ABLAUF_Y + 88.0) + _probleme_grund.custom_minimum_size = Vector2(414.0, 74.0) + _probleme_grund.size = _probleme_grund.custom_minimum_size + _probleme_grund.mouse_filter = Control.MOUSE_FILTER_IGNORE + add_child(_probleme_grund) + + _probleme = RichTextLabel.new() + _probleme.bbcode_enabled = true + _probleme.fit_content = true + _probleme.scroll_active = false + _probleme.set_anchors_preset(Control.PRESET_TOP_LEFT) + _probleme.position = Vector2(ABLAUF_X, ABLAUF_Y + 94.0) + _probleme.custom_minimum_size = Vector2(400, 64) + _probleme.size = Vector2(400, 64) + _probleme.mouse_filter = Control.MOUSE_FILTER_IGNORE + _probleme.add_theme_font_size_override("normal_font_size", 14) + _probleme.add_theme_font_size_override("bold_font_size", 14) + add_child(_probleme) + + +func _probleme_aktualisieren() -> void: + var id := Game.active_mission + var an := id != "" + _probleme.visible = an + _probleme_grund.visible = an + if not an: + return + var zeilen: Array = [] + if Game.situation_laeuft() and Game.situation_bei(id): + var s: Dictionary = Probleme.SITUATIONEN[Game.situation["key"]] + zeilen.append("[color=#ffcc66][b]Jetzt: %s[/b][/color] %s [b]%.0f s[/b]" % + [s["name"], s["was"], float(Game.situation["rest"])]) + else: + zeilen.append("[color=#8e93a8]Ihre Momente[/color]") + var bekannt: Array = [] + for p in Game.probleme_von(id): + var key := str(p) + if not Game.problem_bekannt(id, key): + bekannt.append("[color=#55596b]?[/color]") + elif Game.problem_ueberwunden(id, key): + bekannt.append("[color=#88ff99]%s ✓[/color]" % Probleme.name_von(key)) + else: + bekannt.append("[color=#ffb0a0]%s %d/%d[/color]" % [Probleme.name_von(key), + Game.problem_fortschritt(id, key), Game.PROBLEM_UEBERWINDEN]) + zeilen.append(" · ".join(bekannt)) + _probleme.text = "\n".join(zeilen) + + func _baue_panel() -> void: _dim = ColorRect.new() _dim.color = Color(0, 0, 0, 0.62) @@ -278,6 +335,7 @@ func _hud_aktualisieren() -> void: zeilen.append("[color=#aaaaaa]Keine Mission. Schwarzes Brett am Dorfplatz.[/color]") _hud.text = "\n".join(zeilen) _ablauf_aktualisieren() + _probleme_aktualisieren() func setze_hinweis(text: String) -> void: @@ -531,18 +589,19 @@ func _tagesablauf(human_id: String) -> void: func _stunden_text(human_id: String, nr: int) -> String: - if not Game.gesehen_slot(human_id, nr): - return "—" - var sys := Game.system_in(human_id, nr) - if not sys.is_empty(): - return "%s ← %s" % [Game.slot_aktivitaet(human_id, nr), - TechDB.by_id(str(sys["tech"]))["name"]] var grund := Game.slot_grund(human_id, nr) - if not Game.erkannt(human_id, nr): - return "?" - if Ursachen.ist_blocker(grund): - return "%s (%s)" % [Ursachen.BLOCKER[grund]["name"], - Ursachen.LISTE[Ursachen.BLOCKER[grund]["ursache"]]["name"]] + match Game.slot_zustand(human_id, nr): + Game.Stunde.UNBEKANNT: + return "—" + Game.Stunde.GEBAUT: + var sys := Game.system_in(human_id, nr) + return "%s ← %s" % [Game.slot_aktivitaet(human_id, nr), + TechDB.by_id(str(sys["tech"]))["name"]] + Game.Stunde.UNGEKLAERT: + return "?" + Game.Stunde.BENANNT: + return "%s (%s)" % [Ursachen.BLOCKER[grund]["name"], + Ursachen.LISTE[Ursachen.BLOCKER[grund]["ursache"]]["name"]] return Orte.text(grund) @@ -554,6 +613,49 @@ func _stunden_zeile(human_id: String, nr: int, von: int, bis: int, text: String) func() -> void: slot_panel(human_id, nr), offen) +## Der offene Moment: hier zählt, welche Technik zu diesem Problem passt. +func situation_panel(mensch: Node3D) -> void: + _apply_human = mensch + var id: String = str(mensch.data["id"]) + if not Game.situation_bei(id): + anwenden_panel(mensch) + return + var s: Dictionary = Probleme.SITUATIONEN[Game.situation["key"]] + var problem := Game.situation_problem() + _leeren("%s — %s" % [mensch.data["name"], s["name"]]) + _text("[i]%s[/i]" % s["was"], 16) + if Game.problem_bekannt(id, problem): + _text("Dahinter steckt [b]%s[/b] — %s." % + [Probleme.name_von(problem), Probleme.LISTE[problem]["kurz"]], 15) + _text("[color=#aaaaaa]Überwunden nach %d von %d Malen.[/color]" % + [Game.problem_fortschritt(id, problem), Game.PROBLEM_UEBERWINDEN], 14) + else: + _text("[color=#ffcc66]Was dahintersteckt, weißt du noch nicht.[/color]", 15) + _text("[color=#aaaaaa]Noch %.0f Sekunden. Solange kommt ihr Traum nicht voran.[/color]" % + float(Game.situation["rest"]), 14) + _trenner() + if Game.known.is_empty(): + _text("[color=#ffcc66]Du kennst keine Technik.[/color]", 16) + _zeigen("situation") + return + for tech_id in TechDB.ids(): + if not Game.knows(tech_id): + continue + var t := TechDB.by_id(tech_id) + var wofuer: Array = [] + for tag in t["tags"]: + wofuer.append(Probleme.name_von(str(tag))) + _text("[b]%s[/b] · Stufe %d\n[color=#cccccc]%s[/color]\n[color=#9fb6ff]Hilft bei:[/color] %s" % + [t["name"], Game.skill_of(tech_id), t["wann"], ", ".join(wofuer)], 15) + var frei := Game.bereit(tech_id) + var text := "Jetzt anwenden" if frei else "Noch %.0f s" % Game.cooldown_rest(tech_id) + _knopf(text, func() -> void: + Game.situation_anwenden(tech_id) + schliessen(), frei) + _trenner() + _zeigen("situation") + + ## Was soll in diesem Slot stehen? func slot_panel(human_id: String, nr: int) -> void: var h := HumanDB.by_id(human_id) @@ -664,13 +766,14 @@ func brett_panel() -> void: _text("Ausgehängt wird nur, wem du mit dem, was du kannst, auch helfen kannst. " + "Das Tor zur Erde öffnet sich nur mit einer Mission.", 15) _trenner() + var offen := Game.offene_missionen() var gezeigt := 0 var verdeckt := 0 for h in HumanDB.LIST: var id: String = h["id"] if Game.completed.has(id): continue - if Game.active_mission != id and not Game.machbar(id): + if not offen.has(id): verdeckt += 1 continue gezeigt += 1 diff --git a/scripts/ursachen.gd b/scripts/ursachen.gd index 67287a1..329b43f 100644 --- a/scripts/ursachen.gd +++ b/scripts/ursachen.gd @@ -13,27 +13,22 @@ class_name Ursachen static var LISTE: Dictionary = { "anfang": { "name": "Kein Anfang", "kurz": "kommt nie in Gang", - "symptom": "Sie setzt sich hin und tut dann etwas anderes.", "faktor": 0.30, }, "zeit": { "name": "Keine Zeit", "kurz": "der Tag ist voll", - "symptom": "Der Tag ist voll mit dem, was andere wollen.", "faktor": 0.30, }, "kraft": { "name": "Keine Kraft", "kurz": "abends ist nichts mehr übrig", - "symptom": "Am Abend reicht es nur noch für Berieselung.", "faktor": 0.40, }, "halt": { "name": "Kein Halt", "kurz": "fängt an und hört wieder auf", - "symptom": "Angefangen wird oft, drangeblieben selten.", "faktor": 0.50, }, "grund": { "name": "Kein Grund", "kurz": "glaubt nicht mehr daran", - "symptom": "Sie redet sich das Vorhaben selbst klein.", "faktor": 0.40, }, } diff --git a/tests/verify.gd b/tests/verify.gd index fb98bd8..6475849 100644 --- a/tests/verify.gd +++ b/tests/verify.gd @@ -40,6 +40,7 @@ func _ready() -> void: await test_notieren_im_spiel() await test_tagesablauf_anzeige() await test_ablauf_im_hud() + await test_probleme_und_momente() await test_systeme() await test_tempo_des_traums() await test_cooldown() @@ -878,6 +879,165 @@ func test_diagnose() -> void: ## Gegen eine Ursache baut man ein System. Passt die Belohnung, hält es besser. +## Die zweite Ebene: Probleme hängen nicht an Stunden, sondern an Momenten. +## Gewohnheiten laufen ohne dich weiter -- hier musst du dabei sein. +func test_probleme_und_momente() -> void: + abschnitt("Probleme zeigen sich in Momenten") + var id := "mensch_0" + Game.active_mission = id + Game.situation = {} + Game.situation_pause = 0.0 + Game.probleme_bekannt.clear() + Game.problem_stand.clear() + Game.ueberwunden.clear() + Game.cooldown.clear() + ui().schliessen() + + var prob: Array = Game.probleme_von(id) + p(prob.size() >= 1 and prob.size() <= 2, + "%s trägt %d Problem(e): %s" % [HumanDB.by_id(id)["name"], prob.size(), str(prob)]) + var alle_bekannt := true + for x in prob: + if not Probleme.problem_da(str(x)): + alle_bekannt = false + p(alle_bekannt, "Alle stehen im Katalog") + var ohne_situation: Array = [] + for x in Probleme.LISTE: + if Probleme.situationen_von(str(x)).is_empty(): + ohne_situation.append(str(x)) + p(ohne_situation.is_empty(), "Jedes Problem zeigt sich in Situationen%s" % + ("" if ohne_situation.is_empty() else " — außer " + ", ".join(ohne_situation))) + var ohne_technik: Array = [] + for x in Probleme.LISTE: + if Probleme.techniken_gegen(str(x)).is_empty(): + ohne_technik.append(str(x)) + p(ohne_technik.is_empty(), "Und gegen jedes gibt es Techniken%s" % + ("" if ohne_technik.is_empty() else " — außer " + ", ".join(ohne_technik))) + + # Sie kommt von allein, aber nicht sofort + p(not Game.situation_laeuft(), "Erst läuft kein Moment") + Game.situationen_ticken(1.0, false) + p(not Game.situation_laeuft(), "Bei einem Schlafenden kommt keiner") + Game.situationen_ticken(1.0, true) + p(Game.situation_laeuft(), "Bei einem wachen Menschen bricht einer auf") + var problem := Game.situation_problem() + p(prob.has(problem), "Und gehört zu einem seiner Probleme: %s" % problem) + + # Solange sie läuft, steht der Traum still + Game.progress[id] = 10.0 + Game.traum_voranbringen(6.0) + p(is_equal_approx(Game.mission_progress(id), 10.0), + "Im Moment kommt der Traum nicht voran (%.1f %%)" % Game.mission_progress(id)) + + # Dabeisein zeigt, was dahintersteckt + p(not Game.problem_bekannt(id, problem), "Was dahintersteckt, ist noch unbekannt") + Game.situation_sehen() + p(Game.problem_bekannt(id, problem), "Dabeisein zeigt es: %s" % + Probleme.name_von(problem)) + + # Eine unpassende Technik greift nicht + var passend := "" + var unpassend := "" + for tid in TechDB.ids(): + if Probleme.hilft(tid, problem) and passend == "": + passend = tid + elif not Probleme.hilft(tid, problem) and unpassend == "": + unpassend = tid + Game.known[passend] = 0 + Game.known[unpassend] = 0 + var res_falsch := Game.situation_anwenden(unpassend) + p(res_falsch["ok"] and not res_falsch["treffer"], + "%s greift bei %s nicht" % [TechDB.by_id(unpassend)["name"], + Probleme.name_von(problem)]) + p(Game.situation_laeuft(), "Der Moment läuft weiter") + + # Die passende beendet ihn + var vorher: float = Game.mission_progress(id) + var res := Game.situation_anwenden(passend) + p(res["treffer"], "%s trägt hindurch" % TechDB.by_id(passend)["name"]) + p(not Game.situation_laeuft(), "Der Moment ist vorbei") + p(Game.mission_progress(id) > vorher, "Und bringt den Traum voran (%.0f → %.0f %%)" % + [vorher, Game.mission_progress(id)]) + p(Game.problem_fortschritt(id, problem) == 1, "Einmal geschafft") + + # Oft genug, und das Problem kommt nicht wieder + for i in range(Game.PROBLEM_UEBERWINDEN): + Game.situation = {"mensch": id, "key": Probleme.situationen_von(problem)[0], + "problem": problem, "rest": Game.SITUATION_DAUER} + Game.cooldown.clear() + Game.situation_anwenden(passend) + p(Game.problem_ueberwunden(id, problem), + "Nach %d Malen ist %s überwunden" % [Game.PROBLEM_UEBERWINDEN, + Probleme.name_von(problem)]) + Game.situation = {} + Game.situation_pause = 0.0 + var kommt_wieder := false + for i in range(200): + Game.situationen_ticken(1.0, true) + if Game.situation_laeuft() and Game.situation_problem() == problem: + kommt_wieder = true + break + p(not kommt_wieder, "Dieser Moment kommt nicht wieder") + + # Beide Ebenen stehen nebeneinander im HUD + Game.situation = {} + ui()._hud_aktualisieren() + p(ui()._ablauf.visible and ui()._probleme.visible, + "Tagesablauf und Momente stehen beide im HUD") + p(ui()._probleme.text.contains(Probleme.name_von(problem)), + "Die Momente nennen das Problem: %s" % ui()._probleme.text.replace("\n", " / ")) + Game.situation = {"mensch": id, "key": Probleme.situationen_von(problem)[0], + "problem": problem, "rest": 9.0} + ui()._hud_aktualisieren() + p(ui()._probleme.text.contains("Jetzt:"), "Der laufende Moment steht oben") + + # Und er kündigt sich am Menschen selbst an, nicht nur im HUD + var wer: Bewohner = null + for mm in welt().humans: + if mm.data["id"] == id: + wer = mm + p(wer.beschriftung().contains(str(Probleme.SITUATIONEN[Game.situation["key"]]["name"])), + "Am Menschen steht, was gerade aufbricht: %s" % + wer.beschriftung().replace("\n", " / ")) + Game.situation = {} + p(not wer.beschriftung().contains("»"), "Ohne Moment steht dort nichts davon") + # E führt in den Moment, sonst in den Tagesablauf -- die zwei Ebenen + # teilen sich eine Taste, ohne sich in die Quere zu kommen + var mensch: Bewohner = null + for mm in welt().humans: + if mm.data["id"] == id: + mensch = mm + main.player.set_mode(Player.Mode.GEIST) + main.player.folgt = null + main.player.global_position = mensch.global_position + Vector3(0, 1.5, 1.5) + Game.situation = {} + main._process(0.05) + main._interagieren() + p(ui()._kind == "anwenden", "Ohne Moment führt E in den Tagesablauf (%s)" % ui()._kind) + ui().schliessen() + Game.situation = {"mensch": id, "key": Probleme.situationen_von(problem)[0], + "problem": problem, "rest": 12.0} + main._process(0.05) + main._interagieren() + p(ui()._kind == "situation", "Im Moment führt dieselbe Taste dorthin (%s)" % ui()._kind) + p(_panel_text().contains(str(Probleme.SITUATIONEN[Game.situation["key"]]["was"])), + "Das Panel zeigt, was gerade passiert") + ui().schliessen() + + # Die Gewohnheitsebene läuft davon unberührt weiter + Game.situation = {} + Game.progress[id] = 0.0 + Game.traum_voranbringen(24.0) + p(Game.mission_progress(id) > 0.0, + "Ohne Moment liefert der Tagesablauf wieder sein Tempo (%.0f %%)" % + Game.mission_progress(id)) + + Game.situation = {} + Game.ueberwunden.clear() + Game.probleme_bekannt.clear() + Game.problem_stand.clear() + + ## Der Ablauf steht auch im HUD, ohne dass man ein Panel öffnen muss. func test_ablauf_im_hud() -> void: abschnitt("Der Ablauf steht im HUD")