This commit is contained in:
team3
2026-08-31 11:07:01 +02:00
parent f936dd2b2a
commit ef2ecc08f9
19 changed files with 2094 additions and 438 deletions

View File

@@ -9,22 +9,40 @@ 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
## Drei Stufen wie in Die Sims: normal, schnell, sehr schnell. Die Stufe ist
## 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.
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 cooldown: Dictionary = {} # tech_id -> Restsekunden
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
var tempo: int = 1 # 1, 2 oder 3 -- vom Spieler gewählt
func _ready() -> void:
_setup_input()
welt_erzeugen()
_startweg_lernen()
## Den ersten Weg bringt das Wesen schon mit. Ohne ihn hinge am Schwarzen Brett
## nichts aus, und der erste Gang wäre eine Pflichtübung.
func _startweg_lernen() -> void:
for id in DreamDB.techniken_von("hoersaal"):
if not known.has(str(id)):
known[str(id)] = 0
func _setup_input() -> void:
@@ -37,6 +55,9 @@ func _setup_input() -> void:
"sinken": [KEY_SHIFT],
"interagieren": [KEY_E],
"journal": [KEY_J, KEY_TAB],
"tempo_1": [KEY_1, KEY_KP_1],
"tempo_2": [KEY_2, KEY_KP_2],
"tempo_3": [KEY_3, KEY_KP_3],
}
for action in binds:
if not InputMap.has_action(action):
@@ -47,7 +68,47 @@ func _setup_input() -> void:
InputMap.action_add_event(action, ev)
## Schläft der Mensch, dem du gerade hilfst? Dann ist ohnehin nichts zu holen.
func schlaeft_ziel() -> bool:
if active_mission == "":
return false
var h := HumanDB.by_id(active_mission)
if h.is_empty():
return false
var r: Array = h["routine"]
var cur: Array = r[r.size() - 1]
for e in r:
if time_h >= float(e[0]):
cur = e
return not erreichbar(str(cur[2]))
## Um wie viel schneller läuft die Welt gerade? Über der Nacht des Menschen
## läuft sie immer auf der höchsten Stufe -- warten bringt dort nichts.
func zeit_faktor() -> float:
return faktor_von(3 if schlaeft_ziel() else tempo)
static func faktor_von(stufe: int) -> float:
return float(TEMPO_FAKTOR[clampi(stufe, 1, 3) - 1])
func tempo_setzen(stufe: int) -> void:
var neu := clampi(stufe, 1, 3)
if neu == tempo:
return
tempo = neu
notified.emit("Tempo %d — %d×" % [tempo, int(faktor_von(tempo))], Color(0.8, 0.9, 1))
state_changed.emit()
func advance_time(delta: float) -> void:
for id in cooldown.keys():
var rest: float = float(cooldown[id]) - delta
if rest <= 0.0:
cooldown.erase(id)
else:
cooldown[id] = rest
time_h += delta * 24.0 / DAY_SECONDS
while time_h >= 24.0:
time_h -= 24.0
@@ -75,7 +136,6 @@ func time_string() -> String:
# lehrt eine Technik, die es vorher nirgends gab.
const INSEL_GRUND := 46.0
const OFFENE_MISSIONEN := 3
func haeuser() -> int:
@@ -83,14 +143,13 @@ func haeuser() -> int:
func insel_radius() -> float:
var ring := DreamDB.aeusserster_ring(haeuser())
return maxf(INSEL_GRUND, ring + 14.0)
return maxf(INSEL_GRUND, DreamDB.aussenradius() + 12.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)
HumanDB.erzeuge()
## Das Traumdorf beginnt leer. Mit jedem erfüllten Traum zieht ein Wesen ein.
@@ -117,7 +176,21 @@ func offene_techniken() -> int:
return n
# ------------------------------------------------------------------- Cooldown
# Jede Anwendung sperrt ihre Technik für eine Weile. Wer einen ganzen Tag
# abdecken will, braucht deshalb mehrere Wege — einer allein kommt nicht nach.
func cooldown_rest(tech_id: String) -> float:
return float(cooldown.get(tech_id, 0.0))
func bereit(tech_id: String) -> bool:
return cooldown_rest(tech_id) <= 0.0
# ---------------------------------------------------------------- Fertigkeiten
# Gelernt wird an einem Lehrort, besser wird eine Technik nur durch Anwendung.
# Es gibt keinen Weg, sie am Schreibtisch zu trainieren.
func knows(tech_id: String) -> bool:
return known.has(tech_id)
@@ -149,10 +222,6 @@ func learn(tech_id: String) -> bool:
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))
@@ -171,18 +240,6 @@ func add_xp(tech_id: String, amount: int) -> void:
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:
@@ -204,6 +261,40 @@ 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.
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):
return true
return false
## Die Menschen, die gerade am Brett hängen können.
func offene_missionen() -> Array:
var out: Array = []
for h in HumanDB.LIST:
var id: String = h["id"]
if completed.has(id) or not machbar(id):
continue
out.append(id)
return out
func mission_progress(human_id: String) -> float:
return progress.get(human_id, 0.0)
@@ -242,17 +333,17 @@ 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:
## 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:
return activity
return ""
out.append(activity)
return out
## 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.
## 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] = {}
@@ -260,23 +351,20 @@ func regel_setzen(human_id: String, activity: String, tech_id: String) -> void:
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?
## 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 {}
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 {}
@@ -287,8 +375,8 @@ func regel_ausloesen(human_id: String, activity: String, resisting: bool) -> Dic
[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))],
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))
return res
@@ -324,11 +412,13 @@ func evaluate(tech_id: String, human_id: String, activity: String, resisting: bo
break
res["moment"] = t["momente"].has(activity)
var c := 0.08 + 0.11 * float(skill)
# 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.25
c += 0.20
if res["moment"]:
c += 0.22
c += 0.35
if resisting:
c -= 0.30
res["grund"] = "Der Mensch macht gerade dicht."
@@ -338,9 +428,13 @@ func evaluate(tech_id: String, human_id: String, activity: String, resisting: bo
## 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"])
@@ -349,6 +443,7 @@ func apply_technique(tech_id: String, human_id: String, activity: String, resist
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"]:
@@ -358,25 +453,25 @@ func apply_technique(tech_id: String, human_id: String, activity: String, resist
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 and gain > 0.0:
var p: float = progress.get(human_id, 0.0) + gain
progress[human_id] = minf(p, MISSION_ZIEL)
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 = ""
einsicht += 3
done = true
mission_done.emit(human_id)
notified.emit("%s hat es geschafft. (+3 Einsicht)" % HumanDB.by_id(human_id)["name"],
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,
"wiederholung": wiederholung, "mission_done": done,
"verlust": verlust, "wiederholung": wiederholung, "mission_done": done,
"tag": ev["tag"], "moment": ev["moment"],
}