This commit is contained in:
team3
2026-09-01 13:07:02 +02:00
parent ef2ecc08f9
commit 8c7da42e64
16 changed files with 1178 additions and 878 deletions

View File

@@ -23,7 +23,7 @@ var _aura: MeshInstance3D
var _strahl: MeshInstance3D
var _path: Array = []
var _letzte_aktivitaet: String = ""
var _seit_regel: float = 0.0
var slot: int = 0
var _bob: float = 0.0
var _offset: Vector3
@@ -70,13 +70,18 @@ func setup(d: Dictionary, index: int, orte: Dictionary,
global_position = Orte.center(places, place) + _offset
## [Ort, Tätigkeit, Slotnummer] zur Stunde h. Steht in dem Slot ein System,
## ist die Tätigkeit die Ersatzhandlung.
func _routine_at(h: float) -> Array:
var r: Array = data["routine"]
var cur: Array = r[r.size() - 1]
for eintrag in r:
if h >= float(eintrag[0]):
cur = eintrag
return [cur[1], cur[2]]
var nr := r.size() - 1
for i in range(r.size()):
if h >= float(r[i][0]):
nr = i
var akt: String = str(r[nr][2])
if mit_mission:
akt = Game.slot_aktivitaet(str(data["id"]), nr)
return [str(r[nr][1]), akt, nr]
func _process(roh: float) -> void:
@@ -84,6 +89,7 @@ func _process(roh: float) -> void:
resist_left = maxf(0.0, resist_left - delta)
var soll := _routine_at(Game.time_h)
slot = int(soll[2])
if soll[0] != place:
var war_unterwegs := not _path.is_empty()
_path = Orte.route(places, place, soll[0], _offset, netz, anker)
@@ -117,16 +123,8 @@ func _process(roh: 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))
# Solange der Moment anhält, greift die Regel erneut, sobald ihre Technik
# wieder frei ist. Der Cooldown gibt damit den Takt vor.
_seit_regel += delta
if activity != _letzte_aktivitaet:
_letzte_aktivitaet = activity
_seit_regel = 0.0
_regel_pruefen()
elif _seit_regel >= 1.0:
_seit_regel = 0.0
_regel_pruefen()
var erledigt: bool = mit_mission and Game.completed.has(data["id"])
var aktiv: bool = mit_mission and Game.active_mission == data["id"]
@@ -158,20 +156,6 @@ 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,

View File

