Files
dreamer-game/scripts/main.gd
2026-09-05 10:25:55 +02:00

259 lines
7.5 KiB
GDScript

extends Node3D
## Wurzelknoten: hält Spieler und Oberfläche, wechselt zwischen den Welten
## und wickelt Interaktionen ab.
const REICHWEITE_OBJEKT := 4.2
const REICHWEITE_MENSCH := 3.6
var player: Player
var ui: UI
var welt: Node3D
var auf_erde := false
var _fokus: Node3D = null
var _fokus_mensch: Node3D = null
func _ready() -> void:
ui = UI.new()
add_child(ui)
ui.panel_opened.connect(func() -> void:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
player.look_enabled = false)
ui.panel_closed.connect(func() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
player.look_enabled = true)
player = Player.new()
add_child(player)
Game.mission_done.connect(func(_id: String) -> void:
Game.notified.emit("Kehre zur Traumwelt zurück — die Säule im Park bringt dich hin.",
Color(0.8, 0.95, 1)))
_traumwelt()
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _traumwelt() -> void:
auf_erde = false
_setze_welt(DreamWorld.new())
player.set_mode(Player.Mode.TRAUM)
player.global_position = (welt as DreamWorld).spawn
player.yaw = 0.0
player.pitch = -0.18
func _erde() -> void:
auf_erde = true
_setze_welt(EarthWorld.new())
player.set_mode(Player.Mode.GEIST)
player.global_position = (welt as EarthWorld).spawn
player.yaw = 0.0
player.pitch = -0.42
func _setze_welt(neu: Node3D) -> void:
if welt != null:
remove_child(welt)
welt.queue_free()
welt = neu
add_child(welt)
move_child(welt, 0)
Game.teleport_lock = 1.0
_fokus = null
_fokus_mensch = null
func _process(delta: float) -> void:
var dt := delta * Game.zeit_faktor()
Game.advance_time(dt)
Game.traum_voranbringen(dt * 24.0 / Game.DAY_SECONDS)
Game.teleport_lock = maxf(0.0, Game.teleport_lock - delta)
if ui.offen:
ui.setze_hinweis("")
return
Game.notiz_sperre = maxf(0.0, Game.notiz_sperre - delta)
Game.situationen_ticken(dt, auf_erde and not Game.schlaeft_ziel())
_fokus_suchen()
_dabeisein()
_hinweis_setzen()
func _fokus_suchen() -> void:
_fokus = null
_fokus_mensch = null
var p := player.global_position
var best := REICHWEITE_OBJEKT
for n in get_tree().get_nodes_in_group("interactable"):
var d: float = p.distance_to((n as Node3D).global_position)
if d < best:
best = d
_fokus = n
if auf_erde:
var bestm := REICHWEITE_MENSCH
for m in (welt as EarthWorld).humans:
var d: float = p.distance_to((m as Node3D).global_position + Vector3(0, 1.0, 0))
if d < bestm:
bestm = d
_fokus_mensch = m
if _fokus_mensch != null and (_fokus == null or bestm < best):
_fokus = null
elif _fokus != null:
_fokus_mensch = null
func _hinweis_setzen() -> void:
if player.folgt != null and is_instance_valid(player.folgt):
ui.setze_hinweis("[F] loslassen · %s — %s" %
[player.folgt.data["name"], _notiz_hinweis(player.folgt)])
elif _fokus_mensch != null:
ui.setze_hinweis("[F] folgen · %s — %s" %
[_fokus_mensch.data["name"], _notiz_hinweis(_fokus_mensch)])
elif _fokus != null:
var kind: String = _fokus.get_meta("kind")
var titel: String = _fokus.get_meta("titel")
match kind:
"erde":
ui.setze_hinweis("[E] Zur Erde reisen")
"traum":
ui.setze_hinweis("[E] Zurück in die Traumwelt")
"lehre":
ui.setze_hinweis("[E] %s — Techniken lernen" % titel)
"brett":
ui.setze_hinweis("[E] Schwarzes Brett — Mission annehmen")
_:
ui.setze_hinweis("[E] %s" % titel)
else:
ui.setze_hinweis("")
## Was am Menschen gerade dransteht: notieren, ansprechen oder nur zusehen.
func _notiz_hinweis(mensch: Bewohner) -> String:
var tat := Orte.text(mensch.activity)
if str(mensch.data["id"]) != Game.active_mission:
return tat
if Game.situation_bei(str(mensch.data["id"])):
return "%s · [E] jetzt eine Technik" % \
Probleme.SITUATIONEN[Game.situation["key"]]["name"]
if Game.notiz_sperre > 0.0:
return "%s (%.0f s)" % [tat, Game.notiz_sperre]
if Game.erkannt(str(mensch.data["id"]), int(mensch.slot)):
return "%s · [E] ansprechen" % tat
return "%s · [R] notieren, wenn hier etwas schiefläuft" % tat
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("ui_cancel") and not ui.offen:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if \
Input.mouse_mode == Input.MOUSE_MODE_CAPTURED else Input.MOUSE_MODE_CAPTURED
player.look_enabled = Input.mouse_mode == Input.MOUSE_MODE_CAPTURED
get_viewport().set_input_as_handled()
return
if ui.offen:
return
if event is InputEventMouseButton and event.pressed and Input.mouse_mode == Input.MOUSE_MODE_VISIBLE:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
player.look_enabled = true
return
for stufe in [1, 2, 3]:
if event.is_action_pressed("tempo_%d" % stufe):
Game.tempo_setzen(stufe)
get_viewport().set_input_as_handled()
return
if event.is_action_pressed("journal"):
ui.journal_panel()
get_viewport().set_input_as_handled()
return
if event.is_action_pressed("interagieren"):
_interagieren()
get_viewport().set_input_as_handled()
return
if event.is_action_pressed("folgen"):
_verfolgen()
get_viewport().set_input_as_handled()
return
if event.is_action_pressed("notieren"):
_notieren()
get_viewport().set_input_as_handled()
## Wer dabeisteht, füllt die Stunde im Ablauf -- mehr nicht. Was dort
## schiefläuft, muss er selbst benennen.
func _dabeisein() -> void:
if _fokus_mensch == null or not auf_erde:
return
var id: String = str(_fokus_mensch.data["id"])
if id == Game.active_mission:
Game.sehen(id, int(_fokus_mensch.slot))
if Game.situation_bei(id):
Game.situation_sehen()
## Den Moment benennen: hier läuft etwas schief. Das ist der Kern des
## Beobachtens -- der Spieler entscheidet, wann er hinschaut.
func _notieren() -> void:
if not auf_erde:
return
var ziel: Bewohner = _fokus_mensch
if ziel == null and player.folgt != null and is_instance_valid(player.folgt):
ziel = player.folgt
if ziel == null:
Game.notified.emit("Geh näher an den Menschen heran, dem du hilfst.",
Color(1, 0.85, 0.5))
return
var id: String = str(ziel.data["id"])
if id != Game.active_mission:
Game.notified.emit("Für %s hast du keine Mission angenommen." % ziel.data["name"],
Color(1, 0.85, 0.5))
return
if Game.notiz_sperre > 0.0:
Game.notified.emit("Du bist noch beim letzten Fehlgriff (%.0f s)." %
Game.notiz_sperre, Color(1, 0.75, 0.55))
return
Game.notieren(id, int(ziel.slot))
## Als Geist an einen Menschen hängen und ihn durch seinen Tag begleiten.
func _verfolgen() -> void:
if not auf_erde:
return
if player.folgt != null:
var name: String = player.folgt.data["name"]
player.folgt = null
Game.notified.emit("Du lässt %s wieder los." % name, Color(0.8, 0.85, 1))
return
if _fokus_mensch == null:
Game.notified.emit("Geh näher an einen Menschen heran, um ihm zu folgen.",
Color(1, 0.85, 0.5))
return
player.folgt = _fokus_mensch
Game.notified.emit("Du hängst dich an %s." % _fokus_mensch.data["name"],
Color(0.7, 0.9, 1))
func _interagieren() -> void:
if _fokus_mensch != null:
# Der offene Moment geht vor: er ist gleich wieder vorbei
if Game.situation_bei(str(_fokus_mensch.data["id"])):
ui.situation_panel(_fokus_mensch)
else:
ui.anwenden_panel(_fokus_mensch)
return
if _fokus == null:
return
var kind: String = _fokus.get_meta("kind")
match kind:
"erde":
if Game.active_mission == "" and Game.completed.size() < HumanDB.LIST.size():
Game.notified.emit(
"Das Tor öffnet sich nur mit einer Mission. Sieh am Schwarzen Brett nach.",
Color(1, 0.8, 0.5))
else:
_erde()
"traum":
_traumwelt()
"lehre":
ui.lehre_panel(str(_fokus.get_meta("arg")))
"brett":
ui.brett_panel()