update
This commit is contained in:
340
scripts/game.gd
340
scripts/game.gd
@@ -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"],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user