@@ -21,9 +21,10 @@ const FEHLVERSUCH_VERLUST := 14.0 # ein Fehlschlag wirft den Traum zurück
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 cooldown: Dictionary = {} # tech_id -> Restsekunden
var pipeline: Dictionary = {} # human_id -> {activity -> tech_id}
var systeme: Dictionary = {} # human_id -> {slot -> {tech, guete}}
var erkannt_slots: Dictionary = {} # human_id -> {slot -> true}
var beobachtung: Dictionary = {} # "human:slot" -> Sekunden
var completed: Array = []
var time_h: float = 7.0
var day: int = 1
@@ -113,15 +114,6 @@ 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:
@@ -261,25 +253,12 @@ func angenommen(human_id: String) -> bool:
return progress.has(human_id)
## Eine Mission ist machbar, wenn mindestens eine gelernte Technik das Thema
## des Menschen trifft und in seinem Tag auch einen Moment dafür findet.
## Nur solche werden am Schwarzen Brett überhaupt ausgehängt.
## Eine Mission ist machbar, wenn mindestens eine ihrer Ursachen mit einer
## gelernten Technik anzugehen ist. Die übrigen bremsen dann eben noch.
func machbar(human_id: String) -> bool:
var h := HumanDB.by_id(human_id)
if h.is_empty():
return false
var tag_liste: Array = HumanDB.aktivitaeten(human_id)
for id in known:
var t := TechDB.by_id(id)
var thema := false
for tag in t["tags"]:
if (h["tags"] as Array).has(tag):
thema = true
break
if not thema:
continue
for a in t["momente"]:
if tag_liste.has(a) and erreichbar(a):
for u in ursachen_von(human_id):
for id in known:
if str(TechDB.by_id(id)["ursache"]) == str(u):
return true
return false
@@ -317,161 +296,188 @@ func _insel_waechst() -> void:
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.
# --------------------------------------------------------------------- Systeme
# Ein Traum scheitert an Ursachen. Die sind verdeckt und zeigen sich nur im
# Tagesablauf. Wer sie gefunden hat, baut Systeme dagegen: in einen freien
# Slot ein neues, oder auf einen Blocker ein besseres. Je mehr Ursachen
# abgedeckt sind, desto schneller kommt der Traum voran.
func regeln(human_id: String) -> Dictionary:
return pipeline.get(human_id, {})
const BASIS_TEMPO := 45.0 # Prozent je Tag, wenn nichts mehr bremst
const GUETE_ERSATZ := 1.35 # Blocker mit gleicher Belohnung ersetzt
const GUETE_NEU := 1.0 # System in einen freien Slot gesetzt
const ERKENNEN_SEK := 3.0 # so lange muss man dabeistehen
func regel_fuer(human_id: String, activity: String) -> String:
return regeln(human_id).get(activity, "")
func slots(human_id: String) -> Array:
var h := HumanDB.by_id(human_id)
return h["routine"] if h.has("routine") else []
func regel_anzahl(human_id: String) -> int:
return regeln(human_id).size()
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 ""
## An welchen Tätigkeiten hängt diese Technik gerade?
func regel_momente(human_id: String, tech_id: String) -> Array:
var out: Array = []
for activity in regeln(human_id):
if regeln(human_id)[activity] == tech_id:
out.append(activity)
return out
## Was in diesem Slot wirklich passiert: die Ersatzhandlung, wenn ein System
## steht -- sonst das, was von Haus aus dort steht.
func slot_aktivitaet(human_id: String, nr: int) -> String:
var sys := system_in(human_id, nr)
if not sys.is_empty():
return str(TechDB.by_id(str(sys["tech"]))["ersatz"])
return slot_grund(human_id, nr)
## Leere tech_id löscht die Regel. Eine Technik darf an beliebig vielen
## Tätigkeiten hängen — ob das klug ist, muss der Spieler aus ihr herauslesen.
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:
r[activity] = tech_id
func system_in(human_id: String, nr: int) -> Dictionary:
return systeme.get(human_id, {}).get(nr, {})
# ------------------------------------------------------------------- Erkennen
func erkannt(human_id: String, nr: int) -> bool:
if not Ursachen.ist_blocker(slot_grund(human_id, nr)):
return true
return erkannt_slots.get(human_id, {}).has(nr)
## Dabeistehen deckt auf, was dort wirklich läuft -- und damit die Ursache.
func beobachten(human_id: String, nr: int, delta: float) -> bool:
if erkannt(human_id, nr):
return false
var schluessel := "%s:%d" % [human_id, nr]
var stand: float = float(beobachtung.get(schluessel, 0.0)) + delta
beobachtung[schluessel] = stand
if stand < ERKENNEN_SEK:
return false
if not erkannt_slots.has(human_id):
erkannt_slots[human_id] = {}
erkannt_slots[human_id][nr] = true
var b := slot_grund(human_id, nr)
notified.emit("Erkannt: %s — %s" % [Ursachen.BLOCKER[b]["name"],
Ursachen.LISTE[Ursachen.ursache_von(b)]["name"]], Color(0.8, 0.95, 1))
state_changed.emit()
return true
## Greift für diese Tätigkeit eine Regel? Wird nicht nur beim Wechsel gefragt,
## sondern immer wieder — solange der Moment anhält und die Technik frei ist.
func regel_ausloesen(human_id: String, activity: String, resisting: bool) -> Dictionary:
if human_id != active_mission:
return {}
var tech_id := regel_fuer(human_id, activity)
if tech_id == "" or not known.has(tech_id):
return {}
## Eine Ursache gilt als erkannt, sobald eine ihrer Tätigkeiten aufgedeckt ist.
## Nur eine Quelle -- sonst driften Slot und Ursache auseinander.
func ursache_bekannt(human_id: String, ursache: String) -> bool:
for nr in erkannt_slots.get(human_id, {}):
if Ursachen.ursache_von(slot_grund(human_id, int(nr))) == ursache:
return true
return false
func ursachen_von(human_id: String) -> Array:
var h := HumanDB.by_id(human_id)
return h["ursachen"] if h.has("ursachen") else []
# -------------------------------------------------------------------- Tempo
## Steht gegen diese Ursache ein System? Gibt die Güte zurück, sonst 0.
func ursache_geloest(human_id: String, ursache: String) -> float:
var beste := 0.0
for nr in systeme.get(human_id, {}):
var sys: Dictionary = systeme[human_id][nr]
if str(TechDB.by_id(str(sys["tech"]))["ursache"]) == ursache:
beste = maxf(beste, float(sys["guete"]))
return beste
## Wie schnell kommt der Traum voran? Jede ungelöste Ursache bremst.
func traum_tempo(human_id: String) -> float:
var t := BASIS_TEMPO
for u in ursachen_von(human_id):
var g := ursache_geloest(human_id, str(u))
t *= g if g > 0.0 else float(Ursachen.LISTE[u]["faktor"])
return t
# ------------------------------------------------------------- System setzen
## Passt diese Technik in diesen Slot? Gibt Güte und Grund zurück.
func system_pruefen(human_id: String, nr: int, tech_id: String) -> Dictionary:
var res := {"geht": false, "guete": 0.0, "grund": ""}
if not known.has(tech_id):
res["grund"] = "Diese Technik kennst du nicht."
return res
if not bereit(tech_id):
return {} # still: die Technik greift wieder, sobald ihre Sperre fällt
var res := apply_technique(tech_id, human_id, activity, resisting)
if not res["ok"]:
return {}
res["tech"] = tech_id
res["grund"] = "%s ist noch nicht wieder einsatzbereit." % TechDB.by_id(tech_id)["name"]
return res
var grund_akt := slot_grund(human_id, nr)
if grund_akt == "schlaeft":
res["grund"] = "Im Schlaf lässt sich nichts einrichten."
return res
if not erkannt(human_id, nr):
res["grund"] = "Was hier läuft, hast du noch nicht gesehen."
return res
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 %%)" %
[t["name"], Orte.text(activity), int(round(float(res["verlust"])))],
Color(1, 0.7, 0.5))
if Ursachen.ist_blocker(grund_akt):
var b: Dictionary = Ursachen.BLOCKER[grund_akt]
if str(t["ursache"]) != str(b["ursache"]):
res["grund"] = "Das hier hat einen anderen Grund."
return res
res["geht"] = true
var gleich: bool = str(t["belohnung"]) == str(b["belohnung"])
res["guete"] = GUETE_ERSATZ if gleich or TechDB.entfernt_ausloeser(tech_id) else GUETE_NEU
return res
res["geht"] = true
res["guete"] = GUETE_NEU
return res
# ----------------------------------------------------- Technik auf Mensch anwenden
## Richtet das System ein. Gelingt nicht immer -- Übung entscheidet.
func system_setzen(human_id: String, nr: int, tech_id: String, widerstand: bool) -> Dictionary:
var pruef := system_pruefen(human_id, nr, tech_id)
if not pruef["geht"]:
return {"ok": false, "grund": pruef["grund"]}
cooldown[tech_id] = TECHNIK_COOLDOWN
var chance := clampf(0.20 + 0.13 * float(skill_of(tech_id)) - (0.25 if widerstand else 0.0),
0.05, 0.95)
var erfolg := randf() < chance
if not erfolg:
add_xp(tech_id, 1)
state_changed.emit()
return {"ok": true, "erfolg": false, "guete": 0.0}
if not systeme.has(human_id):
systeme[human_id] = {}
systeme[human_id][nr] = {"tech": tech_id, "guete": float(pruef["guete"])}
add_xp(tech_id, 2)
notified.emit("System steht: %s um %02d:00" %
[TechDB.by_id(tech_id)["name"], int(slots(human_id)[nr][0])], Color(0.6, 1, 0.7))
state_changed.emit()
return {"ok": true, "erfolg": true, "guete": float(pruef["guete"])}
## Schlafende erreicht keine Technik — weder von Hand noch über eine Regel.
func system_entfernen(human_id: String, nr: int) -> void:
if systeme.has(human_id):
systeme[human_id].erase(nr)
state_changed.emit()
func system_anzahl(human_id: String) -> int:
return systeme.get(human_id, {}).size()
## Der Traum kommt voran, solange Systeme stehen.
func traum_voranbringen(stunden: float) -> void:
if active_mission == "":
return
var p: float = progress.get(active_mission, 0.0) \
+ traum_tempo(active_mission) * stunden / 24.0
progress[active_mission] = minf(p, MISSION_ZIEL)
if progress[active_mission] >= MISSION_ZIEL and not completed.has(active_mission):
var wer := active_mission
completed.append(wer)
active_mission = ""
mission_done.emit(wer)
notified.emit("%s hat es geschafft." % HumanDB.by_id(wer)["name"], Color(1, 0.85, 0.4))
_insel_waechst()
state_changed.emit()
# --------------------------------------------------- Erreichbarkeit
## Schlafende erreicht niemand.
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)
var h := HumanDB.by_id(human_id)
var skill := skill_of(tech_id)
var res := {
"chance": 0.0, "tag": false, "moment": false, "blocked": false,
"grund": "", "skill": skill,
}
if not erreichbar(activity):
res["blocked"] = true
res["grund"] = "Sie oder er schläft. Techniken erreichen nur wache Menschen."
return res
if skill == 0:
res["blocked"] = true
res["grund"] = "Technik nicht gelernt."
return res
for tag in t["tags"]:
if h["tags"].has(tag):
res["tag"] = true
break
res["moment"] = t["momente"].has(activity)
# Der Moment wiegt am schwersten: eine Technik zur falschen Zeit greift kaum,
# egal wie gut sie zum Menschen passt. Genau das steht in ihrem "Wann".
var c := 0.05 + 0.09 * float(skill)
if res["tag"]:
c += 0.20
if res["moment"]:
c += 0.35
if resisting:
c -= 0.30
res["grund"] = "Der Mensch macht gerade dicht."
res["chance"] = clampf(c, 0.05, 0.95)
return res
## Wendet die Technik wirklich an: würfelt, verteilt EP und Fortschritt.
func apply_technique(tech_id: String, human_id: String, activity: String, resisting: bool) -> Dictionary:
if not bereit(tech_id):
return {"ok": false, "grund": "%s ist noch nicht wieder einsatzbereit." %
TechDB.by_id(tech_id)["name"]}
var ev := evaluate(tech_id, human_id, activity, resisting)
if ev["blocked"]:
return {"ok": false, "grund": ev["grund"]}
cooldown[tech_id] = TECHNIK_COOLDOWN
var roll := randf()
var success: bool = roll < float(ev["chance"])
var key := "%s:%s" % [human_id, tech_id]
var wiederholung: int = used.get(key, 0)
used[key] = wiederholung + 1
var gain := 0.0
var verlust := 0.0
if success:
var base := 8.0 + 2.0 * float(ev["skill"])
if ev["tag"]:
base += 5.0
if ev["moment"]:
base += 5.0
gain = base / (1.0 + 0.6 * float(wiederholung))
add_xp(tech_id, 2)
else:
verlust = FEHLVERSUCH_VERLUST
add_xp(tech_id, 1)
var done := false
if human_id == active_mission:
var p: float = progress.get(human_id, 0.0) + gain - verlust
progress[human_id] = clampf(p, 0.0, MISSION_ZIEL)
if progress[human_id] >= MISSION_ZIEL and not completed.has(human_id):
completed.append(human_id)
active_mission = ""
done = true
mission_done.emit(human_id)
notified.emit("%s hat es geschafft." % HumanDB.by_id(human_id)["name"],
Color(1, 0.85, 0.4))
_insel_waechst()
state_changed.emit()
return {
"ok": true, "success": success, "chance": ev["chance"], "gain": gain,
"verlust": verlust, "wiederholung": wiederholung, "mission_done": done,
"tag": ev["tag"], "moment": ev["moment"],
}

