This commit is contained in:
Team3
2026-09-16 20:06:38 +02:00
parent 7c4edaa439
commit a86d737d39
10 changed files with 252 additions and 96 deletions

View File

@@ -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_info := ""
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
@@ -127,19 +131,12 @@ func _hut(kopf: Vector3, c: Color) -> void:
## Angst duckt, Erschöpfung beugt, Unruhe zappelt -- solange die Klemme nicht
## überwunden ist. So sieht man Probleme beim Beobachten am Körper.
func _haltung(delta: float, geht: bool) -> 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
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))
if _takt <= 0.0:
_haltungsziele()
var zappeln := sin(Time.get_ticks_msec() * 0.011) * 0.06 if _zappelt and not geht else 0.0
_ober.rotation.x = lerpf(_ober.rotation.x, -_neigung_ziel, 1.0 - pow(0.02, 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
for i in range(_arme.size()):
var a: Node3D = _arme[i]
@@ -147,6 +144,17 @@ func _haltung(delta: float, geht: bool) -> void:
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,
## ist die Tätigkeit die Ersatzhandlung.
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.z = _aura.scale.x
_label.text = beschriftung()
# Auf der Erde tragen nur Fokus, Verfolgte, Mission und wer unter der Maus
# steht ein Schild -- sonst überdecken sich acht Namen im Café.
_label.visible = (not mit_mission and not bool(data.get("stumm", false))) \
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 (
Color(1, 0.9, 0.6) if aktiv else Color(1, 1, 1))

View File

@@ -91,7 +91,8 @@ func _environment() -> void:
sun.light_energy = 1.3
sun.light_color = Color(1.0, 0.84, 0.74)
sun.shadow_enabled = true
sun.shadow_blur = 2.5
sun.shadow_blur = 1.2
sun.directional_shadow_max_distance = 110.0
add_child(sun)
@@ -245,9 +246,9 @@ func _haeuser() -> void:
var waende: Array = Prims.room(self, c, groesse, farbe, farbe.darkened(0.35),
Orte.DIR_VEC[pl["dir"]], 3.4, true)
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,
"teile": [dach], "waende": waende})
Prims.label(self, c + Vector3(0, groesse.y + 4.2, 0), pl["name"], 0.6, Color(1, 0.95, 0.85))
"teile": [dach, schild], "waende": waende})
_einrichtung(c, groesse, farbe, key)
var licht := OmniLight3D.new()

View File

@@ -7,7 +7,7 @@ var humans: Array = []
var passanten: Array = [] # Statisten ohne Geschichte
var spieler: Node3D = null # setzt main, für den Etagenschnitt
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
var _bauten: Array = [] # alles, was der Etagenschnitt aufschneidet
@@ -66,9 +66,8 @@ func _environment() -> void:
_sky = ProceduralSkyMaterial.new()
sky.sky_material = _sky
_env.sky = sky
_env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
_env.glow_enabled = true
_env.glow_intensity = 0.35
_env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR # der Nachthimmel ist zu dunkel als Lichtquelle
_env.glow_enabled = false # Tageslicht braucht kein Leuchten; kostet ein Fünftel der Bildrate
_env.fog_enabled = true
_env.fog_density = 0.0012
we.environment = _env
@@ -76,7 +75,8 @@ func _environment() -> void:
_sun = DirectionalLight3D.new()
_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)
@@ -132,17 +132,16 @@ func _bebauung() -> void:
var g: Vector2 = p["size"]
if minf(g.x, g.y) < 3.0:
continue
var n := 1 + rng.randi_range(0, 3)
var hoehe := HOEHE * float(n)
var ton := Color.from_hsv(fmod(0.07 + rng.randf() * 0.12, 1.0),
0.10 + rng.randf() * 0.14, 0.62 + rng.randf() * 0.26)
var hoehe := HOEHE # nur Erdgeschoss, wie alles in der Stadt
# acht Töne statt achtzig: gleiche Farbe heißt gleiches Material
var ton := Color.from_hsv(0.07 + float(rng.randi_range(0, 3)) * 0.04,
0.10 + float(rng.randi_range(0, 1)) * 0.12, 0.62 + float(rng.randi_range(0, 3)) * 0.08)
var bau := Node3D.new()
add_child(bau)
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
Prims._add_box_body(klotz, Vector3(g.x + 0.9, hoehe, g.y + 0.9))
for f in range(n):
_fenster(bau, c + Vector3(0, HOEHE * float(f), 0), g, ton)
_fenster(bau, c, g, ton)
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))
if rng.randf() < 0.35:
@@ -167,7 +166,9 @@ func _orte() -> void:
add_child(raum)
var waende: Array = Prims.room(raum, c, groesse, farbe, farbe.darkened(0.35),
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)
var wer := HumanDB.bewohner_von(key)
if not wer.is_empty():
@@ -183,19 +184,24 @@ func _orte() -> void:
add_child(dach)
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))
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,
"teile": [dach], "waende": waende})
"teile": [dach, band], "waende": waende, "raum": raum})
## Ein Fensterband je Wand, damit man die Stockwerke von außen zählt.
func _fenster(parent: Node, cf: Vector3, g: Vector2, ton: Color) -> void:
## Ein Fensterband: bei Klötzen ein Kasten, der rundum eine Handbreit
## ü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)
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]:
var quer := absf(d.z) > 0.0
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))
Prims.box(parent, cf + off, Vector3(laenge, 0.9, 0.08) if quer else Vector3(0.08, 0.9, laenge),
glas, false)
@@ -390,7 +396,9 @@ func _himmel_aktualisieren() -> void:
_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_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)
var nacht := 1.0 - sonne

