update
This commit is contained in:
10
README.md
10
README.md
@@ -188,13 +188,13 @@ nicht immer — Übung entscheidet.
|
|||||||
damit die Straßen nicht leer sind.
|
damit die Straßen nicht leer sind.
|
||||||
* **Die Stadt ist einstöckig.** Wohnhäuser und Sonderbauten sind geschlossene
|
* **Die Stadt ist einstöckig.** Wohnhäuser und Sonderbauten sind geschlossene
|
||||||
Häuser: ein Raum mit Tür, Fenstern, Möbeln und den Spuren seines Bewohners.
|
Häuser: ein Raum mit Tür, Fenstern, Möbeln und den Spuren seines Bewohners.
|
||||||
Alles andere steht als Klotz da, unterschiedlich hoch, damit die Stadt eine
|
Alles andere steht als flacher Klotz da, ebenfalls nur Erdgeschoss.
|
||||||
Silhouette hat.
|
|
||||||
* Du bist ein unsichtbarer Geist, der eine Handbreit über dem Boden schwebt,
|
* Du bist ein unsichtbarer Geist, der eine Handbreit über dem Boden schwebt,
|
||||||
sich aber bewegt wie ein Mensch: Wände halten, Türen sind die Wege,
|
sich aber bewegt wie ein Mensch: Wände halten, Türen sind die Wege,
|
||||||
Schwerkraft gilt. Bist du in einem Haus, verschwinden Dach und die Wände zur
|
Schwerkraft gilt. Bist du in einem Haus, verschwinden Dach und Schild, und
|
||||||
Kamera hin, wie bei den Sims. Ein Haus, das zwischen Kamera und dir steht,
|
alle Wände werden zu kniehohen Stümpfen gekürzt, wie bei den Sims. Nichts
|
||||||
wird durchsichtig.
|
wird durchsichtig; bei drei Meter hohen Häusern verdeckt kein Nachbar den
|
||||||
|
Blick.
|
||||||
* **F** oder ein Klick hängt dich an einen Menschen: du gehst seiner Spur
|
* **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
|
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. Steht er weit weg, planst du
|
Tempo 3, wo du ihm zu Fuß nie folgen könntest. Steht er weit weg, planst du
|
||||||
|
|||||||
@@ -22,4 +22,8 @@ window/stretch/aspect="expand"
|
|||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
|
|
||||||
anti_aliasing/quality/msaa_3d=2
|
anti_aliasing/quality/msaa_3d=0
|
||||||
|
anti_aliasing/quality/screen_space_aa=1
|
||||||
|
scaling_3d/scale=0.75
|
||||||
|
lights_and_shadows/directional_shadow/size=2048
|
||||||
|
lights_and_shadows/directional_shadow/soft_shadow_filter_quality=1
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ var nav_map: RID # auf der Erde: Pfade vom Navigationsnetz
|
|||||||
var nav_fehl := 0 # wie oft das Netz keinen Pfad wusste (Rückfall aufs Wegenetz)
|
var nav_fehl := 0 # wie oft das Netz keinen Pfad wusste (Rückfall aufs Wegenetz)
|
||||||
var nav_fehl_info := ""
|
var nav_fehl_info := ""
|
||||||
var _bob: float = 0.0
|
var _bob: float = 0.0
|
||||||
|
var _takt := 0.0 # Schild und Haltungsziele alle 0,3 s, nicht je Bild
|
||||||
|
var _neigung_ziel := 0.0
|
||||||
|
var _groesse_ziel := 1.0
|
||||||
|
var _zappelt := false
|
||||||
var _offset: Vector3
|
var _offset: Vector3
|
||||||
|
|
||||||
|
|
||||||
@@ -127,19 +131,12 @@ func _hut(kopf: Vector3, c: Color) -> void:
|
|||||||
## Angst duckt, Erschöpfung beugt, Unruhe zappelt -- solange die Klemme nicht
|
## Angst duckt, Erschöpfung beugt, Unruhe zappelt -- solange die Klemme nicht
|
||||||
## überwunden ist. So sieht man Probleme beim Beobachten am Körper.
|
## überwunden ist. So sieht man Probleme beim Beobachten am Körper.
|
||||||
func _haltung(delta: float, geht: bool) -> void:
|
func _haltung(delta: float, geht: bool) -> void:
|
||||||
var id := str(data["id"])
|
if _takt <= 0.0:
|
||||||
var offen: Dictionary = {}
|
_haltungsziele()
|
||||||
for k in data.get("probleme", []):
|
var zappeln := sin(Time.get_ticks_msec() * 0.011) * 0.06 if _zappelt and not geht else 0.0
|
||||||
if not Game.problem_ueberwunden(id, str(k)):
|
_ober.rotation.x = lerpf(_ober.rotation.x, -_neigung_ziel, 1.0 - pow(0.02, delta))
|
||||||
offen[str(k)] = true
|
|
||||||
var neigung := 0.38 if offen.has("erschoepfung") else 0.0
|
|
||||||
var groesse := 0.86 if offen.has("angst") else 1.0
|
|
||||||
var zappeln := 0.0
|
|
||||||
if offen.has("unruhe") and not geht:
|
|
||||||
zappeln = sin(Time.get_ticks_msec() * 0.011) * 0.06
|
|
||||||
_ober.rotation.x = lerpf(_ober.rotation.x, -neigung, 1.0 - pow(0.02, delta))
|
|
||||||
_ober.rotation.z = lerpf(_ober.rotation.z, zappeln, 1.0 - pow(0.001, delta))
|
_ober.rotation.z = lerpf(_ober.rotation.z, zappeln, 1.0 - pow(0.001, delta))
|
||||||
_ober.scale = _ober.scale.lerp(Vector3.ONE * groesse, 1.0 - pow(0.02, delta))
|
_ober.scale = _ober.scale.lerp(Vector3.ONE * _groesse_ziel, 1.0 - pow(0.02, delta))
|
||||||
var schwung := sin(_bob) * 0.7 if geht else 0.0
|
var schwung := sin(_bob) * 0.7 if geht else 0.0
|
||||||
for i in range(_arme.size()):
|
for i in range(_arme.size()):
|
||||||
var a: Node3D = _arme[i]
|
var a: Node3D = _arme[i]
|
||||||
@@ -147,6 +144,17 @@ func _haltung(delta: float, geht: bool) -> void:
|
|||||||
1.0 - pow(0.001, delta))
|
1.0 - pow(0.001, delta))
|
||||||
|
|
||||||
|
|
||||||
|
func _haltungsziele() -> void:
|
||||||
|
var id := str(data["id"])
|
||||||
|
var offen: Dictionary = {}
|
||||||
|
for k in data.get("probleme", []):
|
||||||
|
if not Game.problem_ueberwunden(id, str(k)):
|
||||||
|
offen[str(k)] = true
|
||||||
|
_neigung_ziel = 0.38 if offen.has("erschoepfung") else 0.0
|
||||||
|
_groesse_ziel = 0.86 if offen.has("angst") else 1.0
|
||||||
|
_zappelt = offen.has("unruhe")
|
||||||
|
|
||||||
|
|
||||||
## [Ort, Tätigkeit, Slotnummer] zur Stunde h. Steht in dem Slot ein System,
|
## [Ort, Tätigkeit, Slotnummer] zur Stunde h. Steht in dem Slot ein System,
|
||||||
## ist die Tätigkeit die Ersatzhandlung.
|
## ist die Tätigkeit die Ersatzhandlung.
|
||||||
func _routine_at(h: float) -> Array:
|
func _routine_at(h: float) -> Array:
|
||||||
@@ -217,11 +225,15 @@ func _process(roh: float) -> void:
|
|||||||
_aura.scale.x = 1.0 + sin(Time.get_ticks_msec() * 0.003) * 0.08
|
_aura.scale.x = 1.0 + sin(Time.get_ticks_msec() * 0.003) * 0.08
|
||||||
_aura.scale.z = _aura.scale.x
|
_aura.scale.z = _aura.scale.x
|
||||||
|
|
||||||
_label.text = beschriftung()
|
|
||||||
# Auf der Erde tragen nur Fokus, Verfolgte, Mission und wer unter der Maus
|
# Auf der Erde tragen nur Fokus, Verfolgte, Mission und wer unter der Maus
|
||||||
# steht ein Schild -- sonst überdecken sich acht Namen im Café.
|
# steht ein Schild -- sonst überdecken sich acht Namen im Café.
|
||||||
_label.visible = (not mit_mission and not bool(data.get("stumm", false))) \
|
_label.visible = (not mit_mission and not bool(data.get("stumm", false))) \
|
||||||
or Game.markiert.has(str(data["id"]))
|
or Game.markiert.has(str(data["id"]))
|
||||||
|
if _takt <= 0.0:
|
||||||
|
_takt = 0.3
|
||||||
|
if _label.visible:
|
||||||
|
_label.text = beschriftung()
|
||||||
|
_takt -= delta
|
||||||
_label.modulate = Color(0.55, 0.85, 0.6) if erledigt else (
|
_label.modulate = Color(0.55, 0.85, 0.6) if erledigt else (
|
||||||
Color(1, 0.9, 0.6) if aktiv else Color(1, 1, 1))
|
Color(1, 0.9, 0.6) if aktiv else Color(1, 1, 1))
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ func _environment() -> void:
|
|||||||
sun.light_energy = 1.3
|
sun.light_energy = 1.3
|
||||||
sun.light_color = Color(1.0, 0.84, 0.74)
|
sun.light_color = Color(1.0, 0.84, 0.74)
|
||||||
sun.shadow_enabled = true
|
sun.shadow_enabled = true
|
||||||
sun.shadow_blur = 2.5
|
sun.shadow_blur = 1.2
|
||||||
|
sun.directional_shadow_max_distance = 110.0
|
||||||
add_child(sun)
|
add_child(sun)
|
||||||
|
|
||||||
|
|
||||||
@@ -245,9 +246,9 @@ func _haeuser() -> void:
|
|||||||
var waende: Array = Prims.room(self, c, groesse, farbe, farbe.darkened(0.35),
|
var waende: Array = Prims.room(self, c, groesse, farbe, farbe.darkened(0.35),
|
||||||
Orte.DIR_VEC[pl["dir"]], 3.4, true)
|
Orte.DIR_VEC[pl["dir"]], 3.4, true)
|
||||||
var dach := Prims.dach(self, c, groesse, farbe)
|
var dach := Prims.dach(self, c, groesse, farbe)
|
||||||
|
var schild := Prims.label(self, c + Vector3(0, groesse.y + 4.2, 0), pl["name"], 0.6, Color(1, 0.95, 0.85))
|
||||||
_bauten.append({"c": c, "size": Vector2(groesse.x, groesse.z), "hoch": groesse.y + 3.0,
|
_bauten.append({"c": c, "size": Vector2(groesse.x, groesse.z), "hoch": groesse.y + 3.0,
|
||||||
"teile": [dach], "waende": waende})
|
"teile": [dach, schild], "waende": waende})
|
||||||
Prims.label(self, c + Vector3(0, groesse.y + 4.2, 0), pl["name"], 0.6, Color(1, 0.95, 0.85))
|
|
||||||
_einrichtung(c, groesse, farbe, key)
|
_einrichtung(c, groesse, farbe, key)
|
||||||
|
|
||||||
var licht := OmniLight3D.new()
|
var licht := OmniLight3D.new()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ var humans: Array = []
|
|||||||
var passanten: Array = [] # Statisten ohne Geschichte
|
var passanten: Array = [] # Statisten ohne Geschichte
|
||||||
var spieler: Node3D = null # setzt main, für den Etagenschnitt
|
var spieler: Node3D = null # setzt main, für den Etagenschnitt
|
||||||
var nav_map: RID # Navigationsnetz: Straßen, Türen, Möbel, Klötze
|
var nav_map: RID # Navigationsnetz: Straßen, Türen, Möbel, Klötze
|
||||||
const HOEHE := 3.0 # Raumhöhe; Klötze sind Vielfache davon
|
const HOEHE := 3.0 # Raumhöhe -- alles in der Stadt ist Erdgeschoss
|
||||||
const NAV_ZELLE_HOCH := 0.1
|
const NAV_ZELLE_HOCH := 0.1
|
||||||
var _bauten: Array = [] # alles, was der Etagenschnitt aufschneidet
|
var _bauten: Array = [] # alles, was der Etagenschnitt aufschneidet
|
||||||
|
|
||||||
@@ -66,9 +66,8 @@ func _environment() -> void:
|
|||||||
_sky = ProceduralSkyMaterial.new()
|
_sky = ProceduralSkyMaterial.new()
|
||||||
sky.sky_material = _sky
|
sky.sky_material = _sky
|
||||||
_env.sky = sky
|
_env.sky = sky
|
||||||
_env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
|
_env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR # der Nachthimmel ist zu dunkel als Lichtquelle
|
||||||
_env.glow_enabled = true
|
_env.glow_enabled = false # Tageslicht braucht kein Leuchten; kostet ein Fünftel der Bildrate
|
||||||
_env.glow_intensity = 0.35
|
|
||||||
_env.fog_enabled = true
|
_env.fog_enabled = true
|
||||||
_env.fog_density = 0.0012
|
_env.fog_density = 0.0012
|
||||||
we.environment = _env
|
we.environment = _env
|
||||||
@@ -76,7 +75,8 @@ func _environment() -> void:
|
|||||||
|
|
||||||
_sun = DirectionalLight3D.new()
|
_sun = DirectionalLight3D.new()
|
||||||
_sun.shadow_enabled = true
|
_sun.shadow_enabled = true
|
||||||
_sun.shadow_blur = 1.5
|
_sun.shadow_blur = 1.0
|
||||||
|
_sun.directional_shadow_max_distance = 110.0 # die Kamera sieht 60 m weit; nah und scharf statt weit und weich
|
||||||
add_child(_sun)
|
add_child(_sun)
|
||||||
|
|
||||||
|
|
||||||
@@ -132,17 +132,16 @@ func _bebauung() -> void:
|
|||||||
var g: Vector2 = p["size"]
|
var g: Vector2 = p["size"]
|
||||||
if minf(g.x, g.y) < 3.0:
|
if minf(g.x, g.y) < 3.0:
|
||||||
continue
|
continue
|
||||||
var n := 1 + rng.randi_range(0, 3)
|
var hoehe := HOEHE # nur Erdgeschoss, wie alles in der Stadt
|
||||||
var hoehe := HOEHE * float(n)
|
# acht Töne statt achtzig: gleiche Farbe heißt gleiches Material
|
||||||
var ton := Color.from_hsv(fmod(0.07 + rng.randf() * 0.12, 1.0),
|
var ton := Color.from_hsv(0.07 + float(rng.randi_range(0, 3)) * 0.04,
|
||||||
0.10 + rng.randf() * 0.14, 0.62 + rng.randf() * 0.26)
|
0.10 + float(rng.randi_range(0, 1)) * 0.12, 0.62 + float(rng.randi_range(0, 3)) * 0.08)
|
||||||
var bau := Node3D.new()
|
var bau := Node3D.new()
|
||||||
add_child(bau)
|
add_child(bau)
|
||||||
var klotz := Prims.box(bau, c + Vector3(0, hoehe * 0.5, 0), Vector3(g.x, hoehe, g.y), ton, false)
|
var klotz := Prims.box(bau, c + Vector3(0, hoehe * 0.5, 0), Vector3(g.x, hoehe, g.y), ton, false)
|
||||||
# Kollision etwas größer: so schließt sich die Fuge zum Nachbarn, niemand keilt sich hinein
|
# Kollision etwas größer: so schließt sich die Fuge zum Nachbarn, niemand keilt sich hinein
|
||||||
Prims._add_box_body(klotz, Vector3(g.x + 0.9, hoehe, g.y + 0.9))
|
Prims._add_box_body(klotz, Vector3(g.x + 0.9, hoehe, g.y + 0.9))
|
||||||
for f in range(n):
|
_fenster(bau, c, g, ton)
|
||||||
_fenster(bau, c + Vector3(0, HOEHE * float(f), 0), g, ton)
|
|
||||||
Prims.box(bau, c + Vector3(0, hoehe + 0.2, 0),
|
Prims.box(bau, c + Vector3(0, hoehe + 0.2, 0),
|
||||||
Vector3(g.x + 0.5, 0.4, g.y + 0.5), ton.darkened(0.25))
|
Vector3(g.x + 0.5, 0.4, g.y + 0.5), ton.darkened(0.25))
|
||||||
if rng.randf() < 0.35:
|
if rng.randf() < 0.35:
|
||||||
@@ -167,7 +166,9 @@ func _orte() -> void:
|
|||||||
add_child(raum)
|
add_child(raum)
|
||||||
var waende: Array = Prims.room(raum, c, groesse, farbe, farbe.darkened(0.35),
|
var waende: Array = Prims.room(raum, c, groesse, farbe, farbe.darkened(0.35),
|
||||||
Orte.DIR_VEC[pl["dir"]], tuer, true)
|
Orte.DIR_VEC[pl["dir"]], tuer, true)
|
||||||
_fenster(raum, c, Vector2(groesse.x, groesse.z), farbe)
|
var band := Node3D.new() # verschwindet drinnen mit dem Dach
|
||||||
|
add_child(band)
|
||||||
|
_fenster(band, c, Vector2(groesse.x, groesse.z), farbe, true)
|
||||||
_einrichtung(raum, c, groesse, farbe, key)
|
_einrichtung(raum, c, groesse, farbe, key)
|
||||||
var wer := HumanDB.bewohner_von(key)
|
var wer := HumanDB.bewohner_von(key)
|
||||||
if not wer.is_empty():
|
if not wer.is_empty():
|
||||||
@@ -183,19 +184,24 @@ func _orte() -> void:
|
|||||||
add_child(dach)
|
add_child(dach)
|
||||||
Prims.box(dach, c + Vector3(0, HOEHE + 0.1, 0),
|
Prims.box(dach, c + Vector3(0, HOEHE + 0.1, 0),
|
||||||
Vector3(groesse.x + 0.4, 0.2, groesse.z + 0.4), farbe.darkened(0.3))
|
Vector3(groesse.x + 0.4, 0.2, groesse.z + 0.4), farbe.darkened(0.3))
|
||||||
Prims.label(self, c + Vector3(0, HOEHE + 1.6, 0), str(pl["name"]), 0.55, Color(1, 1, 0.9))
|
Prims.label(dach, c + Vector3(0, HOEHE + 1.6, 0), str(pl["name"]), 0.55, Color(1, 1, 0.9))
|
||||||
_bauten.append({"c": c, "size": Vector2(groesse.x, groesse.z), "hoch": HOEHE,
|
_bauten.append({"c": c, "size": Vector2(groesse.x, groesse.z), "hoch": HOEHE,
|
||||||
"teile": [dach], "waende": waende})
|
"teile": [dach, band], "waende": waende, "raum": raum})
|
||||||
|
|
||||||
|
|
||||||
## Ein Fensterband je Wand, damit man die Stockwerke von außen zählt.
|
## Ein Fensterband: bei Klötzen ein Kasten, der rundum eine Handbreit
|
||||||
func _fenster(parent: Node, cf: Vector3, g: Vector2, ton: Color) -> void:
|
## übersteht (ein Netz statt vier); bei Häusern vier dünne Streifen außen an
|
||||||
|
## den Wänden -- ein Kasten läge als Decke über dem Raum.
|
||||||
|
func _fenster(parent: Node, cf: Vector3, g: Vector2, ton: Color, hohl: bool = false) -> void:
|
||||||
var glas := ton.darkened(0.55).lerp(Color(0.35, 0.45, 0.6), 0.5)
|
var glas := ton.darkened(0.55).lerp(Color(0.35, 0.45, 0.6), 0.5)
|
||||||
|
if minf(g.x, g.y) < 3.4:
|
||||||
|
return
|
||||||
|
if not hohl:
|
||||||
|
Prims.box(parent, cf + Vector3(0, 1.75, 0), Vector3(g.x + 0.1, 0.9, g.y + 0.1), glas, false)
|
||||||
|
return
|
||||||
for d: Vector3 in [Vector3.FORWARD, Vector3.BACK, Vector3.LEFT, Vector3.RIGHT]:
|
for d: Vector3 in [Vector3.FORWARD, Vector3.BACK, Vector3.LEFT, Vector3.RIGHT]:
|
||||||
var quer := absf(d.z) > 0.0
|
var quer := absf(d.z) > 0.0
|
||||||
var laenge: float = (g.x if quer else g.y) - 2.4
|
var laenge: float = (g.x if quer else g.y) - 2.4
|
||||||
if laenge < 1.0:
|
|
||||||
continue
|
|
||||||
var off := Vector3(d.x * (g.x * 0.5 + 0.05), 1.75, d.z * (g.y * 0.5 + 0.05))
|
var off := Vector3(d.x * (g.x * 0.5 + 0.05), 1.75, d.z * (g.y * 0.5 + 0.05))
|
||||||
Prims.box(parent, cf + off, Vector3(laenge, 0.9, 0.08) if quer else Vector3(0.08, 0.9, laenge),
|
Prims.box(parent, cf + off, Vector3(laenge, 0.9, 0.08) if quer else Vector3(0.08, 0.9, laenge),
|
||||||
glas, false)
|
glas, false)
|
||||||
@@ -390,7 +396,9 @@ func _himmel_aktualisieren() -> void:
|
|||||||
_sky.sky_horizon_color = nacht_hor.lerp(tag_hor, sonne)
|
_sky.sky_horizon_color = nacht_hor.lerp(tag_hor, sonne)
|
||||||
_sky.ground_horizon_color = nacht_hor.lerp(Color(0.55, 0.60, 0.55), sonne)
|
_sky.ground_horizon_color = nacht_hor.lerp(Color(0.55, 0.60, 0.55), sonne)
|
||||||
_sky.ground_bottom_color = nacht_top.lerp(Color(0.30, 0.38, 0.28), sonne)
|
_sky.ground_bottom_color = nacht_top.lerp(Color(0.30, 0.38, 0.28), sonne)
|
||||||
_env.ambient_light_energy = 0.45 + sonne * 0.8
|
# nachts mondblau statt schwarz: die Stadt bleibt lesbar, die Lichter setzen Akzente
|
||||||
|
_env.ambient_light_color = Color(0.30, 0.34, 0.52).lerp(Color(0.62, 0.66, 0.74), sonne)
|
||||||
|
_env.ambient_light_energy = 1.0 - sonne * 0.35 # tags übernimmt die Sonne, sonst wäscht es aus
|
||||||
_env.fog_light_color = nacht_hor.lerp(tag_hor, sonne)
|
_env.fog_light_color = nacht_hor.lerp(tag_hor, sonne)
|
||||||
|
|
||||||
var nacht := 1.0 - sonne
|
var nacht := 1.0 - sonne
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ var _erde_version := -1
|
|||||||
var auf_erde := false
|
var auf_erde := false
|
||||||
var _fokus: Node3D = null
|
var _fokus: Node3D = null
|
||||||
var _fokus_mensch: 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:
|
func _ready() -> void:
|
||||||
@@ -119,6 +121,8 @@ func _aktivieren(neu: Node3D) -> void:
|
|||||||
w.set("spieler", player if an else null)
|
w.set("spieler", player if an else null)
|
||||||
w.call("aktiv_setzen", an)
|
w.call("aktiv_setzen", an)
|
||||||
welt = neu
|
welt = neu
|
||||||
|
_interaktive = []
|
||||||
|
_fokus_takt = 0.0
|
||||||
Game.teleport_lock = 1.0
|
Game.teleport_lock = 1.0
|
||||||
_fokus = null
|
_fokus = null
|
||||||
_fokus_mensch = null
|
_fokus_mensch = null
|
||||||
@@ -134,6 +138,10 @@ func _process(delta: float) -> void:
|
|||||||
return
|
return
|
||||||
Game.notiz_sperre = maxf(0.0, Game.notiz_sperre - delta)
|
Game.notiz_sperre = maxf(0.0, Game.notiz_sperre - delta)
|
||||||
Game.situationen_ticken(dt, auf_erde and not Game.schlaeft_ziel())
|
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()
|
_fokus_suchen()
|
||||||
_markieren()
|
_markieren()
|
||||||
_dabeisein()
|
_dabeisein()
|
||||||
@@ -177,9 +185,13 @@ func _fokus_suchen() -> void:
|
|||||||
_fokus_mensch = null
|
_fokus_mensch = null
|
||||||
var p := player.global_position
|
var p := player.global_position
|
||||||
var best := REICHWEITE_OBJEKT
|
var best := REICHWEITE_OBJEKT
|
||||||
for n in get_tree().get_nodes_in_group("interactable"):
|
if _interaktive.is_empty():
|
||||||
if not welt.is_ancestor_of(n):
|
for n in get_tree().get_nodes_in_group("interactable"):
|
||||||
continue # die andere Welt liegt im selben Raum
|
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)
|
var d: float = p.distance_to((n as Node3D).global_position)
|
||||||
if d < best:
|
if d < best:
|
||||||
best = d
|
best = d
|
||||||
|
|||||||
@@ -68,16 +68,18 @@ func _ready() -> void:
|
|||||||
# Schleier: oben schmal, unten weit, halb durchsichtig
|
# Schleier: oben schmal, unten weit, halb durchsichtig
|
||||||
_schleier = Prims.cyl(body, Vector3(0, 1.05, 0), 0.6, 1.7, _farbe, false)
|
_schleier = Prims.cyl(body, Vector3(0, 1.05, 0), 0.6, 1.7, _farbe, false)
|
||||||
(_schleier.mesh as CylinderMesh).top_radius = 0.24
|
(_schleier.mesh as CylinderMesh).top_radius = 0.24
|
||||||
_schleier_mat = Prims.mat(Color(_farbe, 0.55), 0.35)
|
_schleier_mat = Prims.mat(Color(_farbe, 0.55), 0.35).duplicate()
|
||||||
_schleier.material_override = _schleier_mat
|
_schleier.material_override = _schleier_mat
|
||||||
_schleier.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
_schleier.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||||
# Lichtkern im Schleier
|
# Lichtkern im Schleier
|
||||||
_kern = Prims.sphere(body, Vector3(0, 1.15, 0), 0.26, _farbe, 2.2)
|
_kern = Prims.sphere(body, Vector3(0, 1.15, 0), 0.26, _farbe, 2.2)
|
||||||
_kern_mat = _kern.material_override as StandardMaterial3D
|
_kern_mat = (_kern.material_override as StandardMaterial3D).duplicate()
|
||||||
|
_kern.material_override = _kern_mat
|
||||||
_kern.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
_kern.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||||
_kopf = Prims.sphere(body, Vector3(0, 2.0, 0), 0.3, _farbe.lightened(0.4), 0.6)
|
_kopf = Prims.sphere(body, Vector3(0, 2.0, 0), 0.3, _farbe.lightened(0.4), 0.6)
|
||||||
_kopf.name = "Kopf"
|
_kopf.name = "Kopf"
|
||||||
_kopf_mat = _kopf.material_override as StandardMaterial3D
|
_kopf_mat = (_kopf.material_override as StandardMaterial3D).duplicate()
|
||||||
|
_kopf.material_override = _kopf_mat
|
||||||
for sx: float in [-1.0, 1.0]:
|
for sx: float in [-1.0, 1.0]:
|
||||||
var auge := Prims.box(body, Vector3(sx * 0.11, 2.04, -0.27), Vector3(0.07, 0.11, 0.05),
|
var auge := Prims.box(body, Vector3(sx * 0.11, 2.04, -0.27), Vector3(0.07, 0.11, 0.05),
|
||||||
Color(1, 1, 1), false, 3.0)
|
Color(1, 1, 1), false, 3.0)
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
class_name Prims
|
class_name Prims
|
||||||
## Kleine Helfer, um die Welt aus Godot-Primitiven zusammenzusetzen.
|
## Kleine Helfer, um die Welt aus Godot-Primitiven zusammenzusetzen.
|
||||||
|
|
||||||
|
## Gleiche Farbe, gleiches Material: tausend Klötze brauchen keine tausend
|
||||||
|
## Materialien. Wer ein Material verändern will, dupliziert es sich.
|
||||||
|
static var _materialien: Dictionary = {}
|
||||||
|
|
||||||
static func mat(c: Color, emis: float = 0.0, rough: float = 0.85) -> StandardMaterial3D:
|
static func mat(c: Color, emis: float = 0.0, rough: float = 0.85) -> StandardMaterial3D:
|
||||||
|
var key := "%s|%.2f|%.2f" % [str(c), emis, rough]
|
||||||
|
if _materialien.has(key):
|
||||||
|
return _materialien[key]
|
||||||
var m := StandardMaterial3D.new()
|
var m := StandardMaterial3D.new()
|
||||||
m.albedo_color = c
|
m.albedo_color = c
|
||||||
m.roughness = rough
|
m.roughness = rough
|
||||||
@@ -12,6 +19,7 @@ static func mat(c: Color, emis: float = 0.0, rough: float = 0.85) -> StandardMat
|
|||||||
m.emission_energy_multiplier = emis
|
m.emission_energy_multiplier = emis
|
||||||
if c.a < 1.0:
|
if c.a < 1.0:
|
||||||
m.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
m.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
_materialien[key] = m
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
@@ -159,35 +167,44 @@ static func _wand(mi: MeshInstance3D, normale: Vector3) -> MeshInstance3D:
|
|||||||
return mi
|
return mi
|
||||||
|
|
||||||
|
|
||||||
|
const STUMPF := 0.6 # so hoch bleiben die Wände, wenn man drinnen steht
|
||||||
|
|
||||||
## Schnitt wie bei den Sims: steht der Spieler in einem Bau, verschwinden Dach
|
## Schnitt wie bei den Sims: steht der Spieler in einem Bau, verschwinden Dach
|
||||||
## (bei Klötzen der ganze Klotz) und die beiden Wände, die zur Kamera zeigen.
|
## und Schild, und alle Wände des Hauses werden zu kniehohen Stümpfen gekürzt.
|
||||||
## Ein Bau: {c, size, hoch, teile, waende}. Ein Bau zwischen Kamera und
|
## Nichts wird durchsichtig. Ein Bau: {c, size, hoch, teile, waende}.
|
||||||
## Spieler wird durchsichtig, sonst verschwände der Spieler hinter jedem Nachbarhaus.
|
static func schnitt(bauten: Array, spieler: Vector3, _kamera: Vector3) -> void:
|
||||||
static func schnitt(bauten: Array, spieler: Vector3, kamera: Vector3) -> void:
|
|
||||||
for b in bauten:
|
for b in bauten:
|
||||||
var c: Vector3 = b["c"]
|
var c: Vector3 = b["c"]
|
||||||
var g: Vector2 = b["size"]
|
var g: Vector2 = b["size"]
|
||||||
var drin: bool = absf(spieler.x - c.x) < g.x * 0.5 + 0.3 \
|
var drin: bool = absf(spieler.x - c.x) < g.x * 0.5 + 0.3 \
|
||||||
and absf(spieler.z - c.z) < g.y * 0.5 + 0.3 \
|
and absf(spieler.z - c.z) < g.y * 0.5 + 0.3 \
|
||||||
and spieler.y > c.y - 0.5 and spieler.y < c.y + float(b["hoch"]) + 0.5
|
and spieler.y > c.y - 0.5 and spieler.y < c.y + float(b["hoch"]) + 0.5
|
||||||
var kasten := AABB(Vector3(c.x - g.x * 0.5, c.y, c.z - g.y * 0.5),
|
if drin == bool(b.get("drin", false)):
|
||||||
Vector3(g.x, float(b["hoch"]), g.y))
|
continue # nur anfassen, wenn sich etwas ändert
|
||||||
var im_weg: bool = not drin \
|
b["drin"] = drin
|
||||||
and kasten.intersects_segment(kamera, spieler + Vector3(0, 0.8, 0)) != null
|
|
||||||
if bool(b.get("im_weg", false)) != im_weg:
|
|
||||||
b["im_weg"] = im_weg
|
|
||||||
if not b.has("meshes"):
|
|
||||||
b["meshes"] = _meshes_von(b)
|
|
||||||
for mi in b["meshes"]:
|
|
||||||
(mi as GeometryInstance3D).transparency = 0.82 if im_weg else 0.0
|
|
||||||
(mi as GeometryInstance3D).cast_shadow = \
|
|
||||||
GeometryInstance3D.SHADOW_CASTING_SETTING_OFF if im_weg \
|
|
||||||
else GeometryInstance3D.SHADOW_CASTING_SETTING_ON
|
|
||||||
for t in b["teile"]:
|
for t in b["teile"]:
|
||||||
(t as Node3D).visible = not drin
|
(t as Node3D).visible = not drin
|
||||||
for w in b["waende"]:
|
for w in b["waende"]:
|
||||||
var n: Vector3 = w.get_meta("normal")
|
_stumpf(w as MeshInstance3D, c.y, drin)
|
||||||
(w as Node3D).visible = not (drin and n.dot(kamera - c) > 0.0)
|
|
||||||
|
|
||||||
|
## Eine Wand kürzen oder wiederherstellen. Was ganz oben sitzt (der Türsturz),
|
||||||
|
## verschwindet drinnen ganz.
|
||||||
|
static func _stumpf(w: MeshInstance3D, boden: float, kurz: bool) -> void:
|
||||||
|
var bm := w.mesh as BoxMesh
|
||||||
|
if bm == null:
|
||||||
|
w.visible = not kurz
|
||||||
|
return
|
||||||
|
if not w.has_meta("voll"):
|
||||||
|
w.set_meta("voll", Vector2(bm.size.y, w.position.y))
|
||||||
|
var voll: Vector2 = w.get_meta("voll")
|
||||||
|
var unten: float = voll.y - voll.x * 0.5 - boden
|
||||||
|
if unten > 0.3:
|
||||||
|
w.visible = not kurz
|
||||||
|
return
|
||||||
|
w.visible = true
|
||||||
|
bm.size.y = STUMPF if kurz else voll.x
|
||||||
|
w.position.y = (boden + STUMPF * 0.5) if kurz else voll.y
|
||||||
|
|
||||||
|
|
||||||
## Ein Satteldach: ein geschlossenes Prisma über den Wänden. Der First läuft
|
## Ein Satteldach: ein geschlossenes Prisma über den Wänden. Der First läuft
|
||||||
|
|||||||
@@ -144,9 +144,13 @@ func _baue_hud() -> void:
|
|||||||
|
|
||||||
var steuerung := Label.new()
|
var steuerung := Label.new()
|
||||||
steuerung.text = "J Journal · 1-3 Tempo · Q Kamera · Mausrad Zoom · Esc"
|
steuerung.text = "J Journal · 1-3 Tempo · Q Kamera · Mausrad Zoom · Esc"
|
||||||
|
# rechts verankert und nach links wachsend: sonst ragt der Text aus dem Fenster
|
||||||
steuerung.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
steuerung.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||||
steuerung.position = Vector2(-260, 16)
|
steuerung.grow_horizontal = Control.GROW_DIRECTION_BEGIN
|
||||||
steuerung.size = Vector2(240, 24)
|
steuerung.offset_right = -16
|
||||||
|
steuerung.offset_left = -520
|
||||||
|
steuerung.offset_top = 16
|
||||||
|
steuerung.offset_bottom = 40
|
||||||
steuerung.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
steuerung.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||||
steuerung.add_theme_font_size_override("font_size", 14)
|
steuerung.add_theme_font_size_override("font_size", 14)
|
||||||
steuerung.add_theme_color_override("font_color", Color(1, 1, 1, 0.4))
|
steuerung.add_theme_color_override("font_color", Color(1, 1, 1, 0.4))
|
||||||
@@ -397,7 +401,10 @@ func _baue_wirte() -> void:
|
|||||||
_titel.add_theme_color_override("font_color", Color(1, 0.93, 0.75))
|
_titel.add_theme_color_override("font_color", Color(1, 0.93, 0.75))
|
||||||
_box.add_child(_titel)
|
_box.add_child(_titel)
|
||||||
_scroll = ScrollContainer.new()
|
_scroll = ScrollContainer.new()
|
||||||
_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
# nie querscrollen, aber auch nie die Leiste verbreitern: zu breiter Inhalt
|
||||||
|
# wird abgeschnitten statt aus dem Fenster zu ragen
|
||||||
|
_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_SHOW_NEVER
|
||||||
|
_scroll.clip_contents = true
|
||||||
_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
_box.add_child(_scroll)
|
_box.add_child(_scroll)
|
||||||
_content = VBoxContainer.new()
|
_content = VBoxContainer.new()
|
||||||
@@ -411,6 +418,7 @@ func _baue_wirte() -> void:
|
|||||||
_drawer.offset_right = -12
|
_drawer.offset_right = -12
|
||||||
_drawer.offset_top = 56
|
_drawer.offset_top = 56
|
||||||
_drawer.offset_bottom = -(LEISTE_H + 22)
|
_drawer.offset_bottom = -(LEISTE_H + 22)
|
||||||
|
_drawer.clip_contents = true
|
||||||
_drawer.visible = false
|
_drawer.visible = false
|
||||||
_root.add_child(_drawer)
|
_root.add_child(_drawer)
|
||||||
|
|
||||||
@@ -435,9 +443,20 @@ func _wirt_fuer(kind: String) -> PanelContainer:
|
|||||||
return _modal
|
return _modal
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
var _takt := 0.0 # HUD- und Leistentexte alle 0,1 s; der Puls läuft je Bild
|
||||||
|
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
_takt -= delta
|
||||||
|
var dran := _takt <= 0.0
|
||||||
|
if dran:
|
||||||
|
_takt = 0.1
|
||||||
if not offen:
|
if not offen:
|
||||||
_hud_aktualisieren()
|
if dran:
|
||||||
|
_hud_aktualisieren()
|
||||||
|
_ablauf_aktualisieren()
|
||||||
|
else:
|
||||||
|
_ablauf_pulsen()
|
||||||
return
|
return
|
||||||
var wirt := _wirt_fuer(_kind)
|
var wirt := _wirt_fuer(_kind)
|
||||||
var wunsch := _content.get_combined_minimum_size().y
|
var wunsch := _content.get_combined_minimum_size().y
|
||||||
@@ -461,7 +480,21 @@ func _process(_delta: float) -> void:
|
|||||||
_karte_host.reset_size()
|
_karte_host.reset_size()
|
||||||
_karte_host.position = Vector2((vp.x - _karte_host.size.x) * 0.5,
|
_karte_host.position = Vector2((vp.x - _karte_host.size.x) * 0.5,
|
||||||
vp.y - LEISTE_H - 22.0 - _karte_host.size.y)
|
vp.y - LEISTE_H - 22.0 - _karte_host.size.y)
|
||||||
_ablauf_aktualisieren()
|
if dran:
|
||||||
|
_ablauf_aktualisieren()
|
||||||
|
else:
|
||||||
|
_ablauf_pulsen()
|
||||||
|
|
||||||
|
|
||||||
|
## Nur die laufende Stunde pulsiert -- das darf jedes Bild passieren.
|
||||||
|
func _ablauf_pulsen() -> void:
|
||||||
|
var id := Game.active_mission
|
||||||
|
if id == "" or not _ablauf.visible or not Game.situation_bei(id):
|
||||||
|
return
|
||||||
|
var jetzt := int(Game.time_h)
|
||||||
|
var puls := 0.5 + 0.5 * sin(Time.get_ticks_msec() * 0.008)
|
||||||
|
var zelle: ColorRect = _zellen[jetzt]
|
||||||
|
zelle.color = _ablauf_farbe(id, Game.slot_bei_stunde(id, jetzt)).lightened(0.3 + 0.4 * puls)
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------ Baukasten
|
# ------------------------------------------------------------------ Baukasten
|
||||||
@@ -549,6 +582,9 @@ func _trenner() -> void:
|
|||||||
func _zeigen(kind: String, zurueck: Callable = Callable()) -> void:
|
func _zeigen(kind: String, zurueck: Callable = Callable()) -> void:
|
||||||
_kind = kind
|
_kind = kind
|
||||||
_ziel = null
|
_ziel = null
|
||||||
|
if kind != "akte":
|
||||||
|
_drawer.remove_theme_stylebox_override("panel")
|
||||||
|
_titel.remove_theme_color_override("font_color")
|
||||||
var wirt := _wirt_fuer(kind)
|
var wirt := _wirt_fuer(kind)
|
||||||
if _box.get_parent() != wirt:
|
if _box.get_parent() != wirt:
|
||||||
if _box.get_parent() != null:
|
if _box.get_parent() != null:
|
||||||
@@ -726,6 +762,7 @@ func akte_panel(human_id: String) -> void:
|
|||||||
_akte_text(str(h["problem"]), 15)
|
_akte_text(str(h["problem"]), 15)
|
||||||
|
|
||||||
_akte_abschnitt("Befund")
|
_akte_abschnitt("Befund")
|
||||||
|
var offen_urs := 0
|
||||||
for u in Game.ursachen_von(human_id):
|
for u in Game.ursachen_von(human_id):
|
||||||
var name: String = str(u)
|
var name: String = str(u)
|
||||||
var stand: String
|
var stand: String
|
||||||
@@ -736,10 +773,12 @@ func akte_panel(human_id: String) -> void:
|
|||||||
stand = "[color=%s]%s · bremst auf %d %%[/color]" % [TINTE_ROT, woher,
|
stand = "[color=%s]%s · bremst auf %d %%[/color]" % [TINTE_ROT, woher,
|
||||||
int(float(Ursachen.LISTE[name]["faktor"]) * 100.0)]
|
int(float(Ursachen.LISTE[name]["faktor"]) * 100.0)]
|
||||||
else:
|
else:
|
||||||
_akte_text("[color=%s]? noch nichts gefunden — Spuren in der Wohnung, Momente im Tag[/color]" %
|
offen_urs += 1
|
||||||
TINTE_HELL, 15)
|
|
||||||
continue
|
continue
|
||||||
_akte_text("[b]%s[/b] %s" % [Ursachen.LISTE[name]["name"], stand], 15)
|
_akte_text("[b]%s[/b] %s" % [Ursachen.LISTE[name]["name"], stand], 15)
|
||||||
|
if offen_urs > 0:
|
||||||
|
_akte_text("[color=%s]? %s noch nichts gefunden — Spuren in der Wohnung, Momente im Tag[/color]" %
|
||||||
|
[TINTE_HELL, "eine Ursache," if offen_urs == 1 else "%d Ursachen," % offen_urs], 15)
|
||||||
var probleme: Array = []
|
var probleme: Array = []
|
||||||
for pr in Game.probleme_von(human_id):
|
for pr in Game.probleme_von(human_id):
|
||||||
var key := str(pr)
|
var key := str(pr)
|
||||||
@@ -768,19 +807,17 @@ func akte_panel(human_id: String) -> void:
|
|||||||
_scroll_frames = 4
|
_scroll_frames = 4
|
||||||
|
|
||||||
|
|
||||||
## Ein Blatt Papier im Drawer; alles Weitere der Akte landet darauf.
|
## Die Leiste selbst wird zum Blatt Papier. Ein Container im Container bricht
|
||||||
|
## den Text nicht um -- er richtet sich an der längsten Zeile aus und ragt hinaus.
|
||||||
func _papier() -> void:
|
func _papier() -> void:
|
||||||
var p := PanelContainer.new()
|
var s := _sb(C_PAPIER, 4, Color(0.78, 0.72, 0.6), 1)
|
||||||
var s := _sb(C_PAPIER, 3, Color(0.78, 0.72, 0.6), 1)
|
s.content_margin_left = 18
|
||||||
s.content_margin_left = 16
|
s.content_margin_right = 18
|
||||||
s.content_margin_right = 16
|
s.content_margin_top = 10
|
||||||
s.content_margin_top = 12
|
|
||||||
s.content_margin_bottom = 14
|
s.content_margin_bottom = 14
|
||||||
p.add_theme_stylebox_override("panel", s)
|
_drawer.add_theme_stylebox_override("panel", s)
|
||||||
_content.add_child(p)
|
_titel.add_theme_color_override("font_color", Color(0.17, 0.15, 0.13))
|
||||||
_blatt = VBoxContainer.new()
|
_blatt = _content
|
||||||
_blatt.add_theme_constant_override("separation", 6)
|
|
||||||
p.add_child(_blatt)
|
|
||||||
|
|
||||||
|
|
||||||
func _akte_text(bb: String, groesse: int = 15) -> RichTextLabel:
|
func _akte_text(bb: String, groesse: int = 15) -> RichTextLabel:
|
||||||
|
|||||||
@@ -891,6 +891,39 @@ func test_akte() -> void:
|
|||||||
p(text.contains("noch keine") and text.contains("noch nichts gefunden"),
|
p(text.contains("noch keine") and text.contains("noch nichts gefunden"),
|
||||||
"Ohne Spuren und Beobachtungen ist der Befund leer")
|
"Ohne Spuren und Beobachtungen ist der Befund leer")
|
||||||
p(_knopf_da("Hinfahren"), "Ein Knopf bringt einen vor die Tür")
|
p(_knopf_da("Hinfahren"), "Ein Knopf bringt einen vor die Tür")
|
||||||
|
await get_tree().process_frame
|
||||||
|
await get_tree().process_frame
|
||||||
|
var breit := 0.0
|
||||||
|
for c in _alle_kinder(ui()._content):
|
||||||
|
if c is Control:
|
||||||
|
breit = maxf(breit, (c as Control).size.x)
|
||||||
|
p(ui()._drawer.size.x <= UI.DRAWER_BREITE + 1.0 and breit <= ui()._content.size.x + 1.0,
|
||||||
|
"Nichts ragt aus der Leiste (Leiste %.0f, breitestes Element %.0f)" % [ui()._drawer.size.x, breit])
|
||||||
|
ui().journal_panel("techniken")
|
||||||
|
p(not ui()._drawer.has_theme_stylebox_override("panel"), "Andere Seiten sind wieder dunkel")
|
||||||
|
# keine Seite der Leiste darf breiter sein als die Leiste -- und die Leiste nicht als das Fenster
|
||||||
|
var zu_breit: Array = []
|
||||||
|
for seite in ["techniken", "missionen"]:
|
||||||
|
ui().journal_panel(seite)
|
||||||
|
await get_tree().process_frame
|
||||||
|
var mind := ui()._content.get_combined_minimum_size().x
|
||||||
|
if mind > UI.DRAWER_BREITE - 30.0 or ui()._drawer.size.x > UI.DRAWER_BREITE + 1.0:
|
||||||
|
zu_breit.append("%s (%.0f)" % [seite, mind])
|
||||||
|
ui().akte_panel(id)
|
||||||
|
await get_tree().process_frame
|
||||||
|
if ui()._content.get_combined_minimum_size().x > UI.DRAWER_BREITE - 30.0 \
|
||||||
|
or ui()._drawer.size.x > UI.DRAWER_BREITE + 1.0:
|
||||||
|
zu_breit.append("akte (%.0f)" % ui()._content.get_combined_minimum_size().x)
|
||||||
|
p(zu_breit.is_empty(), "Journal und Akte bleiben in der Leistenbreite%s" %
|
||||||
|
("" if zu_breit.is_empty() else " — zu breit: " + ", ".join(zu_breit)))
|
||||||
|
var rand: float = ui()._drawer.position.x + ui()._drawer.size.x
|
||||||
|
p(rand <= ui()._root.size.x, "Die Leiste endet im Fenster (%.0f von %.0f)" % [rand, ui()._root.size.x])
|
||||||
|
var hinweis_rechts := 0.0
|
||||||
|
for c in ui()._root.get_children():
|
||||||
|
if c is Label and (c as Label).text.begins_with("J Journal"):
|
||||||
|
hinweis_rechts = (c as Control).position.x + (c as Control).size.x
|
||||||
|
p(hinweis_rechts > 0.0 and hinweis_rechts <= ui()._root.size.x,
|
||||||
|
"Der Steuerungshinweis endet im Fenster (%.0f von %.0f)" % [hinweis_rechts, ui()._root.size.x])
|
||||||
var u := str((h["ursachen"] as Array)[0])
|
var u := str((h["ursachen"] as Array)[0])
|
||||||
Game.spur_lesen(id, u)
|
Game.spur_lesen(id, u)
|
||||||
ui().akte_panel(id)
|
ui().akte_panel(id)
|
||||||
@@ -1286,34 +1319,64 @@ func test_kamera_und_etagen() -> void:
|
|||||||
p(zuhause and absf(oben.global_position.y) < 0.3,
|
p(zuhause and absf(oben.global_position.y) < 0.3,
|
||||||
"Nachts liegt %s zu Hause, ebenerdig (y = %.1f)" % [oben.data["name"], oben.global_position.y])
|
"Nachts liegt %s zu Hause, ebenerdig (y = %.1f)" % [oben.data["name"], oben.global_position.y])
|
||||||
|
|
||||||
# Schnitt: drinnen verschwinden Dach und die Wände zur Kamera
|
# Schnitt wie bei den Sims: drinnen verschwindet das Dach, alle Wände werden Stümpfe
|
||||||
var bau: Dictionary = {}
|
var bau: Dictionary = {}
|
||||||
for b in welt()._bauten:
|
for b in welt()._bauten:
|
||||||
if (b["c"] as Vector3).distance_to(c) < 0.1:
|
if (b["c"] as Vector3).distance_to(c) < 0.1:
|
||||||
bau = b
|
bau = b
|
||||||
p(not bau.is_empty() and (bau["teile"] as Array).size() == 1 and (bau["waende"] as Array).size() >= 4,
|
p(not bau.is_empty() and (bau["teile"] as Array).size() >= 1 and (bau["waende"] as Array).size() >= 4,
|
||||||
"Sein Haus steht mit Dach und Wänden da")
|
"Sein Haus steht mit Dach und Wänden da")
|
||||||
sp.global_position = c + Vector3(0, 0.3, 0)
|
sp.global_position = c + Vector3(0, 0.3, 0)
|
||||||
sp.velocity = Vector3.ZERO
|
sp.velocity = Vector3.ZERO
|
||||||
sp.yaw = Player.CAM_YAW
|
sp.yaw = Player.CAM_YAW
|
||||||
await get_tree().physics_frame
|
await get_tree().physics_frame
|
||||||
welt()._process(0.05)
|
welt()._process(0.05)
|
||||||
var wand_weg := 0
|
var stuempfe := 0
|
||||||
|
var hoch := 0
|
||||||
for w in bau["waende"]:
|
for w in bau["waende"]:
|
||||||
if not (w as Node3D).visible:
|
var bm := (w as MeshInstance3D).mesh as BoxMesh
|
||||||
wand_weg += 1
|
if (w as Node3D).visible and bm.size.y <= Prims.STUMPF + 0.01:
|
||||||
|
stuempfe += 1
|
||||||
|
elif (w as Node3D).visible:
|
||||||
|
hoch += 1
|
||||||
p(not (bau["teile"][0] as Node3D).visible, "Steht der Geist im Haus, verschwindet das Dach")
|
p(not (bau["teile"][0] as Node3D).visible, "Steht der Geist im Haus, verschwindet das Dach")
|
||||||
p(wand_weg >= 2 and wand_weg < (bau["waende"] as Array).size(),
|
p(stuempfe >= 4 and hoch == 0,
|
||||||
"Die Wände zur Kamera hin sind weg, die hinteren bleiben (%d von %d Teile)" %
|
"Alle Wände sind kniehohe Stümpfe, keine bleibt hoch, nichts wird durchsichtig (%d Stümpfe)" % stuempfe)
|
||||||
[wand_weg, (bau["waende"] as Array).size()])
|
var durchsichtig := 0
|
||||||
|
for b in welt()._bauten:
|
||||||
|
for mi in Prims._meshes_von(b):
|
||||||
|
if (mi as GeometryInstance3D).transparency > 0.0:
|
||||||
|
durchsichtig += 1
|
||||||
|
p(durchsichtig == 0, "Kein Bau ist durchsichtig")
|
||||||
|
# nichts über Kniehöhe verdeckt drinnen den Blick von oben: kein Band, keine Decke
|
||||||
|
var hoch_sichtbar: Array = []
|
||||||
|
var knoten: Array = (bau["teile"] as Array).duplicate()
|
||||||
|
knoten.append(bau["raum"])
|
||||||
|
for k in knoten:
|
||||||
|
for mi in (k as Node).find_children("*", "MeshInstance3D", true, false):
|
||||||
|
var m := mi as MeshInstance3D
|
||||||
|
if not m.is_visible_in_tree():
|
||||||
|
continue
|
||||||
|
var kasten := m.get_aabb()
|
||||||
|
var kante: float = m.global_position.y + kasten.end.y * m.scale.y
|
||||||
|
var ist_wand := (bau["waende"] as Array).has(m)
|
||||||
|
if kante > 1.9 and not ist_wand:
|
||||||
|
hoch_sichtbar.append("%s (bis %.1f m)" % [m.name, kante])
|
||||||
|
p(hoch_sichtbar.is_empty(), "Drinnen verdeckt nichts über Kniehöhe die Sicht%s" %
|
||||||
|
("" if hoch_sichtbar.is_empty() else " — " + ", ".join(hoch_sichtbar)))
|
||||||
sp.global_position = c + Vector3(float(g[0]) + 4.0, 0.3, 0)
|
sp.global_position = c + Vector3(float(g[0]) + 4.0, 0.3, 0)
|
||||||
await get_tree().physics_frame
|
await get_tree().physics_frame
|
||||||
welt()._process(0.05)
|
welt()._process(0.05)
|
||||||
var alles := (bau["teile"][0] as Node3D).visible
|
var alles := (bau["teile"][0] as Node3D).visible
|
||||||
for w in bau["waende"]:
|
for w in bau["waende"]:
|
||||||
if not (w as Node3D).visible:
|
var bm := (w as MeshInstance3D).mesh as BoxMesh
|
||||||
alles = false
|
if not (w as Node3D).visible or bm.size.y <= Prims.STUMPF + 0.01:
|
||||||
p(alles, "Draußen steht das Haus wieder ganz")
|
alles = false # der Türsturz ist niedrig, aber kein Stumpf
|
||||||
|
p(alles, "Draußen steht das Haus wieder ganz, Wände in voller Höhe")
|
||||||
|
var hoechster_bau := 0.0
|
||||||
|
for b in welt()._bauten:
|
||||||
|
hoechster_bau = maxf(hoechster_bau, float(b["hoch"]))
|
||||||
|
p(hoechster_bau <= 3.5, "Kein Bau ist höher als ein Erdgeschoss (%.1f m)" % hoechster_bau)
|
||||||
|
|
||||||
# Klick auf einen Menschen
|
# Klick auf einen Menschen
|
||||||
sp.global_position = oben.global_position + Vector3(2.0, 1.4, 2.0)
|
sp.global_position = oben.global_position + Vector3(2.0, 1.4, 2.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user