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