update
This commit is contained in:
19
README.md
19
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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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
|
||||
|
||||
144
scripts/orte.gd
144
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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
91
tests/_probe.gd
Normal file
91
tests/_probe.gd
Normal file
@@ -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()
|
||||
6
tests/_probe.tscn
Normal file
6
tests/_probe.tscn
Normal file
@@ -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")
|
||||
54
tests/_probe2.gd
Normal file
54
tests/_probe2.gd
Normal file
@@ -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)
|
||||
6
tests/_probe2.tscn
Normal file
6
tests/_probe2.tscn
Normal file
@@ -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")
|
||||
115
tests/verify.gd
115
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
|
||||
|
||||
Reference in New Issue
Block a user