From 89fad5dc7af8645d2156fc12e371caebdcdf4d46 Mon Sep 17 00:00:00 2001 From: Team3 Date: Wed, 16 Sep 2026 12:08:04 +0200 Subject: [PATCH] update --- README.md | 19 +++--- scripts/bewohner.gd | 39 +++++------ scripts/earth_world.gd | 70 +++++++++++++------- scripts/game.gd | 1 - scripts/human_db.gd | 4 +- scripts/orte.gd | 144 +++++++++++++++++++++++++++++++++++++---- scripts/player.gd | 111 +++++++++++++++---------------- tests/_probe.gd | 91 ++++++++++++++++++++++++++ tests/_probe.tscn | 6 ++ tests/_probe2.gd | 54 ++++++++++++++++ tests/_probe2.tscn | 6 ++ tests/verify.gd | 115 +++++++++++++++++++++----------- 12 files changed, 493 insertions(+), 167 deletions(-) create mode 100644 tests/_probe.gd create mode 100644 tests/_probe.tscn create mode 100644 tests/_probe2.gd create mode 100644 tests/_probe2.tscn diff --git a/README.md b/README.md index b1460ff..b58ccaa 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ hervorgehobene. Fenster in der Mitte gibt es nur an Orten: Brett, Hörsaal, Aushang schreiben. **Esc** geht eine Ebene zurück, nicht gleich ganz zu. Oben links stehen nur Zeit und Mission. Alles Weitere steht im Journal. -In der Traumwelt springt **Leertaste**; auf der Erde wechselt sie ein -Stockwerk nach oben, **Umschalt** eines nach unten. +In der Traumwelt springt **Leertaste**. Auf der Erde gibt es keinen Sprung: +der Geist nimmt die Treppe. ## Spielschleife @@ -172,14 +172,17 @@ nicht immer — Übung entscheidet. im Büro und an der Hochschule sitzt jeder auf seinem Stockwerk. Die Bewohner gehen die Treppe hoch und runter. Alles andere steht als geschlossener Klotz da. -* Du bist ein unsichtbarer Geist, schwebst und gehst durch Wände. Bist du - in einem Haus, greift der **Etagenschnitt**: Dach, Etagen über dir und +* Du bist ein unsichtbarer Geist, der eine Handbreit über dem Boden schwebt, + sich aber bewegt wie ein Mensch: Wände halten, Türen und Treppen sind die + Wege, Schwerkraft gilt. Bist du in einem Haus, greift der **Etagenschnitt**: Dach, Etagen über dir und die Wände zur Kamera hin verschwinden, wie bei den Sims. Ein Haus, das zwischen Kamera und dir steht, wird durchsichtig. Wer auf einer anderen Etage ist, ist nicht im Fokus — durch die Decke sieht man nichts. -* **F** hängt dich an einen Menschen: du treibst hinter ihm her, durch Türen - und über den ganzen Tag, ohne ihm nachlaufen zu müssen. **F** noch einmal — - oder ein Schritt mit **WASD** — lässt ihn wieder los. +* **F** oder ein Klick hängt dich an einen Menschen: du gehst seiner Spur + nach, durch dieselben Türen und Treppen, so schnell wie nötig — auch bei + Tempo 3, wo du ihm zu Fuß nie folgen könntest. Bleibst du hängen, zieht die + Leine dich nach. **F** noch einmal — oder ein Schritt mit **WASD** — lässt + ihn wieder los. * **R notieren.** Was den Traum aufhält, steht nirgends geschrieben. Du siehst nur, was der Mensch tut, und musst selbst entscheiden: läuft hier etwas schief? Triffst du den Moment, steht die Ursache im Journal und der Slot @@ -221,7 +224,7 @@ eintauschen könnte. Ein Erfolg bringt 2 EP, ein Fehlversuch 1 — auch der zäh scripts/orte.gd Orte, Türen, Wege, Etagen, Treppenhaus (beide Welten) scripts/bewohner.gd Bewohner mit Tagesablauf, Hut nach Beruf, Haltung nach Klemme scripts/main.gd Weltenwechsel, Fokus, Interaktion - scripts/player.gd Geist: feste Kamera, Etagenwechsel, Schleier, Funken, Verfolgung + scripts/player.gd Geist: feste Kamera, Schwerkraft, Spur-Leine, Schleier, Funken scripts/dream_world.gd Insel mit Dorf scripts/earth_world.gd Stadt mit Stockwerken, Treppen, Etagenschnitt, Tag-Nacht-Wechsel scripts/ursachen.gd Ursachen, blockierende Tätigkeiten, Belohnungen diff --git a/scripts/bewohner.gd b/scripts/bewohner.gd index 4d39c32..6504207 100644 --- a/scripts/bewohner.gd +++ b/scripts/bewohner.gd @@ -28,6 +28,7 @@ var _path: Array = [] var _letzte_aktivitaet: String = "" var boden: Callable = Callable() # Höhe des Bodens (x, z) -> y; leer heißt eben var slot: int = 0 +var spur: Array = [] # letzte Positionen -- der Geist folgt ihr durch Türen var _bob: float = 0.0 var _offset: Vector3 @@ -87,33 +88,19 @@ func setup(d: Dictionary, index: int, orte: Dictionary, global_position = Orte.center(places, place) + _offset + Vector3(0, Orte.hoehe(etage(place)), 0) +## Punkt auf der Spur, der mindestens `abstand` hinter dem Menschen liegt. +func spur_punkt(abstand: float) -> Vector3: + for i in range(spur.size() - 1, -1, -1): + if (spur[i] as Vector3).distance_to(global_position) >= abstand: + return spur[i] + return spur[0] if not spur.is_empty() else global_position + + ## In welcher Etage dieser Mensch an einem Ort ist -- 0, wenn er dort nur unten ist. func etage(ort: String) -> int: return int((data.get("etage", {}) as Dictionary).get(ort, 0)) -## Wege führen durchs Treppenhaus: aus der Etage runter zur Tür, und am Ziel -## von der Tür hinauf in die Etage. -func _mit_etagen(pts: Array, von: String, nach: String, war_unterwegs: bool) -> Array: - if pts.is_empty(): - return pts - var e_von := etage(von) - if e_von > 0 and not war_unterwegs: - var runter: Array = [Orte.treppe(places, von, e_von)] - runter.append_array(Orte.treppen_weg(places, von, e_von, 0)) - runter.append(Orte.innen_tuer(places, von)) - runter.append_array(pts) - pts = runter - var e_nach := etage(nach) - if e_nach > 0: - pts.pop_back() - pts.append(Orte.innen_tuer(places, nach)) - pts.append(Orte.treppe(places, nach, 0)) - pts.append_array(Orte.treppen_weg(places, nach, 0, e_nach)) - pts.append(Orte.center(places, nach) + _offset + Vector3(0, Orte.hoehe(e_nach), 0)) - return pts - - ## Kopfbedeckung nach Tagestyp des Berufs -- so unterscheiden sich die Leute ## schon aus der Ferne. func _hut(kopf: Vector3, c: Color) -> void: @@ -180,10 +167,10 @@ func _process(roh: float) -> void: slot = int(soll[2]) if soll[0] != place: var war_unterwegs := not _path.is_empty() - _path = Orte.route(places, place, soll[0], _offset, netz, anker) + _path = Orte.route_voll(places, place, soll[0], _offset, + data.get("etage", {}), netz, anker, war_unterwegs) if war_unterwegs and _path.size() > 1: _path.remove_at(0) # steht schon draußen, alte Tür überspringen - _path = _mit_etagen(_path, place, soll[0], war_unterwegs) place = soll[0] activity = "unterwegs" elif _path.is_empty() and activity != soll[1]: @@ -213,6 +200,10 @@ func _process(roh: float) -> void: _haltung(delta, not _path.is_empty()) if boden.is_valid(): global_position.y = float(boden.call(global_position.x, global_position.z)) + if spur.is_empty() or (spur.back() as Vector3).distance_to(global_position) > 0.4: + spur.append(global_position) + if spur.size() > 120: + spur.pop_front() _visual.rotation.z = lerp_angle(_visual.rotation.z, PI * 0.5 if activity == "schlaeft" else 0.0, 1.0 - pow(0.01, delta)) diff --git a/scripts/earth_world.gd b/scripts/earth_world.gd index 82a7ce2..1eedcae 100644 --- a/scripts/earth_world.gd +++ b/scripts/earth_world.gd @@ -122,10 +122,12 @@ func _orte() -> void: var cf := c + Vector3(0, Orte.hoehe(f), 0) var ton := farbe.lightened(0.06 * float(f)) waende.append(Prims.room(eb, cf, groesse, ton, farbe.darkened(0.35), - Orte.DIR_VEC[pl["dir"]] if f == 0 else Vector3.ZERO, tuer, true)) + Orte.DIR_VEC[pl["dir"]] if f == 0 else Vector3.ZERO, tuer, f == 0)) + if f > 0: + _decke(eb, key, cf, groesse, farbe.darkened(0.35)) _fenster(eb, cf, Vector2(groesse.x, groesse.z), ton) _einrichtung(eb, cf, groesse, farbe, key if f == 0 or HumanDB.ETAGEN_ARBEIT.has(key) - else "heim_") + else "heim_", key) if f + 1 < n: _treppe(eb, key, f) var licht := OmniLight3D.new() @@ -159,51 +161,75 @@ func _fenster(parent: Node, cf: Vector3, g: Vector2, ton: Color) -> void: glas, false) -## Eine Rampe von Podest zu Podest -- die Treppe, die die Bewohner hochgehen. +## Ein Lauf von Podest zu Podest -- die Treppe, die Bewohner und Geist +## hochgehen. Sie trägt, deshalb mit Kollision. func _treppe(parent: Node, key: String, f: int) -> void: - var a := Orte.treppe(HumanDB.PLACES, key, f) - var b := Orte.treppe(HumanDB.PLACES, key, f + 1) - var d := b - a - var stufe := Prims.box(parent, (a + b) * 0.5, Vector3(1.2, 0.18, d.length()), - Color(0.55, 0.50, 0.46), false) + var lauf := Orte.treppen_lauf(HumanDB.PLACES, key, f) + var von: Vector3 = lauf[0] + var bis: Vector3 = lauf[1] + var d := bis - von + var stufe := Prims.box(parent, (von + bis) * 0.5, Vector3(1.6, 0.18, d.length()), + Color(0.55, 0.50, 0.46)) stufe.basis = Basis.looking_at(d.normalized(), Vector3.UP) - Prims.box(parent, b + Vector3(0, -0.09, 0), Vector3(1.4, 0.18, 1.4), Color(0.55, 0.50, 0.46), false) + var r := Orte.treppen_rahmen(HumanDB.PLACES, key) + var podest := Orte.treppe(HumanDB.PLACES, key, f + 1) + var mass := Orte._xz(r["a"], Orte.PODEST * 2.0, Orte.TREPPE_TIEFE - 0.8) + Prims.box(parent, podest + Vector3(0, -0.09, 0), Vector3(mass.x, 0.18, mass.y), + Color(0.55, 0.50, 0.46)) -## Ein paar Möbel, damit man sieht, wofür ein Raum da ist. -func _einrichtung(parent: Node, c: Vector3, groesse: Vector3, farbe: Color, key: String) -> void: +## Der Boden einer oberen Etage lässt über dem Treppenhaus ein Loch frei -- +## sonst stieße, wer die Treppe hochkommt, mit dem Kopf an die Decke. +func _decke(parent: Node, key: String, cf: Vector3, _groesse: Vector3, farbe: Color) -> void: + for platte in Orte.treppen_decke(HumanDB.PLACES, key): + var mitte: Vector3 = platte[0] + var mass: Vector2 = platte[1] + mitte.y = cf.y - 0.1 + Prims.box(parent, mitte, Vector3(mass.x, 0.2, mass.y), farbe) + + +## Ein paar Möbel, damit man sieht, wofür ein Raum da ist. Nichts steht im +## Treppenstreifen. +func _einrichtung(parent: Node, c: Vector3, groesse: Vector3, farbe: Color, key: String, + ort: String = "") -> void: + if ort == "": + ort = key var w := maxf(0.5, groesse.x * 0.5 - 2.0) var h := maxf(0.5, groesse.z * 0.5 - 2.0) + var frei := func(v: Vector3, halb: float) -> Vector3: + var p := Orte.frei_von_treppe(HumanDB.PLACES, ort, c + v, halb) + p.y = c.y + v.y + return p if key.begins_with("heim_"): - Prims.box(parent, c + Vector3(-w * 0.6, 0.35, -h * 0.5), Vector3(1.9, 0.7, 3.0), + Prims.box(parent, frei.call(Vector3(-w * 0.6, 0.35, -h * 0.5), 1.5), Vector3(1.9, 0.7, 3.0), Color(0.85, 0.85, 0.90)) - Prims.box(parent, c + Vector3(w * 0.6, 0.75, 0), Vector3(1.5, 0.15, 2.2), + Prims.box(parent, frei.call(Vector3(w * 0.6, 0.75, 0), 1.1), Vector3(1.5, 0.15, 2.2), farbe.darkened(0.5)) - Prims.box(parent, c + Vector3(w * 0.6, 1.05, 0), Vector3(0.8, 0.55, 0.1), + Prims.box(parent, frei.call(Vector3(w * 0.6, 1.05, 0), 0.1), Vector3(0.8, 0.55, 0.1), Color(0.2, 0.25, 0.3)) elif key == "cafe": for i in range(3): - Prims.cyl(parent, c + Vector3(-w * 0.5 + float(i) * w * 0.5, 0.4, 0.4), 0.7, 0.8, + Prims.cyl(parent, frei.call(Vector3(-w * 0.5 + float(i) * w * 0.5, 0.4, 0.4), 0.7), 0.7, 0.8, Color(0.55, 0.40, 0.30)) - Prims.box(parent, c + Vector3(0, 0.55, -h * 0.7), Vector3(groesse.x - 3.0, 1.1, 0.8), + Prims.box(parent, frei.call(Vector3(0, 0.55, -h * 0.7), 0.4), Vector3(groesse.x - 3.0, 1.1, 0.8), Color(0.45, 0.32, 0.24)) elif key == "buero": for i in range(3): - Prims.box(parent, c + Vector3(-w * 0.5 + float(i) * w * 0.5, 0.75, 0), + Prims.box(parent, frei.call(Vector3(-w * 0.5 + float(i) * w * 0.5, 0.75, 0), 0.8), Vector3(1.6, 0.15, 1.2), Color(0.75, 0.75, 0.78)) elif key == "baeckerei": - Prims.box(parent, c + Vector3(0, 0.55, h * 0.5), Vector3(groesse.x - 3.0, 1.1, 1.0), + Prims.box(parent, frei.call(Vector3(0, 0.55, h * 0.5), 0.5), Vector3(groesse.x - 3.0, 1.1, 1.0), Color(0.70, 0.55, 0.35)) - Prims.box(parent, c + Vector3(0, 0.85, -h * 0.6), Vector3(2.6, 1.7, 1.2), + Prims.box(parent, frei.call(Vector3(0, 0.85, -h * 0.6), 1.3), Vector3(2.6, 1.7, 1.2), Color(0.4, 0.4, 0.42)) elif key == "uni": for i in range(3): - Prims.box(parent, c + Vector3(0, 0.5, -h * 0.6 + float(i) * h * 0.6), + Prims.box(parent, frei.call(Vector3(0, 0.5, -h * 0.6 + float(i) * h * 0.6), 0.35), Vector3(groesse.x - 3.0, 0.2, 0.7), Color(0.66, 0.62, 0.58)) - Prims.box(parent, c + Vector3(0, 1.1, -h), Vector3(groesse.x * 0.55, 1.6, 0.15), + Prims.box(parent, frei.call(Vector3(0, 1.1, -h), 0.1), Vector3(groesse.x * 0.55, 1.6, 0.15), Color(0.15, 0.25, 0.20)) elif key == "werkstatt": - Prims.box(parent, c + Vector3(0, 0.6, 0), Vector3(groesse.x - 4.0, 1.2, 1.4), + Prims.box(parent, frei.call(Vector3(0, 0.6, 0), 0.7), Vector3(groesse.x - 4.0, 1.2, 1.4), Color(0.52, 0.48, 0.46)) diff --git a/scripts/game.gd b/scripts/game.gd index 446c5a2..6e175f6 100644 --- a/scripts/game.gd +++ b/scripts/game.gd @@ -59,7 +59,6 @@ func _setup_input() -> void: "links": [KEY_A, KEY_LEFT], "rechts": [KEY_D, KEY_RIGHT], "steigen": [KEY_SPACE], - "sinken": [KEY_SHIFT], "interagieren": [KEY_E], "folgen": [KEY_F], "notieren": [KEY_R], diff --git a/scripts/human_db.gd b/scripts/human_db.gd index 94afd9d..da066d9 100644 --- a/scripts/human_db.gd +++ b/scripts/human_db.gd @@ -388,7 +388,9 @@ static func _sonderbauten_verteilen() -> void: # Das Café gehört in die Mitte -- jeder Tagesablauf führt dorthin. # Die übrigen Sonderbauten verteilen sich rundum. var wunsch := Vector3.ZERO - if str(SONDER[s][0]) != "cafe": + if str(SONDER[s][0]) == "werkstatt": + wunsch = Vector3(0, 0, 30.0) # kurze Mittagspause: das Café muss nah sein + elif str(SONDER[s][0]) != "cafe": var w := TAU * float(s) / float(SONDER.size() - 1) wunsch = Vector3(cos(w), 0, sin(w)) * 42.0 var idx: int = -1 diff --git a/scripts/orte.gd b/scripts/orte.gd index 66230e1..9091dfb 100644 --- a/scripts/orte.gd +++ b/scripts/orte.gd @@ -59,9 +59,13 @@ static func hoehe(e: int) -> float: return float(e) * ETAGE -## Treppenhaus: die Ecke neben der Tür. Die Podeste wechseln je Etage die -## Seite, so läuft die Treppe im Zickzack an der Türwand hoch. -static func treppe(places: Dictionary, key: String, e: int) -> Vector3: +const TREPPE_LAUF := 6.0 # Abstand der Wendepodeste entlang der Wand +const TREPPE_TIEFE := 4.4 # Streifen an der Treppenwand: zwei Läufe nebeneinander +const PODEST := 0.9 # halbe Podestlänge + +## Rahmen des Treppenhauses: an der längeren Wand, die nicht die Türwand ist. +## n zeigt von der Mitte zur Treppenwand, a läuft an ihr entlang. +static func treppen_rahmen(places: Dictionary, key: String) -> Dictionary: var pl: Dictionary = places[key] var c := center(places, key) var d: Vector3 = DIR_VEC[pl["dir"]] @@ -70,11 +74,98 @@ static func treppe(places: Dictionary, key: String, e: int) -> Vector3: var halb_d: float = (float(size[1]) if quer_ist else float(size[0])) * 0.5 var halb_q: float = (float(size[0]) if quer_ist else float(size[1])) * 0.5 var quer := Vector3(-d.z, 0, d.x) - var p := c + d * (halb_d - 1.6) + quer * (halb_q - 1.4) - quer * (3.0 if e % 2 == 1 else 0.0) + if halb_q >= halb_d: + return {"c": c, "n": -d, "a": quer, "halb_n": halb_d, "halb_a": halb_q} + return {"c": c, "n": -quer, "a": -d, "halb_n": halb_q, "halb_a": halb_d} + + +static func _lauf(r: Dictionary) -> float: + return minf(TREPPE_LAUF, float(r["halb_a"]) * 2.0 - 2.8) + + +static func _podest_a(r: Dictionary, e: int) -> float: + var p0: float = float(r["halb_a"]) - 1.4 + return p0 if e % 2 == 0 else p0 - _lauf(r) + + +## Wendepodest der Etage e: zwei Läufe liegen nebeneinander, das Podest +## verbindet sie. Der obere Lauf ist so nie die Decke des unteren. +static func treppe(places: Dictionary, key: String, e: int) -> Vector3: + var r := treppen_rahmen(places, key) + var p: Vector3 = r["c"] + r["n"] * (float(r["halb_n"]) - TREPPE_TIEFE * 0.5) \ + + r["a"] * _podest_a(r, e) p.y = hoehe(e) return p +## Ein Lauf: [Anfang auf Etage f, Ende auf Etage f + 1]. +static func treppen_lauf(places: Dictionary, key: String, f: int) -> Array: + var r := treppen_rahmen(places, key) + var tiefe: float = float(r["halb_n"]) - (1.4 if f % 2 == 0 else 3.0) + var richtung := -1.0 if f % 2 == 0 else 1.0 + var von: Vector3 = r["c"] + r["n"] * tiefe + r["a"] * (_podest_a(r, f) + richtung * PODEST) + var bis: Vector3 = r["c"] + r["n"] * tiefe + r["a"] * (_podest_a(r, f + 1) - richtung * PODEST) + von.y = hoehe(f) + bis.y = hoehe(f + 1) + return [von, bis] + + +## Wegpunkte durchs Treppenhaus von Etage a nach Etage b, ohne a selbst: +## Laufanfang, Laufende, Podest -- je Etage. +static func treppen_weg(places: Dictionary, key: String, a: int, b: int) -> Array: + var out: Array = [] + if b > a: + for f in range(a, b): + var l := treppen_lauf(places, key, f) + out.append(l[0]) + out.append(l[1]) + out.append(treppe(places, key, f + 1)) + else: + for f in range(a - 1, b - 1, -1): + var l := treppen_lauf(places, key, f) + out.append(l[1]) + out.append(l[0]) + out.append(treppe(places, key, f)) + return out + + +## Deckenplatten einer oberen Etage: alles außer dem Treppenloch. +## Liefert [[mitte, groesse_xz], ...] ohne Höhe. +static func treppen_decke(places: Dictionary, key: String) -> Array: + var r := treppen_rahmen(places, key) + var c: Vector3 = r["c"] + var n: Vector3 = r["n"] + var a: Vector3 = r["a"] + var halb_n := float(r["halb_n"]) + var halb_a := float(r["halb_a"]) + var out: Array = [] + # Hauptplatte: alles außer dem Streifen an der Treppenwand + out.append([c - n * (TREPPE_TIEFE * 0.5), _xz(a, halb_a * 2.0, halb_n * 2.0 - TREPPE_TIEFE)]) + # Rest des Streifens jenseits des Lochs + var ende: float = _podest_a(r, 1) - PODEST + var rest := ende + halb_a + if rest > 0.3: + out.append([c + n * (halb_n - TREPPE_TIEFE * 0.5) + a * ((ende - halb_a) * 0.5), + _xz(a, rest, TREPPE_TIEFE)]) + return out + + +## Kastenmaß in Weltachsen: laenge_a entlang a, laenge_n quer dazu. +static func _xz(a: Vector3, laenge_a: float, laenge_n: float) -> Vector2: + return Vector2(laenge_a, laenge_n) if absf(a.x) > 0.0 else Vector2(laenge_n, laenge_a) + + +## Rückt einen Punkt aus dem Treppenstreifen heraus (für Möbel). +static func frei_von_treppe(places: Dictionary, key: String, pos: Vector3, halb: float) -> Vector3: + var r := treppen_rahmen(places, key) + var n: Vector3 = r["n"] + var pn: float = (pos - r["c"]).dot(n) + var grenze: float = float(r["halb_n"]) - TREPPE_TIEFE - halb - 0.2 + if pn > grenze: + pos -= n * (pn - grenze) + return pos + + ## Punkt gleich hinter der Tür, im Haus. static func innen_tuer(places: Dictionary, key: String) -> Vector3: var pl: Dictionary = places[key] @@ -85,15 +176,42 @@ static func innen_tuer(places: Dictionary, key: String) -> Vector3: return c + d * (halb - 1.2) -## Wegpunkte durchs Treppenhaus von Etage a nach Etage b, ohne a selbst. -static func treppen_weg(places: Dictionary, key: String, a: int, b: int) -> Array: - var out: Array = [] - var schritt := 1 if b > a else -1 - var e := a - while e != b: - e += schritt - out.append(treppe(places, key, e)) - return out +## Ein Ort ist ein Gebäude, wenn er Grundriss und Tür hat. +static func gebaeude(places: Dictionary, key: String) -> bool: + return places.has(key) and float(places[key]["size"][0]) > 0.0 \ + and DIR_VEC[places[key]["dir"]] != Vector3.ZERO + + +## Route mit allem, was ein Körper braucht: aus der Etage runter, gerade durch +## die Tür, über das Netz, durch die nächste Tür und die Treppe hinauf. +## `etagen` sagt, in welcher Etage der Mensch an welchem Ort ist. +static func route_voll(places: Dictionary, a: String, b: String, offset: Vector3, + etagen: Dictionary, netz: Netz = null, anker: Dictionary = {}, + war_unterwegs: bool = false) -> Array: + var pts := route(places, a, b, offset, netz, anker) + if pts.is_empty(): + return pts + if gebaeude(places, a) and not war_unterwegs: + var e_von := int(etagen.get(a, 0)) + var runter: Array = [] + if e_von > 0: + runter.append(treppe(places, a, e_von)) + runter.append_array(treppen_weg(places, a, e_von, 0)) + runter.append(innen_tuer(places, a)) + runter.append(door(places, a)) + runter.append_array(pts) + pts = runter + if gebaeude(places, b): + var e_nach := int(etagen.get(b, 0)) + var ziel: Vector3 = pts.pop_back() + pts.append(door(places, b)) + pts.append(innen_tuer(places, b)) + if e_nach > 0: + pts.append(treppe(places, b, 0)) + pts.append_array(treppen_weg(places, b, 0, e_nach)) + ziel.y = hoehe(e_nach) + pts.append(ziel) + return pts static func center(places: Dictionary, key: String) -> Vector3: diff --git a/scripts/player.gd b/scripts/player.gd index 69b97a3..7391c83 100644 --- a/scripts/player.gd +++ b/scripts/player.gd @@ -9,8 +9,9 @@ const SPEED_TRAUM := 8.0 const SPEED_GEIST := 11.0 const GRAVITY := 24.0 const JUMP := 9.0 -const FOLGE_ABSTAND := 2.2 -const FOLGE_SPIEL := 1.5 +const FOLGE_ABSTAND := 2.5 # so weit bleibt der Geist auf der Spur zurück +const LEINE := 14.0 # weiter weg reißt die Leine: er wird nachgezogen +const SCHWEBE := 0.25 # eine Handbreit über dem Boden const PITCH := -0.96 # 55° von oben, fest wie in Diablo const CAM_YAW := PI * 0.25 # Blick diagonal über den Bezirk const ZOOM := [26.0, 40.0] # zwei Stufen, Mausrad @@ -27,7 +28,6 @@ var arm: Node3D var cam: Camera3D var _dreh_rest := 0.0 # noch zu drehender Winkel (Q) var _zoom_stufe := 0 -var _schwebe_ziel := -1.0 # Etage, zu der der Geist gerade gleitet var body: Node3D var _schleier: MeshInstance3D var _kern: MeshInstance3D @@ -51,10 +51,10 @@ const FUNKEN_MAX := 6 func _ready() -> void: var cs := CollisionShape3D.new() var shape := CapsuleShape3D.new() - shape.radius = 0.45 - shape.height = 1.9 + shape.radius = 0.4 + shape.height = 1.6 cs.shape = shape - cs.position.y = 0.95 + cs.position.y = 0.8 add_child(cs) body = Node3D.new() @@ -107,18 +107,15 @@ func _ready() -> void: func set_mode(m: int) -> void: mode = m folgt = null + collision_layer = 1 + collision_mask = 1 if m == Mode.GEIST: - collision_layer = 0 - collision_mask = 0 body.scale = Vector3.ONE * 0.6 _schleier_mat.albedo_color.a = 0.3 else: - collision_layer = 1 - collision_mask = 1 body.scale = Vector3.ONE * 0.8 _schleier_mat.albedo_color.a = 0.55 velocity = Vector3.ZERO - _schwebe_ziel = -1.0 func _unhandled_input(event: InputEvent) -> void: @@ -157,56 +154,16 @@ func _physics_process(delta: float) -> void: elif dir.length() > 0.01: folgt = null # eigene Bewegung löst die Verfolgung else: - var rechts := Basis(Vector3.UP, yaw) * Vector3(1, 0, 0) - var schulter := folgt.global_position + Vector3(0, 1.4, 0) \ - + rechts * FOLGE_ABSTAND - blickrichtung() * 0.8 - global_position = global_position.lerp(schulter, 1.0 - pow(0.004, delta)) - # weich nachziehen, aber an der Leine: sonst reißt der Faden beim Gehen - var ab := global_position - schulter - if ab.length() > FOLGE_SPIEL: - global_position = schulter + ab.normalized() * FOLGE_SPIEL - velocity = Vector3.ZERO - body.rotation.y = lerp_angle(body.rotation.y, yaw, 1.0 - pow(0.001, delta)) + _an_der_leine(delta) return - if mode == Mode.GEIST: - var v := dir * SPEED_GEIST - velocity.x = lerpf(velocity.x, v.x, 1.0 - pow(0.001, delta)) - velocity.z = lerpf(velocity.z, v.z, 1.0 - pow(0.001, delta)) - # Steigen und Sinken wechseln die Etage: der Geist gleitet ein Stockwerk - if look_enabled: - var von: float = _schwebe_ziel if _schwebe_ziel >= 0.0 else global_position.y - if Input.is_action_just_pressed("steigen"): - _schwebe_ziel = clampf(von + Orte.ETAGE, 0.4, 45.0) - if Input.is_action_just_pressed("sinken"): - _schwebe_ziel = clampf(von - Orte.ETAGE, 0.4, 45.0) - if _schwebe_ziel >= 0.0: - var dy := _schwebe_ziel - global_position.y - velocity.y = clampf(dy * 6.0, -SPEED_GEIST, SPEED_GEIST) - if absf(dy) < 0.05: - global_position.y = _schwebe_ziel - velocity.y = 0.0 - _schwebe_ziel = -1.0 - else: - velocity.y = 0.0 - move_and_slide() - if global_position.y < 0.4: - global_position.y = 0.4 - if global_position.y > 45.0: - global_position.y = 45.0 - else: - velocity.x = lerpf(velocity.x, dir.x * SPEED_TRAUM, 1.0 - pow(0.0005, delta)) - velocity.z = lerpf(velocity.z, dir.z * SPEED_TRAUM, 1.0 - pow(0.0005, delta)) - if is_on_floor(): - if look_enabled and Input.is_action_pressed("steigen"): - velocity.y = JUMP - else: - velocity.y = -1.0 - else: - velocity.y -= GRAVITY * delta - move_and_slide() - if global_position.y < -400.0: - global_position.y = 2.0 + var tempo := SPEED_GEIST if mode == Mode.GEIST else SPEED_TRAUM + velocity.x = lerpf(velocity.x, dir.x * tempo, 1.0 - pow(0.0005, delta)) + velocity.z = lerpf(velocity.z, dir.z * tempo, 1.0 - pow(0.0005, delta)) + _fallen(delta) + move_and_slide() + if global_position.y < -400.0: + global_position.y = 2.0 if dir.length() > 0.01: var ziel := atan2(-dir.x, -dir.z) @@ -217,6 +174,40 @@ func blickrichtung() -> Vector3: return -Vector3(sin(yaw), 0, cos(yaw)) +## Schwerkraft in beiden Welten. Springen kann nur der Läufer im Traum. +func _fallen(delta: float) -> void: + if is_on_floor(): + if mode == Mode.TRAUM and look_enabled and Input.is_action_pressed("steigen"): + velocity.y = JUMP + else: + velocity.y = -1.0 + else: + velocity.y -= GRAVITY * delta + + +## Folgen heißt: seiner Spur nachgehen, durch dieselben Türen und Treppen. +## Er läuft so schnell wie nötig, um dranzubleiben -- auch bei hohem Tempo. +func _an_der_leine(delta: float) -> void: + var mensch := folgt as Bewohner + var ziel: Vector3 = mensch.spur_punkt(FOLGE_ABSTAND) + var d := ziel - global_position + d.y = 0.0 + var tempo := maxf(SPEED_GEIST, Bewohner.GEHTEMPO * Game.zeit_faktor() * 1.4) + if d.length() > 0.3: + var v := d.normalized() * minf(tempo, d.length() / delta) + velocity.x = v.x + velocity.z = v.z + body.rotation.y = lerp_angle(body.rotation.y, atan2(-d.x, -d.z), 1.0 - pow(0.001, delta)) + else: + velocity.x = 0.0 + velocity.z = 0.0 + _fallen(delta) + move_and_slide() + if global_position.distance_to(mensch.global_position) > LEINE: + global_position = ziel # hängen geblieben: die Leine zieht ihn nach + velocity = Vector3.ZERO + + ## Schweben, Funken, Schweif und Modusfarbe -- der Zustand ist am Geist ablesbar. func _beleben(delta: float) -> void: var t := Time.get_ticks_msec() * 0.001 @@ -233,7 +224,7 @@ func _beleben(delta: float) -> void: _kopf_mat.albedo_color = _farbe.lightened(0.4) _kopf_mat.emission = _farbe.lightened(0.4) - body.position.y = sin(t * 1.6) * 0.08 + body.position.y = (SCHWEBE if mode == Mode.GEIST else 0.0) + sin(t * 1.6) * 0.08 _kern.scale = Vector3.ONE * (1.0 + sin(t * 3.1) * 0.12) var tempo_flach := Vector2(velocity.x, velocity.z).length() var neigung := -0.16 * clampf(tempo_flach / SPEED_TRAUM, 0.0, 1.0) diff --git a/tests/_probe.gd b/tests/_probe.gd new file mode 100644 index 0000000..28509cf --- /dev/null +++ b/tests/_probe.gd @@ -0,0 +1,91 @@ +extends Node +var main: Node3D + +func _ready() -> void: + seed(20240) + main = load("res://scenes/main.tscn").instantiate() + add_child(main) + await get_tree().process_frame + main.ui.schliessen() + main._erde() + await get_tree().process_frame + # B: engste Wege mit Türen und Treppen + var liste: Array = [] + for m in HumanDB.LIST: + var r: Array = m["routine"] + for i in range(r.size()): + var von: String = String(r[i - 1][1]) if i > 0 else String(r[r.size() - 1][1]) + var nach: String = String(r[i][1]) + if von == nach: + continue + var pts: Array = Orte.route_voll(HumanDB.PLACES, von, nach, Vector3.ZERO, m.get("etage", {}), HumanDB.NETZ, HumanDB.ANKER) + var l := 0.0 + var pos := Orte.center(HumanDB.PLACES, von) + Vector3(0, Orte.hoehe(int(m.get("etage", {}).get(von, 0))), 0) + for pt in pts: + l += pos.distance_to(pt) + pos = pt + var slot_h: float = (float(r[i + 1][0]) - float(r[i][0])) if i + 1 < r.size() else (24.0 - float(r[i][0])) + liste.append([slot_h * 15.0 - l / 8.0, "%s %s->%s %.1fs / %.0fs" % [m["name"], von, nach, l / 8.0, slot_h * 15.0]]) + liste.sort_custom(func(a, b): return a[0] < b[0]) + for k in range(6): + print("ENG %s" % liste[k][1]) + # C: Treppe hoch, was blockiert? + var sp: Player = main.player + var oben: Bewohner = null + for m in main.welt.humans: + if m.etage(str(m.data["heim"])) > 0: + oben = m + break + var heim := str(oben.data["heim"]) + var pl: Dictionary = HumanDB.PLACES[heim] + print("HAUS %s size=%s dir=%s etagen=%d" % [heim, str(pl["size"]), pl["dir"], Orte.etagen(HumanDB.PLACES, heim)]) + var t1 := Orte.treppe(HumanDB.PLACES, heim, 1) + var stufen: Array = Orte.treppen_lauf(HumanDB.PLACES, heim, 0) + sp.global_position = (stufen[0] as Vector3) + Vector3(0, 0.3, 0) + sp.velocity = Vector3.ZERO + var lauf: Vector3 = stufen[1] - stufen[0] + lauf.y = 0.0 + sp.yaw = atan2(-lauf.x, -lauf.z) + Input.action_press("vor") + var hoechste := 0.0 + for i in range(70): + await get_tree().physics_frame + hoechste = maxf(hoechste, sp.global_position.y) + if i % 10 == 9 or i == 69: + var k := sp.get_last_slide_collision() + var txt := "-" + if k != null and k.get_collider() != null: + var body: Node = k.get_collider() + var mi: Node = body.get_parent() + txt = "%s pos=%s size=%s normal=%s" % [mi.name, str(mi.global_position), str((mi as MeshInstance3D).mesh.get("size")) if mi is MeshInstance3D else "?", str(k.get_normal())] + print("TREPPE f=%d pos=%s y=%.2f koll=%s" % [i, str(sp.global_position), sp.global_position.y, txt]) + Input.action_release("vor") + print("TREPPE hoechste=%.2f t1=%s" % [hoechste, str(t1)]) + # E: Verfolgung -- wo reißt die Leine? + var mensch: Bewohner = main.welt.humans[0] + sp.global_position = mensch.global_position + Vector3(0, 0.3, 0) + sp.velocity = Vector3.ZERO + sp.folgt = mensch + var weiteste := 0.0 + for i in range(2400): + await get_tree().physics_frame + var d := sp.global_position.distance_to(mensch.global_position) + if d > weiteste: + weiteste = d + if d > 6.0 and i % 5 == 0: + var k := sp.get_last_slide_collision() + var txt := "-" + if k != null and k.get_collider() != null: + var mi: Node = (k.get_collider() as Node).get_parent() + txt = "%s pos=%s" % [mi.name, str(mi.global_position)] + print("LEINE f=%d d=%.1f geist=%s mensch=%s ort=%s pfad=%d koll=%s" % [i, d, str(sp.global_position), str(mensch.global_position), mensch.place, mensch._path.size(), txt]) + print("LEINE weiteste=%.1f" % weiteste) + # D: wieder anhängen + sp.folgt = null + sp.global_position = mensch.global_position + Vector3(0, 1.5, 2.0) + sp.velocity = Vector3.ZERO + main._process(0.05) + print("ANHAENGEN fokus_mensch=%s fokus=%s d=%.2f geist=%s mensch=%s auf_erde=%s offen=%s" % [ + str(main._fokus_mensch), str(main._fokus), sp.global_position.distance_to(mensch.global_position + Vector3(0, 1, 0)), + str(sp.global_position), str(mensch.global_position), str(main.auf_erde), str(main.ui.offen)]) + get_tree().quit() diff --git a/tests/_probe.tscn b/tests/_probe.tscn new file mode 100644 index 0000000..039981d --- /dev/null +++ b/tests/_probe.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tests/_probe.gd" id="1_p"] + +[node name="Probe" type="Node"] +script = ExtResource("1_p") diff --git a/tests/_probe2.gd b/tests/_probe2.gd new file mode 100644 index 0000000..73ef0e1 --- /dev/null +++ b/tests/_probe2.gd @@ -0,0 +1,54 @@ +extends Node +var main: Node3D +const OUT := "user://treppe/" + +func _ready() -> void: + seed(7) + DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(OUT)) + main = load("res://scenes/main.tscn").instantiate() + add_child(main) + await get_tree().process_frame + main.ui.schliessen() + Game.time_h = 11.0 + main._erde() + await get_tree().process_frame + var sp: Player = main.player + var oben: Bewohner = null + for m in main.welt.humans: + if m.etage(str(m.data["heim"])) > 0 and Orte.etagen(HumanDB.PLACES, str(m.data["heim"])) >= 3: + oben = m + break + var heim := str(oben.data["heim"]) + # 1 Erdgeschoss, am Fuß der Treppe + var t0 := Orte.treppe(HumanDB.PLACES, heim, 0) + var innen := Orte.innen_tuer(HumanDB.PLACES, heim) + sp.global_position = innen + Vector3(0, 0.3, 0) + sp.velocity = Vector3.ZERO + await knipse("1_erdgeschoss_treppe") + # 2 auf dem Wendepodest im ersten Stock + sp.global_position = Orte.treppe(HumanDB.PLACES, heim, 1) + Vector3(0, 0.3, 0) + sp.velocity = Vector3.ZERO + await knipse("2_podest_erster_stock") + # 3 Leine auf der Straße: an jemanden hängen, der unterwegs ist + var laeufer: Bewohner = null + for i in range(3000): + await get_tree().process_frame + for m in main.welt.humans: + if m._path.size() > 3 and m.etage(m.place) == 0: + laeufer = m + break + if laeufer != null: + break + sp.global_position = laeufer.global_position + Vector3(0, 0.3, 0) + sp.folgt = laeufer + for i in range(240): + await get_tree().process_frame + await knipse("3_leine_strasse") + get_tree().quit() + +func knipse(name: String) -> void: + for i in range(30): + await get_tree().process_frame + await RenderingServer.frame_post_draw + get_viewport().get_texture().get_image().save_png(OUT + name + ".png") + print("Bild: " + name) diff --git a/tests/_probe2.tscn b/tests/_probe2.tscn new file mode 100644 index 0000000..47043d2 --- /dev/null +++ b/tests/_probe2.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tests/_probe2.gd" id="1_p"] + +[node name="Probe" type="Node"] +script = ExtResource("1_p") diff --git a/tests/verify.gd b/tests/verify.gd index 95f7ecd..4f1cac6 100644 --- a/tests/verify.gd +++ b/tests/verify.gd @@ -760,7 +760,8 @@ func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array, var nach: String = String(r[i][1]) if von == nach: continue - var dauer := _weg_laenge(places, von, nach, netz, anker) / Bewohner.GEHTEMPO + var dauer := _weg_laenge(places, von, nach, m.get("etage", {}), netz, anker) \ + / Bewohner.GEHTEMPO var slot_h: float = (float(r[i + 1][0]) - float(r[i][0])) if i + 1 < r.size() \ else (24.0 - float(r[i][0])) var fenster := slot_h * sek_pro_stunde @@ -775,11 +776,11 @@ func _wege_pruefen(welt_name: String, places: Dictionary, liste: Array, p(alles_ok, "%s — jeder Weg passt, engster Fall: %s" % [welt_name, engste_text]) -func _weg_laenge(places: Dictionary, von: String, nach: String, +func _weg_laenge(places: Dictionary, von: String, nach: String, etagen: Dictionary, netz: Netz = null, anker: Dictionary = {}) -> float: - var pts: Array = Orte.route(places, von, nach, Vector3.ZERO, netz, anker) + var pts: Array = Orte.route_voll(places, von, nach, Vector3.ZERO, etagen, netz, anker) var l := 0.0 - var pos := Orte.center(places, von) + var pos := Orte.center(places, von) + Vector3(0, Orte.hoehe(int(etagen.get(von, 0))), 0) for pt in pts: l += pos.distance_to(pt) pos = pt @@ -789,15 +790,14 @@ func _weg_laenge(places: Dictionary, von: String, nach: String, func test_geist() -> void: abschnitt("Geist-Bewegung") p(main.player.mode == Player.Mode.GEIST, "Spieler ist auf der Erde ein Geist") - p(main.player.collision_layer == 0 and main.player.collision_mask == 0, - "Der Geist kollidiert mit nichts — er schwebt durch Wände") - var vor: Vector3 = main.player.global_position + p(main.player.collision_mask == 1, "Der Geist stößt an Wände wie ein Mensch") main.player.global_position = Vector3(0, 8.0, 0) main.player.velocity = Vector3(6, 0, 0) - for i in range(30): + for i in range(60): await get_tree().physics_frame - p(main.player.global_position.x > 0.5, "Der Geist bewegt sich frei durch den Raum") - p(absf(main.player.global_position.y - 8.0) < 0.5, "Ohne Schwerkraft fällt er nicht") + p(main.player.global_position.x > 0.5, "Der Geist bewegt sich durch den Park") + p(main.player.global_position.y < 0.6 and main.player.is_on_floor(), + "Die Schwerkraft holt ihn auf den Boden (y = %.2f)" % main.player.global_position.y) var lena: Bewohner = welt().humans[0] var davor: Vector3 = lena.global_position @@ -817,6 +817,8 @@ func test_charakter() -> void: sp.set_mode(Player.Mode.GEIST) sp.look_enabled = true sp.folgt = null + var moment_merker: Dictionary = Game.situation.duplicate(true) + Game.situation = {} # ein laufender Moment ließe die Farbe flackern p(sp._augen.size() == 2 and sp._augen[0].position.z < 0.0, "Zwei Augen, vorn am Kopf") p(sp._funken.size() == Player.FUNKEN_MAX, "%d Funken stehen bereit" % Player.FUNKEN_MAX) for i in range(60): @@ -835,7 +837,11 @@ func test_charakter() -> void: for i in range(90): await get_tree().physics_frame p(_farbabstand(sp._farbe, Player.FARBE_FOLGE) < 0.1, "Beim Verfolgen leuchtet er warm") + Game.situation = moment_merker sp.folgt = null + sp.global_position = Vector3(0, 0.5, 9.0) # freier Rasen im Park + sp.velocity = Vector3.ZERO + sp.yaw = 0.0 Input.action_press("vor") for i in range(40): await get_tree().physics_frame @@ -1016,12 +1022,23 @@ func test_kamera_und_etagen() -> void: p(wand_weg >= 2 and wand_weg < (bau["waende"][0] as Array).size(), "Die Wände zur Kamera hin sind weg, die hinteren bleiben (%d von %d Teile)" % [wand_weg, (bau["waende"][0] as Array).size()]) - Input.action_press("steigen") - for i in range(60): + var stufen: Array = Orte.treppen_lauf(HumanDB.PLACES, heim, 0) + sp.global_position = (stufen[0] as Vector3) + Vector3(0, 0.3, 0) + sp.velocity = Vector3.ZERO + var lauf: Vector3 = stufen[1] - stufen[0] + lauf.y = 0.0 + sp.yaw = atan2(-lauf.x, -lauf.z) + Input.action_press("vor") + var hoechste := 0.0 + for i in range(70): await get_tree().physics_frame - Input.action_release("steigen") - p(absf(sp.global_position.y - (1.4 + Orte.ETAGE)) < 0.1, - "Leertaste hebt den Geist genau ein Stockwerk (y = %.1f)" % sp.global_position.y) + hoechste = maxf(hoechste, sp.global_position.y) + Input.action_release("vor") + p(hoechste > Orte.ETAGE - 0.3, + "Über die Treppe kommt der Geist in den ersten Stock (y = %.1f)" % hoechste) + sp.global_position = t1 + Vector3(0, 0.3, 0) + sp.velocity = Vector3.ZERO + await get_tree().physics_frame welt()._process(0.05) p((bau["etagen"][0] as Node3D).visible and (bau["etagen"][1] as Node3D).visible and (n < 3 or not (bau["etagen"][2] as Node3D).visible), @@ -1102,7 +1119,14 @@ func test_verfolgung() -> void: main.player.yaw = 0.0 var mensch: Bewohner = welt().humans[0] Game.active_mission = str(mensch.data["id"]) - main.player.global_position = mensch.global_position + Vector3(0, 1.5, 2.0) + # kurz vor seinem nächsten Ortswechsel einsteigen, damit er auch losgeht + var routine: Array = mensch.data["routine"] + for k in range(routine.size()): + if str(routine[k][1]) != mensch.place and float(routine[k][0]) > 0.5: + Game.time_h = float(routine[k][0]) - 0.4 + break + schritte(5) + main.player.global_position = mensch.global_position + Vector3(0, 0.3, 0) main.player.velocity = Vector3.ZERO main._process(0.05) p(main._fokus_mensch == mensch, "Der Mensch ist im Fokus") @@ -1114,9 +1138,8 @@ func test_verfolgung() -> void: var weiteste := 0.0 var gelaufen := false var im_fokus := 0 - for i in range(900): - schritte(1) - await get_tree().physics_frame + for i in range(2400): + await get_tree().physics_frame # nur die Engine steppt: sonst laufen die Menschen doppelt if not mensch._path.is_empty(): gelaufen = true if main._fokus_mensch == mensch: @@ -1124,8 +1147,8 @@ func test_verfolgung() -> void: weiteste = maxf(weiteste, main.player.global_position.distance_to(mensch.global_position)) p(gelaufen, "Er wechselt in dieser Zeit den Ort") - p(weiteste < 6.0, "Der Geist bleibt die ganze Zeit dicht dabei (höchstens %.1f)" % weiteste) - p(im_fokus > 850, "Und behält ihn im Fokus (%d von 900 Schritten)" % im_fokus) + p(weiteste < 8.0, "Der Geist bleibt die ganze Zeit dicht dabei (höchstens %.1f)" % weiteste) + p(im_fokus > 2000, "Und behält ihn im Fokus (%d von 2400 Schritten)" % im_fokus) p(main._fokus_mensch == mensch or main.player.folgt == mensch, "Unterwegs bleibt er notierbar") @@ -2203,8 +2226,8 @@ func test_journal_ueberall() -> void: func test_steuerung() -> void: abschnitt("Steuerung") - var aktionen := ["vor", "zurueck", "links", "rechts", "steigen", "sinken", - "interagieren", "journal", "tempo_1", "tempo_2", "tempo_3"] + var aktionen := ["vor", "zurueck", "links", "rechts", "steigen", "drehen", + "interagieren", "folgen", "notieren", "journal", "tempo_1", "tempo_2", "tempo_3"] var alle := true for a in aktionen: if not InputMap.has_action(a) or InputMap.action_get_events(a).is_empty(): @@ -2236,23 +2259,39 @@ func test_steuerung() -> void: p(main.player.global_position.x > 1.0, "D schiebt den Spieler nach rechts (x = %.1f)" % main.player.global_position.x) - main.player.global_position = Vector3(0, 8.0, 0) + # Ein geschlossener Klotz hält ihn auf: von der Straße aus gegen die Wand + var war_erde := main.auf_erde + if not war_erde: + main._erde() + await get_tree().process_frame + main.player.look_enabled = true + var klotz: Dictionary = {} + var richtung := Vector3.ZERO + for b in welt()._bauten: + if not bool(b.get("voll", false)): + continue + for pz in HumanDB.PARZELLEN: + if (pz["pos"] as Vector3).distance_to(b["c"]) < 0.1: + klotz = b + richtung = Orte.DIR_VEC[pz["dir"]] + if not klotz.is_empty(): + break + p(not klotz.is_empty(), "Es gibt geschlossene Klötze") + var kc: Vector3 = klotz["c"] + var kg: Vector2 = klotz["size"] + var halb: float = (kg.y if absf(richtung.z) > 0.0 else kg.x) * 0.5 + main.player.global_position = kc + richtung * (halb + 2.0) + Vector3(0, 0.3, 0) main.player.velocity = Vector3.ZERO - Input.action_press("steigen") - for i in range(40): + main.player.yaw = atan2(richtung.x, richtung.z) # "vor" zeigt zum Klotz + Input.action_press("vor") + for i in range(60): await get_tree().physics_frame - Input.action_release("steigen") - p(main.player.global_position.y > 9.0, - "Leertaste lässt den Geist steigen (y = %.1f)" % main.player.global_position.y) - - main.player.velocity = Vector3.ZERO - var hoch: float = main.player.global_position.y - Input.action_press("sinken") - for i in range(40): - await get_tree().physics_frame - Input.action_release("sinken") - p(main.player.global_position.y < hoch - 1.0, - "Umschalt lässt den Geist sinken (y = %.1f)" % main.player.global_position.y) + Input.action_release("vor") + var tiefe_im_klotz: float = halb - (main.player.global_position - kc).dot(richtung) + p(tiefe_im_klotz < 0.6, "Wände halten den Geist auf (%.1f m in der Wand)" % maxf(0.0, tiefe_im_klotz)) + if not war_erde: + main._traumwelt() # die folgenden Messungen erwarten die Traumwelt + await get_tree().process_frame ## Nach jedem Einsatz braucht eine Technik Ruhe. Ein Weg allein deckt daher