extends Node ## Globaler Spielzustand: Wissen, Fertigkeiten, Missionen, Uhrzeit. signal state_changed signal notified(text: String, color: Color) signal mission_done(human_id: String) const DAY_SECONDS := 360.0 # eine Erdenrunde in Echtzeit-Sekunden const XP_LEVELS := [0, 4, 10, 18, 28] const MISSION_ZIEL := 100.0 const RESIST_SEKUNDEN := 18.0 var einsicht: int = 3 var known: Dictionary = {} # tech_id -> xp var active_mission: String = "" # human_id oder "" var progress: Dictionary = {} # human_id -> 0..100 var used: Dictionary = {} # "human:tech" -> Anzahl var pipeline: Dictionary = {} # human_id -> {activity -> tech_id} var completed: Array = [] var time_h: float = 7.0 var day: int = 1 var teleport_lock: float = 0.0 func _ready() -> void: _setup_input() welt_erzeugen() func _setup_input() -> void: var binds := { "vor": [KEY_W, KEY_UP], "zurueck": [KEY_S, KEY_DOWN], "links": [KEY_A, KEY_LEFT], "rechts": [KEY_D, KEY_RIGHT], "steigen": [KEY_SPACE], "sinken": [KEY_SHIFT], "interagieren": [KEY_E], "journal": [KEY_J, KEY_TAB], } for action in binds: if not InputMap.has_action(action): InputMap.add_action(action) for code in binds[action]: var ev := InputEventKey.new() ev.physical_keycode = code InputMap.action_add_event(action, ev) func advance_time(delta: float) -> void: time_h += delta * 24.0 / DAY_SECONDS 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: var h := int(time_h) var m := int((time_h - h) * 60.0) return "Tag %d · %02d:%02d" % [day, h, m] # ------------------------------------------------------------ Wachsende Welt # Das Traumdorf wird erzeugt. Fest sind nur Hörsaal, Dorfplatz und Tor. # Jeder erfüllte Traum bringt einen Bewohner mit seinem Haus, und jedes Haus # lehrt eine Technik, die es vorher nirgends gab. const INSEL_GRUND := 46.0 const OFFENE_MISSIONEN := 3 func haeuser() -> int: return completed.size() func insel_radius() -> float: var ring := DreamDB.aeusserster_ring(haeuser()) return maxf(INSEL_GRUND, ring + 14.0) ## Baut Traumdorf und Menschheit auf den Stand der erfüllten Träume. func welt_erzeugen() -> void: DreamDB.erzeuge(haeuser()) HumanDB.erzeuge(completed.size() + OFFENE_MISSIONEN) ## Das Traumdorf beginnt leer. Mit jedem erfüllten Traum zieht ein Wesen ein. func wesen_eingezogen(index: int) -> bool: return index < haeuser() ## Steht dieser Ort schon auf der Insel? Erzeugt wird nur, was schon da ist. func ort_offen(place_key: String) -> bool: return DreamDB.PLACES.has(place_key) ## Ist die Technik überhaupt schon irgendwo zu finden? func technik_offen(tech_id: String) -> bool: var ort := DreamDB.ort_von_technik(tech_id) return ort != "" and ort_offen(ort) func offene_techniken() -> int: var n := 0 for t in TechDB.LIST: if technik_offen(t["id"]): n += 1 return n # ---------------------------------------------------------------- Fertigkeiten func knows(tech_id: String) -> bool: return known.has(tech_id) func skill_of(tech_id: String) -> int: if not known.has(tech_id): return 0 var xp: int = known[tech_id] var lvl := 1 for i in range(XP_LEVELS.size()): if xp >= XP_LEVELS[i]: lvl = i + 1 return lvl func xp_to_next(tech_id: String) -> String: var lvl := skill_of(tech_id) if lvl >= XP_LEVELS.size(): return "Meisterschaft" var need: int = XP_LEVELS[lvl] return "%d / %d EP" % [known[tech_id], need] func learn(tech_id: String) -> bool: if known.has(tech_id): return false if not technik_offen(tech_id): notified.emit("Diesen Weg kennt im Dorf noch niemand. Erfülle einen Traum.", Color(1, 0.8, 0.5)) return false if einsicht < 1: notified.emit("Dafür fehlt dir Einsicht. Hilf erst einem Menschen.", Color(1, 0.6, 0.4)) return false einsicht -= 1 known[tech_id] = 0 var t := TechDB.by_id(tech_id) notified.emit("Technik gelernt: %s" % t["name"], Color(0.6, 1, 0.7)) state_changed.emit() return true func add_xp(tech_id: String, amount: int) -> void: if not known.has(tech_id): return var before := skill_of(tech_id) known[tech_id] = int(known[tech_id]) + amount if skill_of(tech_id) > before: var t := TechDB.by_id(tech_id) notified.emit("%s ist jetzt Stufe %d." % [t["name"], skill_of(tech_id)], Color(0.7, 0.9, 1)) state_changed.emit() func deepen(tech_id: String) -> bool: if not known.has(tech_id): return false if einsicht < 1: notified.emit("Keine Einsicht übrig.", Color(1, 0.6, 0.4)) return false einsicht -= 1 add_xp(tech_id, 3) notified.emit("Vertieft: %s (+3 EP)" % TechDB.by_id(tech_id)["name"], Color(0.7, 0.9, 1)) return true # ------------------------------------------------------------------ Missionen func accept_mission(human_id: String) -> bool: if completed.has(human_id): return false if active_mission == human_id: return false active_mission = human_id if not progress.has(human_id): progress[human_id] = 0.0 var h := HumanDB.by_id(human_id) notified.emit("Mission angenommen: %s" % h["name"], Color(1, 0.9, 0.5)) state_changed.emit() return true ## Angenommen ist, wer schon einmal im Auftragsbuch stand. func angenommen(human_id: String) -> bool: return progress.has(human_id) func mission_progress(human_id: String) -> float: return progress.get(human_id, 0.0) ## Ein erfüllter Traum lässt das Dorf wachsen: ein Wesen zieht mit seinem ## Haus ein, und in diesem Haus wird ein neuer Weg gelehrt. func _insel_waechst() -> void: welt_erzeugen() var i := haeuser() - 1 if i < 0 or i >= DreamDB.LIST.size(): return var w: Dictionary = DreamDB.LIST[i] notified.emit("%s, %s, ist ins Traumdorf gezogen." % [w["name"], w["rolle"]], Color(0.85, 0.75, 1)) var neu: Array = DreamDB.techniken_von(DreamDB.haus_key(i)) if neu.is_empty(): return notified.emit("In %s wird ein neuer Weg gelehrt: %s." % [DreamDB.PLACES[DreamDB.haus_key(i)]["name"], TechDB.by_id(str(neu[0]))["name"]], Color(0.75, 0.95, 1)) # ------------------------------------------------------------------- Regelwerk # Eine Regel ist ein Wenn-Dann: tut der Mensch etwas Bestimmtes, greift die # zugeordnete Technik von allein. Damit automatisiert der Spieler den Fortschritt. func regeln(human_id: String) -> Dictionary: return pipeline.get(human_id, {}) func regel_fuer(human_id: String, activity: String) -> String: return regeln(human_id).get(activity, "") func regel_anzahl(human_id: String) -> int: return regeln(human_id).size() ## Wo hängt diese Technik im Regelwerk gerade? "" wenn nirgends. func regel_moment(human_id: String, tech_id: String) -> String: for activity in regeln(human_id): if regeln(human_id)[activity] == tech_id: return activity return "" ## Leere tech_id löscht die Regel. Eine Technik hält immer nur einen Posten — ## sie an einen neuen Moment zu hängen nimmt sie vom alten weg. Das zwingt zur ## Wahl und macht das Lernen weiterer Techniken erst wertvoll. func regel_setzen(human_id: String, activity: String, tech_id: String) -> void: if not pipeline.has(human_id): pipeline[human_id] = {} var r: Dictionary = pipeline[human_id] if tech_id == "": r.erase(activity) else: var vorher := regel_moment(human_id, tech_id) if vorher != "" and vorher != activity: r.erase(vorher) notified.emit("%s hängt jetzt an »%s« statt an »%s«." % [TechDB.by_id(tech_id)["name"], Orte.text(activity), Orte.text(vorher)], Color(0.85, 0.9, 1)) r[activity] = tech_id state_changed.emit() ## Der Mensch wechselt in eine Tätigkeit: greift dafür eine Regel? func regel_ausloesen(human_id: String, activity: String, resisting: bool) -> Dictionary: if human_id != active_mission: return {} var tech_id := regel_fuer(human_id, activity) if tech_id == "" or not known.has(tech_id): return {} var res := apply_technique(tech_id, human_id, activity, resisting) if not res["ok"]: return {} res["tech"] = tech_id var t := TechDB.by_id(tech_id) if res["success"]: notified.emit("Regel greift — %s bei »%s« (+%d %%)" % [t["name"], Orte.text(activity), int(round(float(res["gain"])))], Color(0.6, 1, 0.7)) else: notified.emit("Regel läuft ins Leere — %s bei »%s« (%d %% Chance)" % [t["name"], Orte.text(activity), int(round(float(res["chance"]) * 100.0))], Color(1, 0.7, 0.5)) return res # ----------------------------------------------------- Technik auf Mensch anwenden ## Schlafende erreicht keine Technik — weder von Hand noch über eine Regel. func erreichbar(activity: String) -> bool: return activity != "schlaeft" ## Bewertet eine Technik in genau dieser Situation, ohne sie anzuwenden. func evaluate(tech_id: String, human_id: String, activity: String, resisting: bool) -> Dictionary: var t := TechDB.by_id(tech_id) 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) var c := 0.08 + 0.11 * float(skill) if res["tag"]: c += 0.25 if res["moment"]: c += 0.22 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: var ev := evaluate(tech_id, human_id, activity, resisting) if ev["blocked"]: return {"ok": false, "grund": ev["grund"]} 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 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: add_xp(tech_id, 1) var done := false if human_id == active_mission and gain > 0.0: var p: float = progress.get(human_id, 0.0) + gain progress[human_id] = minf(p, MISSION_ZIEL) if progress[human_id] >= MISSION_ZIEL and not completed.has(human_id): completed.append(human_id) active_mission = "" einsicht += 3 done = true mission_done.emit(human_id) notified.emit("%s hat es geschafft. (+3 Einsicht)" % HumanDB.by_id(human_id)["name"], Color(1, 0.85, 0.4)) _insel_waechst() state_changed.emit() return { "ok": true, "success": success, "chance": ev["chance"], "gain": gain, "wiederholung": wiederholung, "mission_done": done, "tag": ev["tag"], "moment": ev["moment"], }