View File

@@ -101,32 +101,20 @@ static var ANLIEGEN: Array = [
},
]
## 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"]],
## Der Grundtag. Blocker werden später in passende Fenster gesetzt.
## [Stunde, Ort, Tätigkeit, Fenster]
static var GRUNDTAG: Array = [
[0, "heim", "schlaeft", "nacht"],
[7, "heim", "morgenroutine", "morgen"],
[9, "arbeit", "arbeitet", "tag"],
[12, "cafe", "essen", "tag"],
[14, "arbeit", "arbeitet", "tag"],
[17, "park", "unterwegs", "abend"],
[19, "heim", "ruht", "abend"],
[21, "heim", "ruht", "abend"],
[23, "heim", "abendroutine", "nacht"],
]
## Zu jeder Technik ein Anliegen und ein Tagesablauf, bei dem sie greift:
## [Anliegen, Ablauf]. Mensch i bekommt den Eintrag zu Technik i — und Technik i
## ist genau dann entdeckt, wenn i Träume erfüllt sind. So hängt am Schwarzen
## Brett immer mindestens ein Mensch, dem mit dem Gelernten zu helfen ist.
static var PASSEND: Array = [
[0, 0], # Pomodoro -> aufschieben, zögert
[6, 4], # Zwei-Minuten -> Werkstatt, räumt auf
@@ -403,25 +391,68 @@ static func _anker_setzen() -> void:
ANKER[key] = int(NETZ.andocken(tuer)["knoten"])
static func _mensch(i: int) -> Dictionary:
# Bis alle Wege entdeckt sind, folgt jeder Mensch dem Weg gleicher Nummer.
# Danach mischt sich das Feld wieder.
var a_nr: int = i % ANLIEGEN.size()
var b_nr: int = i % ABLAEUFE.size()
if i < PASSEND.size():
a_nr = int(PASSEND[i][0])
b_nr = int(PASSEND[i][1])
var anliegen: Dictionary = ANLIEGEN[a_nr]
var heim := heim_key(i)
var arbeit := _naechster_arbeitsplatz(heim, i)
var ablauf: Array = []
for e in ABLAEUFE[b_nr]:
## 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.
static func ursachen_von(i: int) -> Array:
var alle: Array = Ursachen.LISTE.keys()
var erste: String = str(alle[i % alle.size()])
if i < TechDB.LIST.size():
erste = str(TechDB.LIST[i]["ursache"])
var out: Array = [erste]
var zweite: String = str(alle[(alle.find(erste) + 1 + i % 3) % alle.size()])
if zweite != erste:
out.append(zweite)
if i % 3 == 2:
var dritte: String = str(alle[(alle.find(erste) + 3) % alle.size()])
if not out.has(dritte):
out.append(dritte)
return out
## Der Tagesablauf: Grundtag, in den je Ursache ein Blocker gesetzt wird.
static func _tagesablauf(i: int, heim: String, arbeit: String) -> Array:
var slots: Array = []
for e in GRUNDTAG:
var ort: String = str(e[1])
if ort == "heim":
ort = heim
elif ort == "arbeit":
ort = arbeit
ablauf.append([e[0], ort, e[2]])
slots.append([int(e[0]), ort, str(e[2]), str(e[3])])
var belegt: Dictionary = {}
var nr := 0
for u in ursachen_von(i):
for fenster in ["tag", "abend", "morgen", "nacht"]:
var kandidaten: Array = Ursachen.blocker_fuer(str(u), fenster)
if kandidaten.is_empty():
continue
var blocker: String = str(kandidaten[(i + nr) % kandidaten.size()])
var gesetzt := false
for k in range(slots.size()):
if belegt.has(k) or str(slots[k][2]) == "schlaeft":
continue
if str(slots[k][3]) != fenster:
continue
slots[k][2] = blocker
belegt[k] = true
gesetzt = true
break
if gesetzt:
break
nr += 1
var out: Array = []
for e in slots:
out.append([e[0], e[1], e[2]])
return out
static func _mensch(i: int) -> Dictionary:
var anliegen: Dictionary = ANLIEGEN[i % ANLIEGEN.size()]
if i < PASSEND.size():
anliegen = ANLIEGEN[int(PASSEND[i][0])]
var heim := heim_key(i)
var arbeit := _naechster_arbeitsplatz(heim, i)
return {
"id": "mensch_%d" % i,
"name": "%s %s" % [_vorname(i), _nachname(i)],
@@ -430,10 +461,11 @@ static func _mensch(i: int) -> Dictionary:
"traum": str(anliegen["traum"]),
"problem": str(anliegen["problem"]),
"tags": (anliegen["tags"] as Array).duplicate(),
"ursachen": ursachen_von(i),
"mission": true,
"heim": heim,
"farbe": Color.from_hsv(fmod(0.02 + float(i) * 0.191, 1.0), 0.5, 0.8),
"routine": ablauf,
"routine": _tagesablauf(i, heim, arbeit),
}

View File

@@ -65,15 +65,28 @@ func _setze_welt(neu: Node3D) -> void:
func _process(delta: float) -> void:
Game.advance_time(delta * Game.zeit_faktor())
var dt := delta * Game.zeit_faktor()
Game.advance_time(dt)
Game.traum_voranbringen(dt * 24.0 / Game.DAY_SECONDS)
Game.teleport_lock = maxf(0.0, Game.teleport_lock - delta)
if ui.offen:
ui.setze_hinweis("")
return
_fokus_suchen()
_beobachten(dt)
_hinweis_setzen()
## Dabeistehen deckt auf, was der Mensch gerade wirklich tut.
func _beobachten(dt: float) -> void:
if _fokus_mensch == null or not auf_erde:
return
var id: String = str(_fokus_mensch.data["id"])
if id != Game.active_mission:
return
Game.beobachten(id, int(_fokus_mensch.slot), dt)
func _fokus_suchen() -> void:
_fokus = null
_fokus_mensch = null
@@ -99,8 +112,13 @@ func _fokus_suchen() -> void:
func _hinweis_setzen() -> void:
if _fokus_mensch != null:
ui.setze_hinweis("[E] %s ansprechen — %s" %
[_fokus_mensch.data["name"], Orte.text(_fokus_mensch.activity)])
var mid: String = str(_fokus_mensch.data["name"])
var mnr: int = int(_fokus_mensch.slot)
var mkey: String = str(_fokus_mensch.data["id"])
if mkey == Game.active_mission and not Game.erkannt(mkey, mnr):
ui.setze_hinweis("%s — schau zu, was sie hier tut …" % mid)
else:
ui.setze_hinweis("[E] %s — %s" % [mid, Orte.text(_fokus_mensch.activity)])
elif _fokus != null:
var kind: String = _fokus.get_meta("kind")
var titel: String = _fokus.get_meta("titel")

View File

@@ -37,8 +37,15 @@ static var ACTIVITY_TEXT: Dictionary = {
}
## Blocker tragen ihren Namen im Ursachen-Katalog, alles andere hier.
static func text(a: String) -> String:
return ACTIVITY_TEXT.get(a, a)
if Ursachen.ist_blocker(a):
return str(Ursachen.BLOCKER[a]["name"])
return str(ACTIVITY_TEXT.get(a, a))
static func kennt(a: String) -> bool:
return ACTIVITY_TEXT.has(a) or Ursachen.ist_blocker(a)
static func center(places: Dictionary, key: String) -> Vector3:

View File

@@ -9,7 +9,10 @@ static var LIST: Array = [
"was": "25 Minuten am Stück an einer Sache arbeiten, dann 5 Minuten Pause. Nach vier Runden eine lange Pause.",
"wann": "Wenn eine Aufgabe zu groß wirkt und der Anfang immer wieder verschoben wird.",
"warum": "Ein klar begrenzter Zeitraum senkt die Einstiegshürde. Der Kopf muss nur 25 Minuten durchhalten, nicht den ganzen Tag.",
"woran": "jemandem, der zögert — oder der arbeitet und trotzdem nicht vorankommt.",
"woran": "jemandem, der aufräumt oder recherchiert, statt endlich anzufangen.",
"ursache": "anfang",
"belohnung": "erfolg",
"ersatz": "arbeitet in Blöcken",
"tags": ["prokrastination", "fokus"],
"momente": ["arbeitet", "zoegert"],
"farbe": Color(0.95, 0.45, 0.30),
@@ -20,7 +23,10 @@ static var LIST: Array = [
"was": "Was unter zwei Minuten dauert, sofort erledigen. Große Vorhaben auf einen Zwei-Minuten-Anfang schrumpfen.",
"wann": "Bei einem Berg von Kleinkram oder wenn schon der erste Schritt blockiert.",
"warum": "Der Start kostet die meiste Energie. Zwei Minuten sind zu klein zum Ablehnen und ziehen meist die ganze Aufgabe nach sich.",
"woran": "jemandem, der zögert, lieber aufräumt als anzufangen, oder beim Arbeiten im Kleinkram hängt.",
"woran": "jemandem, der aufräumt statt anzufangen oder eine Später-Liste schreibt.",
"ursache": "anfang",
"belohnung": "erfolg",
"ersatz": "fängt zwei Minuten an",
"tags": ["prokrastination", "ueberforderung"],
"momente": ["zoegert", "aufraeumen", "arbeitet"],
"farbe": Color(0.98, 0.72, 0.25),
@@ -31,7 +37,10 @@ static var LIST: Array = [
"was": "Den Vorsatz an einen konkreten Auslöser binden: \"Wenn ich X tue, dann mache ich Y.\"",
"wann": "Wenn ein Vorhaben im Alltag jedes Mal untergeht.",
"warum": "Der Plan hängt an einer Situation statt an Willenskraft. Der Auslöser erinnert von allein.",
"woran": "der Morgenroutine und der Pause — den festen Punkten, an die sich ein Vorsatz hängen lässt.",
"woran": "jemandem, der eine Später-Liste schreibt und dann doch nichts tut.",
"ursache": "anfang",
"belohnung": "ordnung",
"ersatz": "folgt dem Wenn-Dann-Plan",
"tags": ["gewohnheit", "prokrastination"],
"momente": ["morgenroutine", "pause"],
"farbe": Color(0.45, 0.80, 0.55),
@@ -42,7 +51,10 @@ static var LIST: Array = [
"was": "Eine neue, kleine Gewohnheit direkt an eine bestehende anhängen.",
"wann": "Wenn eine neue Routine nie einen Platz im Tag findet.",
"warum": "Die alte Gewohnheit ist der Anker. Es muss kein neuer Zeitpunkt erfunden werden.",
"woran": "den Ankern des Tages: Morgenroutine, Essen, Abendroutine.",
"woran": "jemandem, der auf etwas anderes umschaltet, sobald es zäh wird.",
"ursache": "halt",
"belohnung": "ordnung",
"ersatz": "hängt es an die Routine",
"tags": ["gewohnheit"],
"momente": ["morgenroutine", "essen", "abendroutine"],
"farbe": Color(0.40, 0.70, 0.85),
@@ -53,7 +65,10 @@ static var LIST: Array = [
"was": "Ein großes Ziel in benennbare Einzelschritte teilen, bis der nächste Schritt trivial ist.",
"wann": "Wenn jemand vor einem Vorhaben erstarrt und nicht weiß, wo er anfangen soll.",
"warum": "Unklarheit lähmt stärker als Aufwand. Ein sichtbarer nächster Schritt macht Handeln wieder möglich.",
"woran": "jemandem, der grübelt statt anzufangen, oder der arbeitet und sich im Vorhaben verliert.",
"woran": "jemandem, der recherchiert statt anzufangen oder eine Später-Liste schreibt.",
"ursache": "anfang",
"belohnung": "ordnung",
"ersatz": "zerlegt die Aufgabe",
"tags": ["ueberforderung", "prokrastination", "ziel"],
"momente": ["gruebelt", "arbeitet"],
"farbe": Color(0.75, 0.55, 0.95),
@@ -64,7 +79,10 @@ static var LIST: Array = [
"was": "Das gewünschte Verhalten leichter machen, das störende schwerer. Handy in einen anderen Raum, Instrument sichtbar aufstellen.",
"wann": "Wenn gute Vorsätze regelmäßig an Ablenkung scheitern.",
"warum": "Verhalten folgt dem geringsten Widerstand. Die Umgebung wirkt zuverlässiger als Disziplin.",
"woran": "jemandem, der aufräumt, arbeitet oder sich ausruht — dann ist der Raum um ihn herum greifbar.",
"woran": "jemandem, der noch eine Runde spielt oder ständig umschaltet.",
"ursache": "halt",
"belohnung": "",
"ersatz": "hat es sich leicht gemacht",
"tags": ["ablenkung", "gewohnheit"],
"momente": ["aufraeumen", "arbeitet", "ruht"],
"farbe": Color(0.55, 0.85, 0.40),
@@ -75,7 +93,10 @@ static var LIST: Array = [
"was": "Einen belastenden Gedanken prüfen und durch eine ebenso wahre, aber tragfähigere Sicht ersetzen.",
"wann": "Wenn jemand in einem Gedanken feststeckt, der ihn klein hält.",
"warum": "Nicht das Ereignis belastet, sondern seine Deutung. Eine andere Deutung verändert Gefühl und Handlung.",
"woran": "jemandem, der grübelt, angespannt ist oder sich ausruht und den Gedanken doch nicht loswird.",
"woran": "jemandem, der sich mit anderen vergleicht und dabei klein wird.",
"ursache": "grund",
"belohnung": "einordnung",
"ersatz": "deutet den Gedanken um",
"tags": ["selbstzweifel", "angst"],
"momente": ["gruebelt", "ruht", "angespannt"],
"farbe": Color(0.95, 0.55, 0.75),
@@ -86,7 +107,10 @@ static var LIST: Array = [
"was": "Vier Sekunden einatmen, vier halten, vier ausatmen, vier halten. Mehrere Runden lang.",
"wann": "Bei akuter Anspannung, kurz vor einem gefürchteten Gespräch oder Auftritt.",
"warum": "Langes Ausatmen bremst den Alarmzustand des Körpers. Der Puls sinkt, der Kopf wird wieder klar.",
"woran": "jemandem, der angespannt ist, wartet oder in der Pause nicht runterkommt.",
"woran": "jemandem, der abends eine Serie schaut, um überhaupt runterzukommen.",
"ursache": "kraft",
"belohnung": "abschalten",
"ersatz": "atmet sich ruhig",
"tags": ["angst", "stress"],
"momente": ["angespannt", "wartet", "pause"],
"farbe": Color(0.40, 0.85, 0.95),
@@ -97,7 +121,10 @@ static var LIST: Array = [
"was": "Mit sich selbst so sprechen wie mit einem guten Freund in derselben Lage.",
"wann": "Nach einem Fehler oder Rückschlag, wenn die innere Kritik lauter wird als der Vorsatz.",
"warum": "Harte Selbstkritik erhöht die Vermeidung. Freundlichkeit hält handlungsfähig und senkt die Abbruchquote.",
"woran": "jemandem, der nach einem Fehler grübelt, angespannt ist oder sich ausruht, ohne zur Ruhe zu kommen.",
"woran": "jemandem, der am Telefon klagt, statt weiterzumachen.",
"ursache": "grund",
"belohnung": "verbundenheit",
"ersatz": "ist freundlich mit sich",
"tags": ["selbstzweifel", "rueckschlag"],
"momente": ["gruebelt", "ruht", "angespannt"],
"farbe": Color(1.0, 0.80, 0.60),
@@ -108,7 +135,10 @@ static var LIST: Array = [
"was": "Feste Aufsteh- und Schlafenszeit, die letzte Stunde ohne Bildschirm, Zimmer dunkel und kühl.",
"wann": "Bei Dauermüdigkeit, Reizbarkeit und wegbrechender Konzentration.",
"warum": "Der innere Rhythmus braucht feste Anker. Erholter Schlaf ist die Grundlage für jede andere Veränderung.",
"woran": "der Abendroutine, an jemandem, der müde ist oder sich mitten am Tag ausruht.",
"woran": "jemandem, der wach bleibt oder abends am Kühlschrank steht.",
"ursache": "kraft",
"belohnung": "zeit_fuer_sich",
"ersatz": "hält das Schlaffenster",
"tags": ["erschoepfung", "gesundheit"],
"momente": ["abendroutine", "muede", "ruht"],
"farbe": Color(0.45, 0.50, 0.90),
@@ -119,7 +149,10 @@ static var LIST: Array = [
"was": "Eine klare, kleine Bitte an eine konkrete Person richten, statt allgemein zu hoffen.",
"wann": "Wenn jemand allein an etwas hängen bleibt, das andere leicht lösen könnten.",
"warum": "Vage Bitten werden überhört. Menschen helfen gern, wenn sie genau wissen, was zu tun ist.",
"woran": "jemandem, der wartet, Pause macht oder unterwegs ist — da ist Luft für eine Bitte.",
"woran": "jemandem, der zu allem ja sagt und alles allein stemmt.",
"ursache": "zeit",
"belohnung": "anschluss",
"ersatz": "bittet um Hilfe",
"tags": ["einsamkeit", "ueberforderung"],
"momente": ["wartet", "pause", "unterwegs"],
"farbe": Color(0.90, 0.90, 0.45),
@@ -130,7 +163,10 @@ static var LIST: Array = [
"was": "Absagen und zugleich benennen, was ginge: \"Heute nicht, am Donnerstag gern.\"",
"wann": "Wenn der eigene Kalender von fremden Anliegen aufgebraucht wird.",
"warum": "Ein nacktes Nein fühlt sich riskant an. Mit Alternative bleibt die Beziehung heil und die Zeit geschützt.",
"woran": "jemandem, der arbeitet, angespannt ist oder unterwegs zwischen Terminen hängt.",
"woran": "jemandem, der zu allem ja sagt, bis für ihn nichts übrig bleibt.",
"ursache": "zeit",
"belohnung": "anerkennung",
"ersatz": "sagt Nein mit Alternative",
"tags": ["stress", "grenzen"],
"momente": ["arbeitet", "angespannt", "unterwegs"],
"farbe": Color(0.85, 0.35, 0.45),
@@ -138,6 +174,21 @@ static var LIST: Array = [
]
## Techniken, die gegen diese Ursache helfen.
static func fuer_ursache(ursache: String) -> Array:
var out: Array = []
for t in LIST:
if t["ursache"] == ursache:
out.append(t["id"])
return out
## Manche Techniken ersetzen die Handlung nicht, sie nehmen ihr den Auslöser.
## Die passen überall, wo ihre Ursache stimmt.
static func entfernt_ausloeser(id: String) -> bool:
return str(by_id(id).get("belohnung", "")) == ""
static func by_id(id: String) -> Dictionary:
for t in LIST:
if t["id"] == id:

View File

@@ -154,9 +154,9 @@ func _hud_aktualisieren() -> void:
[Game.known.size(), Game.offene_techniken(), TechDB.LIST.size()])
if Game.active_mission != "":
var h := HumanDB.by_id(Game.active_mission)
zeilen.append("Mission: [color=#ffdd77]%s[/color] — %d %% · %d Regeln" %
zeilen.append("Mission: [color=#ffdd77]%s[/color] — %d %% · %d %% je Tag" %
[h["name"], int(Game.mission_progress(Game.active_mission)),
Game.regel_anzahl(Game.active_mission)])
int(round(Game.traum_tempo(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]")
@@ -347,84 +347,126 @@ func _journal_missionen() -> void:
_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))
_knopf("Tagesablauf — %d Systeme" % Game.system_anzahl(id),
func() -> void: systeme_panel(id))
_trenner()
# -------------------------------------------------------- Regelwerk einer Mission
# ---------------------------------------------- Diagnose und Systeme
## 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
## Der Traum scheitert an Ursachen. Die stehen nicht im Panel -- man findet sie,
## indem man dabeisteht. Gegen jede gefundene Ursache baut man ein System in
## den Tag. Je mehr abgedeckt ist, desto schneller kommt der Traum voran.
func systeme_panel(human_id: String) -> void:
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "systeme" 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.\n" +
"Nach jedem Einsatz braucht eine Technik Ruhe, bevor sie wieder anschlägt.", 15)
_text("[color=#aaaaaa]Fortschritt: [b]%d %%[/b] · Regeln: [b]%d[/b][/color]" %
[int(Game.mission_progress(human_id)), Game.regel_anzahl(human_id)], 15)
_leeren("%s — Tagesablauf" % h["name"])
_text("[b]Traum:[/b] %s" % h["traum"], 16)
_text("[b]Fortschritt:[/b] %d %% · [b]Tempo:[/b] %d %% je Tag" %
[int(Game.mission_progress(human_id)), int(round(Game.traum_tempo(human_id)))], 16)
_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)
_text("[b]Woran es scheitert[/b]", 17)
var offene := 0
for u in Game.ursachen_von(human_id):
var name: String = str(u)
if not Game.ursache_bekannt(human_id, name):
_text("[color=#8e93a8]? — noch nicht erkannt[/color]", 15)
offene += 1
continue
var tech: String = Game.regel_fuer(human_id, activity)
var beschriftung := "%s → keine Regel" % tat
if tech != "":
beschriftung = "%s → %s" % [tat, TechDB.by_id(tech)["name"]]
if not Game.bereit(tech):
beschriftung += " (noch gesperrt)"
_knopf(beschriftung, func() -> void: regel_panel(human_id, activity))
var g := Game.ursache_geloest(human_id, name)
var stand := "[color=#88ff99]gelöst[/color]" if g > 0.0 else \
"[color=#ff9977]offen · bremst auf %d %%[/color]" % \
int(float(Ursachen.LISTE[name]["faktor"]) * 100.0)
_text("[b]%s[/b] — %s %s" % [Ursachen.LISTE[name]["name"],
Ursachen.LISTE[name]["kurz"], stand], 15)
if offene > 0:
_text("[color=#aaaaaa]Steh dabei, wenn sie etwas tut, das du noch nicht " +
"kennst. Dann zeigt sich, warum.[/color]", 14)
_trenner()
_text("[b]Ihr Tag[/b]", 17)
for nr in range(Game.slots(human_id).size()):
_slot_zeile(human_id, nr)
_trenner()
_knopf("Zurück zum Journal", func() -> void: journal_panel("missionen"))
_zeigen("pipeline")
_zeigen("systeme")
_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")
func _slot_zeile(human_id: String, nr: int) -> void:
var slot: Array = Game.slots(human_id)[nr]
var stunde := "%02d:00" % int(slot[0])
var grund := Game.slot_grund(human_id, nr)
if grund == "schlaeft":
_knopf("%s schläft" % stunde, func() -> void: pass, false)
return
_text("Was soll greifen, wenn %s %s? Ob eine Technik hier taugt, steht in ihr " %
[h["name"], Orte.text(activity)] +
"selbst — nachzulesen im Journal. Ein Fehlgriff wirft den Traum zurück.", 15)
var sys := Game.system_in(human_id, nr)
if not sys.is_empty():
_knopf("%s %s ← %s" % [stunde, Game.slot_aktivitaet(human_id, nr),
TechDB.by_id(str(sys["tech"]))["name"]],
func() -> void: slot_panel(human_id, nr))
return
if not Game.erkannt(human_id, nr):
_knopf("%s ?" % stunde, func() -> void: pass, false)
return
var text := Orte.text(grund)
if Ursachen.ist_blocker(grund):
text = "%s (%s)" % [Ursachen.BLOCKER[grund]["name"],
Ursachen.LISTE[Ursachen.BLOCKER[grund]["ursache"]]["name"]]
_knopf("%s %s" % [stunde, text], func() -> void: slot_panel(human_id, nr))
## Was soll in diesem Slot stehen?
func slot_panel(human_id: String, nr: int) -> void:
var h := HumanDB.by_id(human_id)
var grund := Game.slot_grund(human_id, nr)
var stunde := "%02d:00" % int(Game.slots(human_id)[nr][0])
_leeren("%s um %s" % [h["name"], stunde])
if Ursachen.ist_blocker(grund):
var b: Dictionary = Ursachen.BLOCKER[grund]
_text("Sie %s.\n[b]Auslöser:[/b] %s\n[b]Belohnung:[/b] %s" %
[b["name"], b["ausloeser"], Ursachen.belohnung_text(str(b["belohnung"]))], 16)
_text("[color=#aaaaaa]Eine Ersatzhandlung muss dieselbe Belohnung liefern, " +
"sonst hält sie nicht.[/color]", 15)
else:
_text("Hier ist Platz. Ein neues System passt in diesen Slot.", 16)
_trenner()
var sys := Game.system_in(human_id, nr)
if not sys.is_empty():
_text("[color=#88ff99]Hier steht: %s[/color]" %
TechDB.by_id(str(sys["tech"]))["name"], 16)
_knopf("System wieder herausnehmen", func() -> void:
Game.system_entfernen(human_id, nr)
systeme_panel(human_id))
_trenner()
for id in TechDB.ids():
if not Game.knows(id):
continue
var pruef := Game.system_pruefen(human_id, nr, id)
var t := TechDB.by_id(id)
var ev := Game.evaluate(id, human_id, activity, false)
var wo: Array = Game.regel_momente(human_id, id)
var gewaehlt := ""
if wo.has(activity):
gewaehlt = " — hier eingestellt"
elif not wo.is_empty():
var namen: Array = []
for a in wo:
namen.append("»%s«" % Orte.text(a))
gewaehlt = " — hängt an %s" % ", ".join(namen)
var beschriftung := "%s · Stufe %d" % [t["name"], Game.skill_of(id)]
if ev["blocked"]:
beschriftung += " — " + str(ev["grund"])
elif gewaehlt != "":
beschriftung += gewaehlt
_knopf(beschriftung, func() -> void:
Game.regel_setzen(human_id, activity, id)
pipeline_panel(human_id), not ev["blocked"])
_knopf("Keine Regel für diese Tätigkeit", func() -> void:
Game.regel_setzen(human_id, activity, "")
pipeline_panel(human_id))
_zeigen("regel")
if not pruef["geht"]:
beschriftung += " — " + str(pruef["grund"])
elif float(pruef["guete"]) > Game.GUETE_NEU:
beschriftung += " — passt genau"
_knopf(beschriftung, func() -> void: _system_setzen(human_id, nr, id),
bool(pruef["geht"]))
_trenner()
_knopf("Zurück", func() -> void: systeme_panel(human_id))
_zeigen("slot")
func _system_setzen(human_id: String, nr: int, tech_id: String) -> void:
var res := Game.system_setzen(human_id, nr, tech_id, false)
if not res["ok"]:
Game.notified.emit(str(res["grund"]), Color(1, 0.6, 0.4))
return
if not res["erfolg"]:
Game.notified.emit("%s greift diesmal nicht. Später noch einmal." %
TechDB.by_id(tech_id)["name"], Color(1, 0.6, 0.5))
systeme_panel(human_id)
# -------------------------------------------------------- Lehrorte
@@ -522,70 +564,66 @@ func brett_panel() -> void:
_scroll_frames = 4
# -------------------------------------------------------- Anwenden auf der Erde
# -------------------------------------------------------- Vor Ort
## Draußen am Menschen: hier greift man in den Slot ein, der gerade läuft.
func anwenden_panel(mensch: Node3D) -> void:
_apply_human = mensch
var h: Dictionary = mensch.data
var aktiv: String = mensch.activity
_leeren("%s — %s" % [h["name"], Orte.text(aktiv)])
var id: String = str(h["id"])
var nr: int = int(mensch.slot)
_leeren("%s — %s" % [h["name"], Orte.text(mensch.activity)])
_text("[b]Traum:[/b] %s" % h["traum"], 16)
if Game.active_mission == h["id"]:
_text("Fortschritt: [b]%d %%[/b]" % int(Game.mission_progress(h["id"])), 16)
elif Game.completed.has(h["id"]):
_text("[color=#88ff99]★ Traum erfüllt.[/color]", 16)
else:
_text("[color=#ffcc66]Keine aktive Mission für diesen Menschen. " +
"Fortschritt zählt nur mit angenommener Mission.[/color]", 15)
if Game.active_mission != id:
_text("[color=#ffcc66]Keine aktive Mission für diesen Menschen.[/color]", 15)
_zeigen("anwenden")
return
var grund := Game.slot_grund(id, nr)
if not Game.erkannt(id, nr):
_text("[color=#ffcc66]Was hier läuft, hast du noch nicht gesehen. " +
"Bleib stehen und schau zu.[/color]", 16)
_zeigen("anwenden")
return
if Ursachen.ist_blocker(grund):
var b: Dictionary = Ursachen.BLOCKER[grund]
_text("Sie %s. [b]Auslöser:[/b] %s · [b]Belohnung:[/b] %s" %
[b["name"], b["ausloeser"], Ursachen.belohnung_text(str(b["belohnung"]))], 15)
_trenner()
if Game.known.is_empty():
_text("[color=#ffcc66]Du kennst keine Technik.[/color]")
_zeigen("anwenden")
return
_text("[color=#aaaaaa]Was eine Technik tut, steht im Journal.[/color]", 15)
for id in TechDB.ids():
if not Game.knows(id):
for tid in TechDB.ids():
if not Game.knows(tid):
continue
var t := TechDB.by_id(id)
var ev := Game.evaluate(id, h["id"], aktiv, mensch.resist_left > 0.0)
var gesperrt := not Game.bereit(id)
var beschriftung := "%s · Stufe %d" % [t["name"], Game.skill_of(id)]
if gesperrt:
beschriftung += " — noch %d s gesperrt" % int(ceil(Game.cooldown_rest(id)))
elif ev["blocked"]:
beschriftung += " — " + str(ev["grund"])
var pruef := Game.system_pruefen(id, nr, tid)
var t := TechDB.by_id(tid)
var beschriftung := "%s · Stufe %d" % [t["name"], Game.skill_of(tid)]
if not pruef["geht"]:
beschriftung += " — " + str(pruef["grund"])
elif mensch.resist_left > 0.0:
beschriftung += " — macht gerade dicht"
_knopf(beschriftung, func() -> void: _anwenden(id), not ev["blocked"] and not gesperrt)
_knopf(beschriftung, func() -> void: _vor_ort_setzen(tid), bool(pruef["geht"]))
_zeigen("anwenden")
func _anwenden(id: String) -> void:
func _vor_ort_setzen(tech_id: String) -> void:
if _apply_human == null or not is_instance_valid(_apply_human):
schliessen()
return
var mensch := _apply_human
var h: Dictionary = mensch.data
var res := Game.apply_technique(id, h["id"], mensch.activity, mensch.resist_left > 0.0)
var id: String = str(mensch.data["id"])
var res := Game.system_setzen(id, int(mensch.slot), tech_id, mensch.resist_left > 0.0)
schliessen()
if not res["ok"]:
Game.notified.emit(res["grund"], Color(1, 0.6, 0.4))
Game.notified.emit(str(res["grund"]), Color(1, 0.6, 0.4))
return
var t := TechDB.by_id(id)
if res["success"]:
mensch.reagiere(true, "%s wirkt\n+%d %%" % [t["name"], int(round(float(res["gain"])))])
Game.notified.emit("%s erreicht %s. (+%d %%)" %
[t["name"], h["name"], int(round(float(res["gain"])))], Color(0.6, 1, 0.7))
if res["wiederholung"] >= 2:
Game.notified.emit("Immer dieselbe Technik wirkt schwächer. Probier eine andere.",
Color(0.85, 0.85, 0.6))
var t := TechDB.by_id(tech_id)
if res["erfolg"]:
mensch.reagiere(true, "%s\nsteht" % t["name"])
else:
mensch.reagiere(false, "%s prallt ab\n−%d %%" % [t["name"],
int(round(float(res["verlust"])))])
Game.notified.emit("%s dringt nicht durch. (−%d %%)" %
[t["name"], int(round(float(res["verlust"])))], Color(1, 0.6, 0.5))
mensch.reagiere(false, "%s greift nicht" % t["name"])
func _unhandled_input(event: InputEvent) -> void:

136
scripts/ursachen.gd Normal file
View File

@@ -0,0 +1,136 @@
class_name Ursachen
## Woran ein Traum scheitert, und was im Tag davon zu sehen ist.
##
## Ein Traum hat eine Grundrate. Jede ungelöste Ursache bremst sie. Zwei
## ungelöste Ursachen bremsen doppelt -- deshalb lohnt es, alle zu finden.
## Sichtbar wird eine Ursache nur an den Tätigkeiten, die sie hervorbringt:
## wer nie in Gang kommt, räumt auf, recherchiert und schreibt Listen.
##
## Grundlage: Habit Reversal Training beginnt mit awareness training -- erst
## bemerken, dann ersetzen. Und Duhiggs goldene Regel: Auslöser und Belohnung
## bleiben, nur die Handlung dazwischen wird getauscht.
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,
},
}
## Tätigkeiten, die einen Traum aufhalten. Jede hat einen Auslöser und eine
## Belohnung -- genau die muss eine Ersatzhandlung wieder liefern.
## zeit: wann im Tag die Tätigkeit auftaucht.
static var BLOCKER: Dictionary = {
"putzflucht": {
"name": "räumt auf statt anzufangen", "ausloeser": "eine große Aufgabe",
"belohnung": "erfolg", "ursache": "anfang", "zeit": "tag",
},
"recherche": {
"name": "recherchiert statt anzufangen", "ausloeser": "Unsicherheit",
"belohnung": "erfolg", "ursache": "anfang", "zeit": "tag",
},
"liste": {
"name": "schreibt eine Später-Liste", "ausloeser": "Überforderung",
"belohnung": "ordnung", "ursache": "anfang", "zeit": "morgen",
},
"jasagen": {
"name": "sagt zu allem ja", "ausloeser": "eine fremde Bitte",
"belohnung": "anerkennung", "ursache": "zeit", "zeit": "tag",
},
"scrollen": {
"name": "scrollt endlos", "ausloeser": "Leerlauf",
"belohnung": "ablenkung", "ursache": "zeit", "zeit": "tag",
},
"serie": {
"name": "schaut eine Serie", "ausloeser": "Erschöpfung",
"belohnung": "abschalten", "ursache": "kraft", "zeit": "abend",
},
"wach": {
"name": "bleibt wach", "ausloeser": "ein fremdbestimmter Tag",
"belohnung": "zeit_fuer_sich", "ursache": "kraft", "zeit": "nacht",
},
"kuehlschrank": {
"name": "steht am Kühlschrank", "ausloeser": "Langeweile",
"belohnung": "unterbrechung", "ursache": "kraft", "zeit": "abend",
},
"spielen": {
"name": "spielt noch eine Runde", "ausloeser": "Anspannung",
"belohnung": "erfolg", "ursache": "halt", "zeit": "abend",
},
"umschalten": {
"name": "schaltet auf etwas anderes um", "ausloeser": "Widerstand",
"belohnung": "neuheit", "ursache": "halt", "zeit": "tag",
},
"klagen": {
"name": "klagt am Telefon", "ausloeser": "Frust",
"belohnung": "verbundenheit", "ursache": "grund", "zeit": "abend",
},
"vergleichen": {
"name": "vergleicht sich mit anderen", "ausloeser": "ein fremder Erfolg",
"belohnung": "einordnung", "ursache": "grund", "zeit": "morgen",
},
}
static var BELOHNUNG_TEXT: Dictionary = {
"erfolg": "ein Erfolgserlebnis",
"ordnung": "Ordnung",
"anerkennung": "Anerkennung",
"anschluss": "Anschluss",
"abschalten": "Abschalten",
"zeit_fuer_sich": "Zeit für sich",
"unterbrechung": "eine Unterbrechung",
"neuheit": "etwas Neues",
"verbundenheit": "Verbundenheit",
"einordnung": "Einordnung",
"ablenkung": "Ablenkung",
}
static func ist_blocker(activity: String) -> bool:
return BLOCKER.has(activity)
static func ursache_von(activity: String) -> String:
return str(BLOCKER[activity]["ursache"]) if BLOCKER.has(activity) else ""
static func belohnung_text(schluessel: String) -> String:
return str(BELOHNUNG_TEXT.get(schluessel, schluessel))
## Blocker einer Ursache, die in dieses Zeitfenster passen.
static func blocker_fuer(ursache: String, zeit: String) -> Array:
var out: Array = []
for id in BLOCKER:
if BLOCKER[id]["ursache"] == ursache and BLOCKER[id]["zeit"] == zeit:
out.append(id)
return out
static func alle_blocker(ursache: String) -> Array:
var out: Array = []
for id in BLOCKER:
if BLOCKER[id]["ursache"] == ursache:
out.append(id)
return out

1
scripts/ursachen.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://ddavhllmw3nlk