383 lines
11 KiB
GDScript
383 lines
11 KiB
GDScript
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"],
|
|
}
|