This commit is contained in:
Team3
2026-09-16 16:15:32 +02:00
parent e813b5f7aa
commit 7c4edaa439
19 changed files with 1244 additions and 636 deletions

View File

@@ -6,6 +6,7 @@ class_name UI
## der Mitte für Orte. Die Zeitleiste unten ist der Tag des Menschen -- alles
## im Spiel hängt daran.
signal hinfahren(id: String)
signal panel_opened
signal panel_closed
@@ -25,8 +26,15 @@ const C_UNBEKANNT := Color(0.22, 0.23, 0.30)
const C_GESEHEN := Color(0.46, 0.50, 0.62)
const C_SCHLAF := Color(0.30, 0.33, 0.44)
const C_WARN := Color(0.85, 0.66, 0.32)
const C_VERDAECHTIG := Color(0.58, 0.46, 0.26) # Spur gelesen, Stunde noch nicht gesehen
const C_BLOCKER := Color(0.84, 0.42, 0.35)
const C_SYSTEM := Color(0.44, 0.78, 0.50)
const C_PAPIER := Color(0.95, 0.92, 0.85) # die Akte: Papier, dunkle Tinte
const C_PAPIER_ZEILE := Color(0.90, 0.86, 0.78)
const TINTE := "#2b2620"
const TINTE_HELL := "#6b6257"
const TINTE_ROT := "#a83a2e"
const TINTE_GRUEN := "#3d7a45"
var offen := false
var _kind := ""
@@ -299,6 +307,8 @@ func _ablauf_farbe(human_id: String, nr: int) -> Color:
return C_BLOCKER
Game.Stunde.SCHLAF:
return C_SCHLAF
Game.Stunde.VERDAECHTIG:
return C_VERDAECHTIG
return C_GESEHEN
@@ -310,20 +320,24 @@ func _ablauf_aktualisieren() -> void:
return
var jetzt := int(Game.time_h)
var offen_std := 0
var verdaechtig := 0
var puls := 0.5 + 0.5 * sin(Time.get_ticks_msec() * 0.008)
for stunde in range(24):
var nr := Game.slot_bei_stunde(id, stunde)
var f := _ablauf_farbe(id, nr)
if not Game.gesehen_slot(id, nr):
offen_std += 1
if Game.slot_zustand(id, nr) == Game.Stunde.VERDAECHTIG:
verdaechtig += 1
var zelle: ColorRect = _zellen[stunde]
if stunde == jetzt:
f = f.lightened(0.55 if not Game.situation_bei(id) else 0.3 + 0.4 * puls)
zelle.color = f
zelle.custom_minimum_size.y = 24 if stunde == jetzt else 16
zelle.tooltip_text = "%02d Uhr — %s" % [stunde, _stunden_text(id, nr)]
_ablauf_titel.text = "Ihr Tag 0 – 23 Uhr%s" % \
("" if offen_std == 0 else " · %d Stunden unbekannt" % offen_std)
_ablauf_titel.text = "Ihr Tag 0 – 23 Uhr%s%s" % [
"" if offen_std == 0 else " · %d Stunden unbekannt" % offen_std,
"" if verdaechtig == 0 else " · %d verdächtig" % verdaechtig]
var urs: Array = []
for u in Game.ursachen_von(id):
@@ -414,7 +428,7 @@ func _baue_wirte() -> void:
func _wirt_fuer(kind: String) -> PanelContainer:
match kind:
"journal", "technik", "systeme":
"journal", "technik", "akte":
return _drawer
"slot", "situation", "anwenden":
return _karte_host
@@ -609,7 +623,7 @@ func _reiter() -> void:
_content.add_child(hb)
var seiten := [
["techniken", "Techniken (%d)" % Game.known.size()],
["missionen", "Missionen (%d)" % Game.progress.size()],
["missionen", "Akten (%d)" % Game.progress.size()],
]
for eintrag in seiten:
var b := Button.new()
@@ -675,44 +689,145 @@ func _journal_missionen() -> void:
stand = "aktiv · %d %%" % int(Game.mission_progress(id))
farbe = C_AKZENT
_karte("%s · %s" % [h["name"], stand], farbe,
func() -> void: systeme_panel(id))
func() -> void: akte_panel(id))
# ---------------------------------------------- Diagnose und Systeme
# ---------------------------------------------- Die Akte
## Alles zu einem Menschen auf einem Blatt: Person und Traum, was im Weg steht,
## der Befund aus Spuren und Beobachtungen, der Tag, die Routinen, die Notizen.
## Keine Patientenakte -- die Leute sind nicht krank, sie haben Gewohnheiten.
var _blatt: VBoxContainer = null
## Der Traum scheitert an Ursachen. Die stehen nicht im Panel -- man findet sie,
## indem man dabeisteht. Gegen jede gefundene Ursache baut man ein System in
## den Tag. Je mehr abgedeckt ist, desto schneller kommt der Traum voran.
func systeme_panel(human_id: String) -> void:
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "systeme" else 0
var h := HumanDB.by_id(human_id)
_leeren("%s — Tagesablauf" % h["name"])
_text("%d %% · +%d %% je Tag · [color=#aaaaaa]%s[/color]" %
[int(Game.mission_progress(human_id)), int(round(Game.traum_tempo(human_id))),
h["traum"]], 16)
_trenner()
akte_panel(human_id)
_text("[b]Woran es scheitert[/b]", 17)
var offene := 0
func akte_panel(human_id: String) -> void:
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "akte" else 0
var h := HumanDB.by_id(human_id)
_leeren("Akte")
_papier()
var erfuellt := Game.completed.has(human_id)
_akte_text("[b]%s[/b] [color=%s]%d · %s[/color]" % [h["name"], TINTE_HELL,
int(h.get("alter", 0)), h.get("beruf", "")], 20)
_akte_text("[color=%s]Traum[/color] „%s“" % [TINTE_HELL, h["traum"]], 16)
if erfuellt:
_stempel("TRAUM ERFÜLLT")
else:
_akte_text("[color=%s]Stand[/color] %d %% · +%d %% je Tag" % [TINTE_HELL,
int(Game.mission_progress(human_id)), int(round(Game.traum_tempo(human_id)))], 16)
_knopf("Hinfahren — vor die Tür", func() -> void:
schliessen()
hinfahren.emit(human_id), true, _blatt)
_akte_abschnitt("Was im Weg steht")
_akte_text(str(h["problem"]), 15)
_akte_abschnitt("Befund")
for u in Game.ursachen_von(human_id):
var name: String = str(u)
if not Game.ursache_bekannt(human_id, name):
_text("[color=#8e93a8]? — noch nicht erkannt[/color]", 15)
offene += 1
var stand: String
if Game.ursache_geloest(human_id, name) > 0.0:
stand = "[color=%s]Routine steht[/color]" % TINTE_GRUEN
elif Game.ursache_bekannt(human_id, name):
var woher := "Spur gelesen" if Game.spur_bekannt(human_id, name) else "beobachtet"
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)
continue
var g := Game.ursache_geloest(human_id, name)
var stand := "[color=#88ff99]gelöst[/color]" if g > 0.0 else \
"[color=#ff9977]offen · bremst auf %d %%[/color]" % \
int(float(Ursachen.LISTE[name]["faktor"]) * 100.0)
_text("[b]%s[/b] %s" % [Ursachen.LISTE[name]["name"], stand], 15)
if offene > 0: _trenner()
_akte_text("[b]%s[/b] %s" % [Ursachen.LISTE[name]["name"], stand], 15)
var probleme: Array = []
for pr in Game.probleme_von(human_id):
var key := str(pr)
if not Game.problem_bekannt(human_id, key):
probleme.append("[color=%s]?[/color]" % TINTE_HELL)
elif Game.problem_ueberwunden(human_id, key):
probleme.append("[color=%s]%s ✓[/color]" % [TINTE_GRUEN, Probleme.name_von(key)])
else:
probleme.append("%s %d/%d" % [Probleme.name_von(key),
Game.problem_fortschritt(human_id, key), Game.PROBLEM_UEBERWINDEN])
if not probleme.is_empty():
_akte_text("[color=%s]In Momenten[/color] %s" % [TINTE_HELL, " · ".join(probleme)], 15)
_akte_abschnitt("Tag")
_tagesablauf(human_id)
_zeigen("systeme", journal_panel.bind("missionen"))
_akte_abschnitt("Notizen")
var zeilen: Array = Game.notizen.get(human_id, [])
if zeilen.is_empty():
_akte_text("[color=%s]noch keine[/color]" % TINTE_HELL, 14)
for i in range(maxi(0, zeilen.size() - 8), zeilen.size()):
_akte_text("[color=%s]%s[/color]" % [TINTE, zeilen[i]], 14)
_zeigen("akte", journal_panel.bind("missionen"))
_scroll_wunsch = scroll_alt
_scroll_frames = 4
## Ein Blatt Papier im Drawer; alles Weitere der Akte landet darauf.
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
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)
func _akte_text(bb: String, groesse: int = 15) -> RichTextLabel:
var r := _text("[color=%s]%s[/color]" % [TINTE, bb], groesse, _blatt)
return r
func _akte_abschnitt(titel: String) -> void:
var r := _text("[color=%s]%s[/color]" % [TINTE_HELL, titel.to_upper()], 12, _blatt)
r.add_theme_constant_override("line_separation", 2)
var linie := HSeparator.new()
linie.add_theme_stylebox_override("separator", _sb(Color(0.78, 0.72, 0.6, 0.6), 0))
linie.custom_minimum_size.y = 2
_blatt.add_child(linie)
func _stempel(text: String) -> void:
var l := Label.new()
l.text = text
l.add_theme_font_size_override("font_size", 18)
l.add_theme_color_override("font_color", Color(0.66, 0.2, 0.15))
l.add_theme_stylebox_override("normal", _sb(Color(0, 0, 0, 0), 2, Color(0.66, 0.2, 0.15), 2))
l.rotation_degrees = -4.0
_blatt.add_child(l)
## Eine Zeile der Akte, die sich aufschlagen lässt (Stunden des Tages).
func _akte_karte(beschriftung: String, farbe: Color, cb: Callable, aktiv: bool = true) -> Button:
var b := Button.new()
b.text = beschriftung
b.alignment = HORIZONTAL_ALIGNMENT_LEFT
b.disabled = not aktiv
b.custom_minimum_size = Vector2(0, 30)
b.add_theme_font_size_override("font_size", 14)
b.add_theme_color_override("font_color", Color(0.17, 0.15, 0.13))
b.add_theme_color_override("font_hover_color", Color(0.1, 0.08, 0.06))
b.add_theme_color_override("font_disabled_color", Color(0.3, 0.27, 0.24))
b.add_theme_stylebox_override("normal", _sb(C_PAPIER_ZEILE, 3, farbe, 0, 4))
b.add_theme_stylebox_override("hover", _sb(C_PAPIER_ZEILE.darkened(0.06), 3, farbe, 0, 4))
b.add_theme_stylebox_override("pressed", _sb(C_PAPIER_ZEILE.darkened(0.1), 3, farbe, 0, 4))
b.add_theme_stylebox_override("disabled", _sb(C_PAPIER_ZEILE, 3, farbe.darkened(0.2), 0, 4))
_blatt.add_child(b)
if aktiv:
b.pressed.connect(cb)
return b
## Der Ablauf von 0 bis 23 Uhr. Jeder Tag ist gleich -- es gibt keine
## Wochentage. Zu Beginn ist jede Stunde leer; wer dabeisteht, füllt sie.
func _tagesablauf(human_id: String) -> void:
@@ -720,8 +835,8 @@ func _tagesablauf(human_id: String) -> void:
for stunde in range(24):
if not Game.gesehen_slot(human_id, Game.slot_bei_stunde(human_id, stunde)):
offen += 1
_text("[b]Ihr Tag[/b]%s" %
("" if offen == 0 else " [color=#8e93a8]%d Stunden leer[/color]" % offen), 17)
if offen > 0:
_akte_text("[color=%s]%d Stunden leer[/color]" % [TINTE_HELL, offen], 14)
# eine Zeile je Abschnitt des Tages -- auch wenn noch keiner bekannt ist,
# steht der Tag vollständig da
@@ -756,8 +871,8 @@ func _stunden_zeile(human_id: String, nr: int, von: int, bis: int, text: String)
var spanne := "%02d – %02d" % [von, bis + 1 if bis < 23 else 24]
var offen: bool = Game.gesehen_slot(human_id, nr) and Game.erkannt(human_id, nr) \
and Game.slot_grund(human_id, nr) != "schlaeft"
_karte("%s %s" % [spanne, text], _ablauf_farbe(human_id, nr),
func() -> void: slot_panel(human_id, nr, systeme_panel.bind(human_id)), offen)
_akte_karte("%s %s" % [spanne, text], _ablauf_farbe(human_id, nr),
func() -> void: slot_panel(human_id, nr, akte_panel.bind(human_id)), offen)
## Der offene Moment: hier zählt, welche Technik zu diesem Problem passt.
@@ -808,13 +923,13 @@ func slot_panel(human_id: String, nr: int, zurueck: Callable = Callable()) -> vo
_text("Sie %s.\n[b]Auslöser:[/b] %s\n[b]Belohnung:[/b] %s" %
[b["name"], b["ausloeser"], Ursachen.belohnung_text(str(b["belohnung"]))], 16)
else:
_text("Hier ist Platz. Ein neues System passt in diesen Slot.", 16)
_text("Hier ist Platz für eine neue Routine.", 16)
_trenner()
var sys := Game.system_in(human_id, nr)
if not sys.is_empty():
_text("[color=#88ff99]Hier steht: %s[/color]" %
TechDB.by_id(str(sys["tech"]))["name"], 16)
_knopf("System wieder herausnehmen", func() -> void:
_knopf("Routine wieder herausnehmen", func() -> void:
Game.system_entfernen(human_id, nr)
systeme_panel(human_id))
_trenner()
@@ -1043,7 +1158,7 @@ func _vorschau_aktualisieren() -> void:
var wert := int(bild["verhalten"][v])
if wert != 0:
zuege.append("%s %s" % [namen[v], "+" if wert > 0 else "−"])
_vorschau.text = "[color=#ff9a8a]%s[/color]\n[color=#ffcc66]%s[/color] · %d Blocker im Tag\n[color=#8e93a8]%s[/color]" % \
_vorschau.text = "[color=#ff9a8a]%s[/color]\n[color=#ffcc66]%s[/color] · %d Gewohnheiten im Tag\n[color=#8e93a8]%s[/color]" % \
[" · ".join(urs), " · ".join(prob), (bild["blocker"] as Array).size(),
" ".join(zuege) if not zuege.is_empty() else "unauffällig"]