389 lines
12 KiB
GDScript
389 lines
12 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 # die aktive Welt
|
|
var traum: DreamWorld = null # beide Welten bleiben bestehen und leben weiter
|
|
var erde: EarthWorld = null
|
|
var _traum_version := -1
|
|
var _erde_version := -1
|
|
var auf_erde := false
|
|
var _fokus: Node3D = null
|
|
var _fokus_mensch: Node3D = null
|
|
var _interaktive: Array = [] # Ansprechbares der aktiven Welt, einmal gesammelt
|
|
var _fokus_takt := 0.0 # Fokus, Markierung und Hinweis alle 0,05 s
|
|
|
|
|
|
func _ready() -> void:
|
|
ui = UI.new()
|
|
add_child(ui)
|
|
ui.panel_opened.connect(func() -> void:
|
|
player.look_enabled = false)
|
|
ui.panel_closed.connect(func() -> void:
|
|
player.look_enabled = true)
|
|
ui.hinfahren.connect(hinfahren)
|
|
|
|
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_VISIBLE
|
|
|
|
|
|
func _traumwelt() -> void:
|
|
auf_erde = false
|
|
if traum == null or _traum_version != Game.welt_version:
|
|
traum = _neu_bauen(traum, DreamWorld.new(), 1) as DreamWorld
|
|
_traum_version = Game.welt_version
|
|
_aktivieren(traum)
|
|
player.set_mode(Player.Mode.TRAUM)
|
|
player.collision_mask = 1
|
|
player.global_position = traum.spawn
|
|
player.yaw = Player.CAM_YAW
|
|
|
|
|
|
func _erde() -> void:
|
|
auf_erde = true
|
|
if erde == null or _erde_version != Game.welt_version:
|
|
erde = _neu_bauen(erde, EarthWorld.new(), 2) as EarthWorld
|
|
_erde_version = Game.welt_version
|
|
_aktivieren(erde)
|
|
player.set_mode(Player.Mode.GEIST)
|
|
player.collision_mask = 2
|
|
player.global_position = erde.spawn
|
|
player.yaw = Player.CAM_YAW
|
|
if Game.active_mission != "":
|
|
zum_ziel(Game.active_mission, false) # wer mit Mission reist, landet vor der Tür seines Ziels
|
|
|
|
|
|
## Vor die Tür eines Menschen setzen -- steht er draußen, neben ihn. Der Fußweg
|
|
## quer durch den Bezirk war Leerlauf; das Gehen zählt in der Wohnung und beim Folgen.
|
|
func zum_ziel(id: String, melden: bool = true) -> void:
|
|
var wer: Bewohner = null
|
|
for h in erde.humans:
|
|
if str(h.data["id"]) == id:
|
|
wer = h
|
|
if wer == null:
|
|
return
|
|
var p: Vector3
|
|
var drinnen: bool = HumanDB.PLACES.has(wer.place) \
|
|
and float(HumanDB.PLACES[wer.place]["size"][0]) > 0.0 and wer._path.is_empty()
|
|
if drinnen:
|
|
p = Orte.door(HumanDB.PLACES, wer.place)
|
|
else:
|
|
p = wer.global_position + Vector3(2.0, 0, 0)
|
|
p = NavigationServer3D.map_get_closest_point(erde.nav_map, p)
|
|
player.global_position = p + Vector3(0, 0.3, 0)
|
|
player.velocity = Vector3.ZERO
|
|
player.folgt = null
|
|
Game.teleport_lock = 1.0
|
|
if melden:
|
|
Game.notified.emit("Du stehst %s %s." % ["vor der Tür von" if drinnen else "bei",
|
|
wer.data["name"]], Color(0.7, 0.9, 1))
|
|
|
|
|
|
## Knopf in der Akte: zur Erde und vor die Tür.
|
|
func hinfahren(id: String) -> void:
|
|
if not auf_erde:
|
|
_erde()
|
|
zum_ziel(id)
|
|
|
|
|
|
## Eine Welt neu bauen -- nur wenn Dorf oder Stadt sich geändert haben. Jede
|
|
## Welt liegt auf ihrer eigenen Kollisionsebene, denn beide teilen sich den Raum.
|
|
func _neu_bauen(alt: Node3D, neu: Node3D, ebene: int) -> Node3D:
|
|
if alt != null:
|
|
remove_child(alt)
|
|
alt.queue_free()
|
|
add_child(neu)
|
|
move_child(neu, 0)
|
|
for k in neu.find_children("*", "CollisionObject3D", true, false):
|
|
(k as CollisionObject3D).collision_layer = 1 << (ebene - 1)
|
|
return neu
|
|
|
|
|
|
## Die andere Welt bleibt da und lebt weiter, nur unsichtbar und ohne Himmel.
|
|
func _aktivieren(neu: Node3D) -> void:
|
|
for w in [traum, erde]:
|
|
if w == null:
|
|
continue
|
|
var an: bool = w == neu
|
|
w.visible = an
|
|
w.set("spieler", player if an else null)
|
|
w.call("aktiv_setzen", an)
|
|
welt = neu
|
|
_interaktive = []
|
|
_fokus_takt = 0.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_takt -= delta
|
|
if _fokus_takt > 0.0:
|
|
return
|
|
_fokus_takt = 0.05
|
|
_fokus_suchen()
|
|
_markieren()
|
|
_dabeisein()
|
|
_hinweis_setzen()
|
|
|
|
|
|
## Wer ein Schild trägt: Mission, Fokus, Verfolgter und wer unter der Maus steht.
|
|
func _markieren() -> void:
|
|
Game.markiert = {}
|
|
if Game.active_mission != "":
|
|
Game.markiert[Game.active_mission] = true
|
|
if _fokus_mensch != null:
|
|
Game.markiert[str(_fokus_mensch.data["id"])] = true
|
|
if player.folgt != null and is_instance_valid(player.folgt):
|
|
Game.markiert[str(player.folgt.data["id"])] = true
|
|
if auf_erde:
|
|
var unter_maus := _mensch_unter_maus(get_viewport().get_mouse_position())
|
|
if unter_maus != null:
|
|
Game.markiert[str(unter_maus.data["id"])] = true
|
|
|
|
|
|
## Der Mensch, der auf dem Bildschirm am nächsten am Punkt steht -- oder null.
|
|
func _mensch_unter_maus(pos: Vector2) -> Bewohner:
|
|
if not auf_erde:
|
|
return null
|
|
var bester: Bewohner = null
|
|
var beste := 48.0
|
|
for m in (welt as EarthWorld).humans:
|
|
var w: Vector3 = (m as Node3D).global_position + Vector3(0, 0.8, 0)
|
|
if player.cam.is_position_behind(w):
|
|
continue
|
|
var d := player.cam.unproject_position(w).distance_to(pos)
|
|
if d < beste:
|
|
beste = d
|
|
bester = m
|
|
return bester
|
|
|
|
|
|
func _fokus_suchen() -> void:
|
|
_fokus = null
|
|
_fokus_mensch = null
|
|
var p := player.global_position
|
|
var best := REICHWEITE_OBJEKT
|
|
if _interaktive.is_empty():
|
|
for n in get_tree().get_nodes_in_group("interactable"):
|
|
if welt.is_ancestor_of(n): # die andere Welt liegt im selben Raum
|
|
_interaktive.append(n)
|
|
for n in _interaktive:
|
|
if not is_instance_valid(n):
|
|
continue
|
|
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 mp: Vector3 = (m as Node3D).global_position + Vector3(0, 1.0, 0)
|
|
var d: float = p.distance_to(mp)
|
|
if d < bestm:
|
|
bestm = d
|
|
_fokus_mensch = m
|
|
if player.folgt != null and is_instance_valid(player.folgt):
|
|
_fokus_mensch = player.folgt # wer sich angehängt hat, ist dabei -- auch auf der Treppe
|
|
if _fokus_mensch != null and (_fokus == null or bestm < best or _fokus_mensch == player.folgt):
|
|
_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("%s · %s · [F] los" %
|
|
[player.folgt.data["name"], _notiz_hinweis(player.folgt)])
|
|
elif _fokus_mensch != null:
|
|
ui.setze_hinweis("%s · %s · [F] folgen" %
|
|
[_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")
|
|
"spur":
|
|
ui.setze_hinweis("[E] Spur: %s" % titel)
|
|
"wesen":
|
|
ui.setze_hinweis("[E] %s — zuhören" % titel)
|
|
_:
|
|
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] 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" % tat
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if ui.offen:
|
|
return
|
|
if event is InputEventMouseButton and event.pressed \
|
|
and event.button_index == MOUSE_BUTTON_LEFT:
|
|
_klick(event.position)
|
|
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))
|
|
|
|
|
|
## Klick auf einen Menschen: an ihn hängen, noch ein Klick löst wieder.
|
|
func _klick(pos: Vector2) -> void:
|
|
var bester := _mensch_unter_maus(pos)
|
|
if bester == null:
|
|
return
|
|
if player.folgt == bester:
|
|
player.folgt = null
|
|
Game.notified.emit("Du lässt %s wieder los." % bester.data["name"], Color(0.8, 0.85, 1))
|
|
else:
|
|
player.anhaengen(bester)
|
|
Game.notified.emit("Du hängst dich an %s." % bester.data["name"], Color(0.7, 0.9, 1))
|
|
|
|
|
|
## 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.anhaengen(_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()
|
|
"wesen":
|
|
Game.notified.emit(Game.wesen_fluestert(int(str(_fokus.get_meta("arg")))), Color(0.85, 0.8, 1))
|
|
"spur":
|
|
var wer := str(_fokus.get_meta("human"))
|
|
var u := str(_fokus.get_meta("arg"))
|
|
var neu := Game.spur_lesen(wer, u)
|
|
Game.notified.emit("%s%s — %s" % ["" if neu else "Schon gesehen: ",
|
|
Ursachen.LISTE[u]["spur"], Ursachen.LISTE[u]["spur_text"]],
|
|
Color(1, 0.9, 0.6) if neu else Color(0.8, 0.85, 1))
|