View File

@@ -15,6 +15,8 @@ var _erde_version := -1
var auf_erde := false
var _fokus: Node3D = null
var _fokus_mensch: Node3D = null
var _interaktive: Array = [] # Ansprechbares der aktiven Welt, einmal gesammelt
var _fokus_takt := 0.0 # Fokus, Markierung und Hinweis alle 0,05 s
func _ready() -> void:
@@ -119,6 +121,8 @@ func _aktivieren(neu: Node3D) -> void:
w.set("spieler", player if an else null)
w.call("aktiv_setzen", an)
welt = neu
_interaktive = []
_fokus_takt = 0.0
Game.teleport_lock = 1.0
_fokus = null
_fokus_mensch = null
@@ -134,6 +138,10 @@ func _process(delta: float) -> void:
return
Game.notiz_sperre = maxf(0.0, Game.notiz_sperre - delta)
Game.situationen_ticken(dt, auf_erde and not Game.schlaeft_ziel())
_fokus_takt -= delta
if _fokus_takt > 0.0:
return
_fokus_takt = 0.05
_fokus_suchen()
_markieren()
_dabeisein()
@@ -177,9 +185,13 @@ func _fokus_suchen() -> void:
_fokus_mensch = null
var p := player.global_position
var best := REICHWEITE_OBJEKT
for n in get_tree().get_nodes_in_group("interactable"):
if not welt.is_ancestor_of(n):
continue # die andere Welt liegt im selben Raum
if _interaktive.is_empty():
for n in get_tree().get_nodes_in_group("interactable"):
if welt.is_ancestor_of(n): # die andere Welt liegt im selben Raum
_interaktive.append(n)
for n in _interaktive:
if not is_instance_valid(n):
continue
var d: float = p.distance_to((n as Node3D).global_position)
if d < best:
best = d

View File

@@ -68,16 +68,18 @@ func _ready() -> void:
# Schleier: oben schmal, unten weit, halb durchsichtig
_schleier = Prims.cyl(body, Vector3(0, 1.05, 0), 0.6, 1.7, _farbe, false)
(_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.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
# Lichtkern im Schleier
_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
_kopf = Prims.sphere(body, Vector3(0, 2.0, 0), 0.3, _farbe.lightened(0.4), 0.6)
_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]:
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)

View File

@@ -1,7 +1,14 @@
class_name Prims
## 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:
var key := "%s|%.2f|%.2f" % [str(c), emis, rough]
if _materialien.has(key):
return _materialien[key]
var m := StandardMaterial3D.new()
m.albedo_color = c
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
if c.a < 1.0:
m.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
_materialien[key] = m
return m
@@ -159,35 +167,44 @@ static func _wand(mi: MeshInstance3D, normale: Vector3) -> MeshInstance3D:
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
## (bei Klötzen der ganze Klotz) und die beiden Wände, die zur Kamera zeigen.
## Ein Bau: {c, size, hoch, teile, waende}. Ein Bau zwischen Kamera und
## Spieler wird durchsichtig, sonst verschwände der Spieler hinter jedem Nachbarhaus.
static func schnitt(bauten: Array, spieler: Vector3, kamera: Vector3) -> void:
## und Schild, und alle Wände des Hauses werden zu kniehohen Stümpfen gekürzt.
## Nichts wird durchsichtig. Ein Bau: {c, size, hoch, teile, waende}.
static func schnitt(bauten: Array, spieler: Vector3, _kamera: Vector3) -> void:
for b in bauten:
var c: Vector3 = b["c"]
var g: Vector2 = b["size"]
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 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),
Vector3(g.x, float(b["hoch"]), g.y))
var im_weg: bool = not 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
if drin == bool(b.get("drin", false)):
continue # nur anfassen, wenn sich etwas ändert
b["drin"] = drin
for t in b["teile"]:
(t as Node3D).visible = not drin
for w in b["waende"]:
var n: Vector3 = w.get_meta("normal")
(w as Node3D).visible = not (drin and n.dot(kamera - c) > 0.0)
_stumpf(w as MeshInstance3D, c.y, drin)
## 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

View File

@@ -144,9 +144,13 @@ func _baue_hud() -> void:
var steuerung := Label.new()
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.position = Vector2(-260, 16)
steuerung.size = Vector2(240, 24)
steuerung.grow_horizontal = Control.GROW_DIRECTION_BEGIN
steuerung.offset_right = -16
steuerung.offset_left = -520
steuerung.offset_top = 16
steuerung.offset_bottom = 40
steuerung.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
steuerung.add_theme_font_size_override("font_size", 14)
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))
_box.add_child(_titel)
_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
_box.add_child(_scroll)
_content = VBoxContainer.new()
@@ -411,6 +418,7 @@ func _baue_wirte() -> void:
_drawer.offset_right = -12
_drawer.offset_top = 56
_drawer.offset_bottom = -(LEISTE_H + 22)
_drawer.clip_contents = true
_drawer.visible = false
_root.add_child(_drawer)
@@ -435,9 +443,20 @@ func _wirt_fuer(kind: String) -> PanelContainer:
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:
_hud_aktualisieren()
if dran:
_hud_aktualisieren()
_ablauf_aktualisieren()
else:
_ablauf_pulsen()
return
var wirt := _wirt_fuer(_kind)
var wunsch := _content.get_combined_minimum_size().y
@@ -461,7 +480,21 @@ func _process(_delta: float) -> void:
_karte_host.reset_size()
_karte_host.position = Vector2((vp.x - _karte_host.size.x) * 0.5,
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
@@ -549,6 +582,9 @@ func _trenner() -> void:
func _zeigen(kind: String, zurueck: Callable = Callable()) -> void:
_kind = kind
_ziel = null
if kind != "akte":
_drawer.remove_theme_stylebox_override("panel")
_titel.remove_theme_color_override("font_color")
var wirt := _wirt_fuer(kind)
if _box.get_parent() != wirt:
if _box.get_parent() != null:
@@ -726,6 +762,7 @@ func akte_panel(human_id: String) -> void:
_akte_text(str(h["problem"]), 15)
_akte_abschnitt("Befund")
var offen_urs := 0
for u in Game.ursachen_von(human_id):
var name: String = str(u)
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,
int(float(Ursachen.LISTE[name]["faktor"]) * 100.0)]
else:
_akte_text("[color=%s]? noch nichts gefunden — Spuren in der Wohnung, Momente im Tag[/color]" %
TINTE_HELL, 15)
offen_urs += 1
continue
_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 = []
for pr in Game.probleme_von(human_id):
var key := str(pr)
@@ -768,19 +807,17 @@ func akte_panel(human_id: String) -> void:
_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:
var p := PanelContainer.new()
var s := _sb(C_PAPIER, 3, Color(0.78, 0.72, 0.6), 1)
s.content_margin_left = 16
s.content_margin_right = 16
s.content_margin_top = 12
var s := _sb(C_PAPIER, 4, Color(0.78, 0.72, 0.6), 1)
s.content_margin_left = 18
s.content_margin_right = 18
s.content_margin_top = 10
s.content_margin_bottom = 14
p.add_theme_stylebox_override("panel", s)
_content.add_child(p)
_blatt = VBoxContainer.new()
_blatt.add_theme_constant_override("separation", 6)
p.add_child(_blatt)
_drawer.add_theme_stylebox_override("panel", s)
_titel.add_theme_color_override("font_color", Color(0.17, 0.15, 0.13))
_blatt = _content
func _akte_text(bb: String, groesse: int = 15) -> RichTextLabel: