1212 lines
41 KiB
GDScript
1212 lines
41 KiB
GDScript
extends CanvasLayer
|
||
class_name UI
|
||
## Die Oberfläche. Ein Baukasten (_text, _karte, _knopf) füllt immer denselben
|
||
## Inhalt (_content); drei Wirte zeigen ihn: die Seitenleiste rechts für das
|
||
## Journal, die Karte über der Zeitleiste für alles am Menschen, das Fenster in
|
||
## der Mitte für Orte. Die Zeitleiste unten ist der Tag des Menschen -- alles
|
||
## im Spiel hängt daran.
|
||
|
||
signal panel_opened
|
||
signal panel_closed
|
||
|
||
const BREITE := 900
|
||
const BREITE_FORMULAR := 1080
|
||
const DRAWER_BREITE := 470.0
|
||
const KARTE_BREITE := 780.0
|
||
const LEISTE_H := 116.0
|
||
|
||
const C_PANEL := Color(0.09, 0.09, 0.16, 0.97)
|
||
const C_ROW := Color(0.14, 0.15, 0.24)
|
||
const C_ROW_HOVER := Color(0.20, 0.22, 0.34)
|
||
const C_AKZENT := Color(0.50, 0.84, 1.0)
|
||
const C_TEXT := Color(0.92, 0.93, 0.97)
|
||
const C_MUTED := Color(0.56, 0.58, 0.68)
|
||
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_BLOCKER := Color(0.84, 0.42, 0.35)
|
||
const C_SYSTEM := Color(0.44, 0.78, 0.50)
|
||
|
||
var offen := false
|
||
var _kind := ""
|
||
|
||
var _root: Control
|
||
var _hud: RichTextLabel
|
||
var _hinweis: Label
|
||
var _log: VBoxContainer
|
||
# Zeitleiste
|
||
var _ablauf: PanelContainer
|
||
var _ablauf_titel: Label
|
||
var _ursachen: RichTextLabel
|
||
var _probleme: RichTextLabel
|
||
var _zellen: Array = []
|
||
var _ticks: Array = []
|
||
var _ablauf_jetzt: RichTextLabel
|
||
# Wirte und Inhalt
|
||
var _drawer: PanelContainer
|
||
var _karte_host: PanelContainer
|
||
var _modal: PanelContainer
|
||
var _dim: ColorRect
|
||
var _box: VBoxContainer
|
||
var _titel: Label
|
||
var _scroll: ScrollContainer
|
||
var _content: VBoxContainer
|
||
var _ziel: Container = null # wohin die Helfer gerade bauen
|
||
var _verlauf: Array = [] # wohin Esc zurückführt
|
||
var _apply_human: Node3D
|
||
var _journal_seite := "techniken"
|
||
var _scroll_wunsch := 0
|
||
var _scroll_frames := 0
|
||
|
||
|
||
func _ready() -> void:
|
||
layer = 10
|
||
_root = Control.new()
|
||
_root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
_root.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_root.theme = _theme()
|
||
add_child(_root)
|
||
_baue_hud()
|
||
_baue_leiste()
|
||
_baue_wirte()
|
||
Game.notified.connect(_notiz)
|
||
Game.state_changed.connect(_hud_aktualisieren)
|
||
_hud_aktualisieren()
|
||
|
||
|
||
# ------------------------------------------------------------------ Theme
|
||
|
||
func _sb(bg: Color, radius: int = 6, rand: Color = Color(0, 0, 0, 0), rand_breite: int = 0,
|
||
links: int = 0) -> StyleBoxFlat:
|
||
var s := StyleBoxFlat.new()
|
||
s.bg_color = bg
|
||
s.set_corner_radius_all(radius)
|
||
s.border_color = rand
|
||
s.set_border_width_all(rand_breite)
|
||
if links > 0:
|
||
s.border_width_left = links
|
||
s.content_margin_left = 12
|
||
s.content_margin_right = 12
|
||
s.content_margin_top = 6
|
||
s.content_margin_bottom = 6
|
||
return s
|
||
|
||
|
||
## Dunkel, flach, eine Akzentfarbe. Einmal gesetzt, gilt es überall darunter.
|
||
func _theme() -> Theme:
|
||
var th := Theme.new()
|
||
th.set_stylebox("normal", "Button", _sb(C_ROW))
|
||
th.set_stylebox("hover", "Button", _sb(C_ROW_HOVER))
|
||
th.set_stylebox("pressed", "Button", _sb(C_ROW_HOVER.lightened(0.1)))
|
||
th.set_stylebox("disabled", "Button", _sb(C_ROW.darkened(0.25)))
|
||
th.set_stylebox("focus", "Button", StyleBoxEmpty.new())
|
||
th.set_color("font_color", "Button", C_TEXT)
|
||
th.set_color("font_hover_color", "Button", Color.WHITE)
|
||
th.set_color("font_disabled_color", "Button", C_MUTED)
|
||
for typ in ["LineEdit", "TextEdit", "SpinBox", "OptionButton"]:
|
||
th.set_stylebox("normal", typ, _sb(Color(0.06, 0.06, 0.11), 5, C_MUTED, 1))
|
||
th.set_stylebox("focus", typ, _sb(Color(0.06, 0.06, 0.11), 5, C_AKZENT, 1))
|
||
th.set_stylebox("hover", "OptionButton", _sb(C_ROW_HOVER, 5, C_MUTED, 1))
|
||
th.set_color("font_color", "LineEdit", C_TEXT)
|
||
th.set_color("font_color", "TextEdit", C_TEXT)
|
||
th.set_color("font_color", "CheckBox", C_TEXT)
|
||
th.set_color("font_color", "Label", C_TEXT)
|
||
th.set_color("default_color", "RichTextLabel", C_TEXT)
|
||
th.set_stylebox("panel", "PanelContainer", _sb(C_PANEL, 10, C_AKZENT * Color(1, 1, 1, 0.45), 1))
|
||
th.set_stylebox("panel", "ScrollContainer", StyleBoxEmpty.new())
|
||
return th
|
||
|
||
|
||
# ------------------------------------------------------------------ HUD
|
||
|
||
func _baue_hud() -> void:
|
||
_hud = RichTextLabel.new()
|
||
_hud.bbcode_enabled = true
|
||
_hud.fit_content = true
|
||
_hud.scroll_active = false
|
||
_hud.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||
_hud.position = Vector2(18, 14)
|
||
_hud.custom_minimum_size = Vector2(420, 50)
|
||
_hud.size = Vector2(420, 50)
|
||
_hud.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_hud.add_theme_font_size_override("normal_font_size", 17)
|
||
_hud.add_theme_font_size_override("bold_font_size", 17)
|
||
_root.add_child(_hud)
|
||
|
||
var steuerung := Label.new()
|
||
steuerung.text = "J Journal · 1-3 Tempo · Esc"
|
||
steuerung.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||
steuerung.position = Vector2(-260, 16)
|
||
steuerung.size = Vector2(240, 24)
|
||
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))
|
||
steuerung.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_root.add_child(steuerung)
|
||
|
||
_hinweis = Label.new()
|
||
_hinweis.set_anchors_preset(Control.PRESET_CENTER_BOTTOM)
|
||
_hinweis.position = Vector2(-320, -(LEISTE_H + 44))
|
||
_hinweis.size = Vector2(640, 30)
|
||
_hinweis.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
_hinweis.add_theme_font_size_override("font_size", 19)
|
||
_hinweis.add_theme_color_override("font_color", Color(1, 0.95, 0.75))
|
||
_hinweis.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.9))
|
||
_hinweis.add_theme_constant_override("outline_size", 6)
|
||
_hinweis.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_root.add_child(_hinweis)
|
||
|
||
_log = VBoxContainer.new()
|
||
_log.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||
_log.position = Vector2(-520, -(LEISTE_H + 110))
|
||
_log.size = Vector2(500, 90)
|
||
_log.alignment = BoxContainer.ALIGNMENT_END
|
||
_log.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_root.add_child(_log)
|
||
|
||
|
||
func _hud_aktualisieren() -> void:
|
||
var kopf := "[b]%s[/b]" % Game.time_string()
|
||
if Game.schlaeft_ziel():
|
||
kopf += " [color=#9fb6ff]Tempo 3 (%d×) Nacht[/color]" % int(Game.zeit_faktor())
|
||
elif Game.tempo != 1:
|
||
kopf += " [color=#9fb6ff]Tempo %d (%d×)[/color]" % [Game.tempo, int(Game.zeit_faktor())]
|
||
if Game.active_mission != "":
|
||
var h := HumanDB.by_id(Game.active_mission)
|
||
kopf += "\n[color=#ffdd77]%s[/color] %d %% [color=#8e93a8]+%d %%/Tag[/color]" % \
|
||
[h["name"], int(Game.mission_progress(Game.active_mission)),
|
||
int(round(Game.traum_tempo(Game.active_mission)))]
|
||
elif Game.completed.size() >= HumanDB.LIST.size():
|
||
kopf += "\n[color=#88ff99]Alle Träume erfüllt[/color]"
|
||
else:
|
||
kopf += "\n[color=#8e93a8]Keine Mission — Schwarzes Brett[/color]"
|
||
_hud.text = kopf
|
||
_ablauf_aktualisieren()
|
||
|
||
|
||
func setze_hinweis(text: String) -> void:
|
||
_hinweis.text = text
|
||
|
||
|
||
func _notiz(text: String, farbe: Color) -> void:
|
||
var l := Label.new()
|
||
l.text = text
|
||
l.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||
l.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
l.custom_minimum_size.x = 500
|
||
l.add_theme_font_size_override("font_size", 14)
|
||
l.add_theme_color_override("font_color", farbe)
|
||
l.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.9))
|
||
l.add_theme_constant_override("outline_size", 5)
|
||
_log.add_child(l)
|
||
while _log.get_child_count() > 3:
|
||
var alt := _log.get_child(0)
|
||
_log.remove_child(alt)
|
||
alt.queue_free()
|
||
var tw := l.create_tween()
|
||
tw.tween_interval(6.0)
|
||
tw.tween_property(l, "modulate:a", 0.0, 1.5)
|
||
tw.tween_callback(l.queue_free)
|
||
|
||
|
||
# ------------------------------------------------------------ Zeitleiste
|
||
# Der Tag des Menschen, unten über die ganze Breite. 24 Felder, die laufende
|
||
# Stunde steht heraus, links die Ursachen, rechts die Momente. Ein Klick auf
|
||
# eine Stunde öffnet ihre Karte darüber.
|
||
|
||
func _baue_leiste() -> void:
|
||
_ablauf = PanelContainer.new()
|
||
_ablauf.set_anchors_preset(Control.PRESET_BOTTOM_WIDE)
|
||
_ablauf.offset_left = 12
|
||
_ablauf.offset_right = -12
|
||
_ablauf.offset_top = -LEISTE_H
|
||
_ablauf.offset_bottom = -10
|
||
_ablauf.add_theme_stylebox_override("panel", _sb(Color(0.06, 0.06, 0.11, 0.92), 10,
|
||
C_AKZENT * Color(1, 1, 1, 0.3), 1))
|
||
_root.add_child(_ablauf)
|
||
|
||
var vb := VBoxContainer.new()
|
||
vb.add_theme_constant_override("separation", 4)
|
||
_ablauf.add_child(vb)
|
||
|
||
var kopf := HBoxContainer.new()
|
||
kopf.add_theme_constant_override("separation", 16)
|
||
vb.add_child(kopf)
|
||
_ablauf_titel = Label.new()
|
||
_ablauf_titel.add_theme_font_size_override("font_size", 13)
|
||
_ablauf_titel.add_theme_color_override("font_color", C_MUTED)
|
||
kopf.add_child(_ablauf_titel)
|
||
_ursachen = _hud_zeile(14)
|
||
_ursachen.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
kopf.add_child(_ursachen)
|
||
_probleme = _hud_zeile(14)
|
||
_probleme.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
kopf.add_child(_probleme)
|
||
|
||
var reihe := HBoxContainer.new()
|
||
reihe.add_theme_constant_override("separation", 3)
|
||
vb.add_child(reihe)
|
||
for stunde in range(24):
|
||
var zelle := ColorRect.new()
|
||
zelle.custom_minimum_size = Vector2(10, 16)
|
||
zelle.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
zelle.size_flags_vertical = Control.SIZE_SHRINK_END
|
||
zelle.mouse_filter = Control.MOUSE_FILTER_STOP
|
||
zelle.gui_input.connect(func(ev: InputEvent) -> void:
|
||
if ev is InputEventMouseButton and ev.pressed and ev.button_index == MOUSE_BUTTON_LEFT:
|
||
_stunde_klick(stunde))
|
||
reihe.add_child(zelle)
|
||
_zellen.append(zelle)
|
||
reihe.custom_minimum_size.y = 24
|
||
|
||
var ticks := HBoxContainer.new()
|
||
ticks.add_theme_constant_override("separation", 3)
|
||
vb.add_child(ticks)
|
||
for stunde in range(24):
|
||
var t := Label.new()
|
||
t.text = "%d" % stunde if stunde % 3 == 0 else ""
|
||
t.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
t.add_theme_font_size_override("font_size", 10)
|
||
t.add_theme_color_override("font_color", Color(1, 1, 1, 0.35))
|
||
ticks.add_child(t)
|
||
_ticks.append(t)
|
||
|
||
_ablauf_jetzt = _hud_zeile(15)
|
||
vb.add_child(_ablauf_jetzt)
|
||
|
||
|
||
func _hud_zeile(groesse: int) -> RichTextLabel:
|
||
var r := RichTextLabel.new()
|
||
r.bbcode_enabled = true
|
||
r.fit_content = true
|
||
r.scroll_active = false
|
||
r.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
r.add_theme_font_size_override("normal_font_size", groesse)
|
||
r.add_theme_font_size_override("bold_font_size", groesse)
|
||
return r
|
||
|
||
|
||
func _ablauf_farbe(human_id: String, nr: int) -> Color:
|
||
match Game.slot_zustand(human_id, nr):
|
||
Game.Stunde.UNBEKANNT:
|
||
return C_UNBEKANNT
|
||
Game.Stunde.GEBAUT:
|
||
return C_SYSTEM
|
||
Game.Stunde.UNGEKLAERT:
|
||
return C_WARN
|
||
Game.Stunde.BENANNT:
|
||
return C_BLOCKER
|
||
Game.Stunde.SCHLAF:
|
||
return C_SCHLAF
|
||
return C_GESEHEN
|
||
|
||
|
||
func _ablauf_aktualisieren() -> void:
|
||
var id := Game.active_mission
|
||
var an := id != ""
|
||
_ablauf.visible = an
|
||
if not an:
|
||
return
|
||
var jetzt := int(Game.time_h)
|
||
var offen_std := 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
|
||
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)
|
||
|
||
var urs: Array = []
|
||
for u in Game.ursachen_von(id):
|
||
var name := str(Ursachen.LISTE[u]["name"])
|
||
if Game.ursache_geloest(id, str(u)) > 0.0:
|
||
urs.append("[color=#7bea8f]%s[/color]" % name)
|
||
elif Game.ursache_bekannt(id, str(u)):
|
||
urs.append("[color=#ff9a8a]%s[/color]" % name)
|
||
else:
|
||
urs.append("[color=#55596b]?[/color]")
|
||
_ursachen.text = "[center]" + " · ".join(urs) + "[/center]"
|
||
|
||
var zeilen: Array = []
|
||
if Game.situation_bei(id):
|
||
var s: Dictionary = Probleme.SITUATIONEN[Game.situation["key"]]
|
||
zeilen.append("[right][color=#ffcc66][b]Jetzt: %s[/b][/color] %s [b]%.0f s[/b][/right]" %
|
||
[s["name"], s["was"], float(Game.situation["rest"])])
|
||
else:
|
||
var chips: Array = []
|
||
for pr in Game.probleme_von(id):
|
||
var key := str(pr)
|
||
if not Game.problem_bekannt(id, key):
|
||
chips.append("[color=#55596b]?[/color]")
|
||
elif Game.problem_ueberwunden(id, key):
|
||
chips.append("[color=#7bea8f]%s ✓[/color]" % Probleme.name_von(key))
|
||
else:
|
||
chips.append("[color=#ffb0a0]%s %d/%d[/color]" % [Probleme.name_von(key),
|
||
Game.problem_fortschritt(id, key), Game.PROBLEM_UEBERWINDEN])
|
||
zeilen.append("[right]" + " ".join(chips) + "[/right]")
|
||
_probleme.text = "\n".join(zeilen)
|
||
_ablauf_jetzt.text = "%02d Uhr [color=#cfd6ff]%s[/color]" % \
|
||
[jetzt, _stunden_text(id, Game.slot_bei_stunde(id, jetzt))]
|
||
|
||
|
||
## Klick auf eine Stunde: ihre Karte über der Leiste.
|
||
func _stunde_klick(stunde: int) -> void:
|
||
var id := Game.active_mission
|
||
if id == "":
|
||
return
|
||
slot_panel(id, Game.slot_bei_stunde(id, stunde))
|
||
|
||
|
||
# ------------------------------------------------------------------ Wirte
|
||
|
||
func _baue_wirte() -> void:
|
||
_dim = ColorRect.new()
|
||
_dim.color = Color(0, 0, 0, 0.55)
|
||
_dim.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||
_dim.visible = false
|
||
_dim.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||
_root.add_child(_dim)
|
||
|
||
_box = VBoxContainer.new()
|
||
_box.add_theme_constant_override("separation", 10)
|
||
_titel = Label.new()
|
||
_titel.add_theme_font_size_override("font_size", 24)
|
||
_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
|
||
_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
_box.add_child(_scroll)
|
||
_content = VBoxContainer.new()
|
||
_content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_content.add_theme_constant_override("separation", 8)
|
||
_scroll.add_child(_content)
|
||
|
||
_drawer = PanelContainer.new()
|
||
_drawer.set_anchors_preset(Control.PRESET_RIGHT_WIDE)
|
||
_drawer.offset_left = -DRAWER_BREITE - 12
|
||
_drawer.offset_right = -12
|
||
_drawer.offset_top = 56
|
||
_drawer.offset_bottom = -(LEISTE_H + 22)
|
||
_drawer.visible = false
|
||
_root.add_child(_drawer)
|
||
|
||
_karte_host = PanelContainer.new()
|
||
_karte_host.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||
_karte_host.custom_minimum_size.x = KARTE_BREITE
|
||
_karte_host.visible = false
|
||
_root.add_child(_karte_host)
|
||
|
||
_modal = PanelContainer.new()
|
||
_modal.set_anchors_preset(Control.PRESET_CENTER)
|
||
_modal.visible = false
|
||
_root.add_child(_modal)
|
||
|
||
|
||
func _wirt_fuer(kind: String) -> PanelContainer:
|
||
match kind:
|
||
"journal", "technik", "systeme":
|
||
return _drawer
|
||
"slot", "situation", "anwenden":
|
||
return _karte_host
|
||
return _modal
|
||
|
||
|
||
func _process(_delta: float) -> void:
|
||
if not offen:
|
||
_hud_aktualisieren()
|
||
return
|
||
var wirt := _wirt_fuer(_kind)
|
||
var wunsch := _content.get_combined_minimum_size().y
|
||
var vp := get_viewport().get_visible_rect().size
|
||
if wirt == _drawer:
|
||
_scroll.custom_minimum_size.y = maxf(120.0, _drawer.size.y - 90.0)
|
||
else:
|
||
var platz: float = vp.y - LEISTE_H - 120.0 if wirt == _karte_host else vp.y - 200.0
|
||
_scroll.custom_minimum_size.y = clampf(wunsch, 60.0, platz)
|
||
if _scroll_frames > 0:
|
||
_scroll.scroll_vertical = _scroll_wunsch
|
||
_scroll_frames -= 1
|
||
if wirt == _modal:
|
||
var breite: float = BREITE_FORMULAR if _kind == "schreiben" else BREITE
|
||
_modal.custom_minimum_size.x = breite
|
||
_scroll.custom_minimum_size.x = breite - 48
|
||
_modal.reset_size()
|
||
_modal.position = (vp - _modal.size) * 0.5
|
||
elif wirt == _karte_host:
|
||
_scroll.custom_minimum_size.x = KARTE_BREITE - 48
|
||
_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()
|
||
|
||
|
||
# ------------------------------------------------------------------ Baukasten
|
||
|
||
func _wo() -> Container:
|
||
return _ziel if _ziel != null else _content
|
||
|
||
|
||
func _leeren(titel: String) -> void:
|
||
_ziel = null
|
||
for c in _content.get_children():
|
||
_content.remove_child(c)
|
||
c.queue_free()
|
||
_titel.text = titel
|
||
|
||
|
||
func _text(bb: String, groesse: int = 17, in_: Container = null) -> RichTextLabel:
|
||
var r := RichTextLabel.new()
|
||
r.bbcode_enabled = true
|
||
r.fit_content = true
|
||
r.scroll_active = false
|
||
r.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
r.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
r.text = bb
|
||
r.add_theme_font_size_override("normal_font_size", groesse)
|
||
r.add_theme_font_size_override("bold_font_size", groesse)
|
||
(in_ if in_ != null else _wo()).add_child(r)
|
||
return r
|
||
|
||
|
||
## Eine Aktion: farbig, mittig. Nur für Dinge, die etwas tun.
|
||
func _knopf(beschriftung: String, cb: Callable, aktiv: bool = true, in_: Container = null) -> Button:
|
||
var b := Button.new()
|
||
b.text = beschriftung
|
||
b.disabled = not aktiv
|
||
b.custom_minimum_size = Vector2(0, 34)
|
||
b.add_theme_font_size_override("font_size", 16)
|
||
b.add_theme_stylebox_override("normal", _sb(C_AKZENT.darkened(0.62), 6, C_AKZENT, 1))
|
||
b.add_theme_stylebox_override("hover", _sb(C_AKZENT.darkened(0.48), 6, C_AKZENT, 1))
|
||
b.add_theme_stylebox_override("pressed", _sb(C_AKZENT.darkened(0.35), 6, C_AKZENT, 1))
|
||
(in_ if in_ != null else _wo()).add_child(b)
|
||
if aktiv:
|
||
b.pressed.connect(cb)
|
||
return b
|
||
|
||
|
||
## Eine Datenzeile: linksbündig, Farbkante für den Zustand. Klick öffnet Details.
|
||
func _karte(beschriftung: String, farbe: Color, cb: Callable, aktiv: bool = true,
|
||
in_: Container = null) -> Button:
|
||
var b := Button.new()
|
||
b.text = beschriftung
|
||
b.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||
b.disabled = not aktiv
|
||
b.custom_minimum_size = Vector2(0, 34)
|
||
b.add_theme_font_size_override("font_size", 15)
|
||
b.add_theme_stylebox_override("normal", _sb(C_ROW, 6, farbe, 0, 4))
|
||
b.add_theme_stylebox_override("hover", _sb(C_ROW_HOVER, 6, farbe, 0, 4))
|
||
b.add_theme_stylebox_override("pressed", _sb(C_ROW_HOVER.lightened(0.1), 6, farbe, 0, 4))
|
||
b.add_theme_stylebox_override("disabled", _sb(C_ROW.darkened(0.25), 6, farbe.darkened(0.45), 0, 4))
|
||
(in_ if in_ != null else _wo()).add_child(b)
|
||
if aktiv:
|
||
b.pressed.connect(cb)
|
||
return b
|
||
|
||
|
||
## Leise Aktion: zurück, abbrechen.
|
||
func _knopf_leise(beschriftung: String, cb: Callable, in_: Container = null) -> Button:
|
||
var b := Button.new()
|
||
b.text = beschriftung
|
||
b.custom_minimum_size = Vector2(0, 30)
|
||
b.add_theme_font_size_override("font_size", 14)
|
||
b.add_theme_color_override("font_color", C_MUTED)
|
||
(in_ if in_ != null else _wo()).add_child(b)
|
||
b.pressed.connect(cb)
|
||
return b
|
||
|
||
|
||
func _trenner() -> void:
|
||
var s := HSeparator.new()
|
||
_wo().add_child(s)
|
||
|
||
|
||
## Zeigt den gebauten Inhalt im passenden Wirt. `zurueck` ist der Weg, den Esc
|
||
## nimmt -- nur setzen, wenn das Panel aus einem anderen heraus geöffnet wurde.
|
||
func _zeigen(kind: String, zurueck: Callable = Callable()) -> void:
|
||
_kind = kind
|
||
_ziel = null
|
||
var wirt := _wirt_fuer(kind)
|
||
if _box.get_parent() != wirt:
|
||
if _box.get_parent() != null:
|
||
_box.get_parent().remove_child(_box)
|
||
wirt.add_child(_box)
|
||
_drawer.visible = wirt == _drawer
|
||
_karte_host.visible = wirt == _karte_host
|
||
_modal.visible = wirt == _modal
|
||
_dim.visible = wirt == _modal
|
||
if zurueck.is_valid():
|
||
_verlauf.append(zurueck)
|
||
if wirt == _modal:
|
||
_modal.reset_size()
|
||
if not offen:
|
||
offen = true
|
||
panel_opened.emit()
|
||
|
||
|
||
## Esc: eine Ebene zurück, sonst zu.
|
||
func _zurueck() -> void:
|
||
if _verlauf.is_empty():
|
||
schliessen()
|
||
return
|
||
var cb: Callable = _verlauf.pop_back()
|
||
cb.call()
|
||
|
||
|
||
func schliessen() -> void:
|
||
if not offen:
|
||
return
|
||
offen = false
|
||
_kind = ""
|
||
_ziel = null
|
||
_verlauf.clear()
|
||
_apply_human = null
|
||
_drawer.visible = false
|
||
_karte_host.visible = false
|
||
_modal.visible = false
|
||
_dim.visible = false
|
||
panel_closed.emit()
|
||
|
||
|
||
func _hinweis_block(t: Dictionary) -> String:
|
||
return ("[b]Was:[/b] %s\n[b]Wann:[/b] %s\n[b]Warum:[/b] %s\n" +
|
||
"[color=#9fb6ff][b]Zu sehen an:[/b] %s[/color]") % \
|
||
[t["was"], t["wann"], t["warum"], t["woran"]]
|
||
|
||
|
||
# -------------------------------------------------------- Journal
|
||
|
||
## Das Journal ist immer dabei und führt Buch: welche Techniken du gelernt hast
|
||
## und welche Missionen du angenommen hast. Gelehrt wird im Hörsaal,
|
||
## vergeben werden die Missionen am Schwarzen Brett.
|
||
func journal_panel(seite: String = "") -> void:
|
||
if seite != "":
|
||
_journal_seite = seite
|
||
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "journal" else 0
|
||
_leeren("Journal")
|
||
_reiter()
|
||
if _journal_seite == "missionen":
|
||
_journal_missionen()
|
||
else:
|
||
_journal_techniken()
|
||
_zeigen("journal")
|
||
_scroll_wunsch = scroll_alt
|
||
_scroll_frames = 4
|
||
|
||
|
||
func _reiter() -> void:
|
||
var hb := HBoxContainer.new()
|
||
hb.add_theme_constant_override("separation", 8)
|
||
_content.add_child(hb)
|
||
var seiten := [
|
||
["techniken", "Techniken (%d)" % Game.known.size()],
|
||
["missionen", "Missionen (%d)" % Game.progress.size()],
|
||
]
|
||
for eintrag in seiten:
|
||
var b := Button.new()
|
||
b.text = eintrag[1]
|
||
b.custom_minimum_size = Vector2(0, 36)
|
||
b.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
b.add_theme_font_size_override("font_size", 16)
|
||
b.disabled = _journal_seite == eintrag[0]
|
||
if b.disabled:
|
||
b.add_theme_color_override("font_disabled_color", Color(1, 0.88, 0.55))
|
||
hb.add_child(b)
|
||
if not b.disabled:
|
||
b.pressed.connect(func() -> void: journal_panel(eintrag[0]))
|
||
_trenner()
|
||
|
||
|
||
func _journal_techniken() -> void:
|
||
if Game.known.is_empty():
|
||
_text("[color=#ffcc66]Noch nichts gelernt — Hörsaal der Wege.[/color]", 16)
|
||
return
|
||
for id in TechDB.ids():
|
||
if not Game.knows(id):
|
||
continue
|
||
var t := TechDB.by_id(id)
|
||
_karte("%s · Stufe %d · %s" % [t["name"], Game.skill_of(id),
|
||
Ursachen.LISTE[t["ursache"]]["name"]], t["farbe"],
|
||
func() -> void: technik_panel(id))
|
||
|
||
|
||
## Alles zu einer Technik -- nur, wenn man sie aufschlägt.
|
||
func technik_panel(tech_id: String) -> void:
|
||
var t := TechDB.by_id(tech_id)
|
||
_leeren(str(t["name"]))
|
||
_text("[color=#aaaaaa]Stufe %d (%s) · gegen [b]%s[/b][/color]" %
|
||
[Game.skill_of(tech_id), Game.xp_to_next(tech_id),
|
||
Ursachen.LISTE[t["ursache"]]["name"]], 15)
|
||
_trenner()
|
||
_text(_hinweis_block(t), 15)
|
||
_text("[color=#9fb6ff]Zu sehen an:[/color] %s" % t["woran"], 15)
|
||
var gegen: Array = []
|
||
for tag in t["tags"]:
|
||
if Probleme.LISTE.has(str(tag)):
|
||
gegen.append(Probleme.name_von(str(tag)))
|
||
if not gegen.is_empty():
|
||
_text("[color=#9fb6ff]Im Moment gegen:[/color] %s" % ", ".join(gegen), 15)
|
||
_zeigen("technik", journal_panel.bind("techniken"))
|
||
|
||
|
||
func _journal_missionen() -> void:
|
||
if Game.progress.is_empty():
|
||
_text("[color=#ffcc66]Keine Mission — Schwarzes Brett am Dorfplatz.[/color]", 16)
|
||
return
|
||
for h in HumanDB.LIST:
|
||
var id: String = h["id"]
|
||
if not Game.angenommen(id):
|
||
continue
|
||
var stand := "%d %%" % int(Game.mission_progress(id))
|
||
var farbe := C_GESEHEN
|
||
if Game.completed.has(id):
|
||
stand = "★ erfüllt"
|
||
farbe = C_SYSTEM
|
||
elif Game.active_mission == id:
|
||
stand = "aktiv · %d %%" % int(Game.mission_progress(id))
|
||
farbe = C_AKZENT
|
||
_karte("%s · %s" % [h["name"], stand], farbe,
|
||
func() -> void: systeme_panel(id))
|
||
|
||
|
||
# ---------------------------------------------- Diagnose und Systeme
|
||
|
||
## 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()
|
||
|
||
_text("[b]Woran es scheitert[/b]", 17)
|
||
var offene := 0
|
||
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
|
||
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()
|
||
|
||
_tagesablauf(human_id)
|
||
_zeigen("systeme", journal_panel.bind("missionen"))
|
||
_scroll_wunsch = scroll_alt
|
||
_scroll_frames = 4
|
||
|
||
|
||
## 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:
|
||
var offen := 0
|
||
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)
|
||
|
||
# eine Zeile je Abschnitt des Tages -- auch wenn noch keiner bekannt ist,
|
||
# steht der Tag vollständig da
|
||
var von := 0
|
||
while von < 24:
|
||
var nr := Game.slot_bei_stunde(human_id, von)
|
||
var bis := von
|
||
while bis + 1 < 24 and Game.slot_bei_stunde(human_id, bis + 1) == nr:
|
||
bis += 1
|
||
_stunden_zeile(human_id, nr, von, bis, _stunden_text(human_id, nr))
|
||
von = bis + 1
|
||
|
||
|
||
func _stunden_text(human_id: String, nr: int) -> String:
|
||
var grund := Game.slot_grund(human_id, nr)
|
||
match Game.slot_zustand(human_id, nr):
|
||
Game.Stunde.UNBEKANNT:
|
||
return "—"
|
||
Game.Stunde.GEBAUT:
|
||
var sys := Game.system_in(human_id, nr)
|
||
return "%s ← %s" % [Game.slot_aktivitaet(human_id, nr),
|
||
TechDB.by_id(str(sys["tech"]))["name"]]
|
||
Game.Stunde.UNGEKLAERT:
|
||
return "?"
|
||
Game.Stunde.BENANNT:
|
||
return "%s (%s)" % [Ursachen.BLOCKER[grund]["name"],
|
||
Ursachen.LISTE[Ursachen.BLOCKER[grund]["ursache"]]["name"]]
|
||
return Orte.text(grund)
|
||
|
||
|
||
func _stunden_zeile(human_id: String, nr: int, von: int, bis: int, text: String) -> void:
|
||
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)
|
||
|
||
|
||
## Der offene Moment: hier zählt, welche Technik zu diesem Problem passt.
|
||
func situation_panel(mensch: Node3D) -> void:
|
||
_apply_human = mensch
|
||
var id: String = str(mensch.data["id"])
|
||
if not Game.situation_bei(id):
|
||
anwenden_panel(mensch)
|
||
return
|
||
var s: Dictionary = Probleme.SITUATIONEN[Game.situation["key"]]
|
||
var problem := Game.situation_problem()
|
||
_leeren("%s — %s" % [mensch.data["name"], s["name"]])
|
||
var kopf := "[i]%s[/i]" % s["was"]
|
||
if Game.problem_bekannt(id, problem):
|
||
kopf += " [color=#ffb0a0]%s %d/%d[/color]" % [Probleme.name_von(problem),
|
||
Game.problem_fortschritt(id, problem), Game.PROBLEM_UEBERWINDEN]
|
||
else:
|
||
kopf += " [color=#ffcc66]?[/color]"
|
||
kopf += " [b]%.0f s[/b]" % float(Game.situation["rest"])
|
||
_text(kopf, 16)
|
||
_trenner()
|
||
if Game.known.is_empty():
|
||
_text("[color=#ffcc66]Du kennst keine Technik.[/color]", 16)
|
||
_zeigen("situation")
|
||
return
|
||
for tech_id in TechDB.ids():
|
||
if not Game.knows(tech_id):
|
||
continue
|
||
var t := TechDB.by_id(tech_id)
|
||
var frei := Game.bereit(tech_id)
|
||
var text := "%s · Stufe %d" % [t["name"], Game.skill_of(tech_id)]
|
||
if not frei:
|
||
text += " · %.0f s" % Game.cooldown_rest(tech_id)
|
||
_knopf(text, func() -> void:
|
||
Game.situation_anwenden(tech_id)
|
||
schliessen(), frei)
|
||
_zeigen("situation")
|
||
|
||
|
||
## Was soll in diesem Slot stehen?
|
||
func slot_panel(human_id: String, nr: int, zurueck: Callable = Callable()) -> void:
|
||
var h := HumanDB.by_id(human_id)
|
||
var grund := Game.slot_grund(human_id, nr)
|
||
var stunde := "%02d:00" % int(Game.slots(human_id)[nr][0])
|
||
_leeren("%s um %s" % [h["name"], stunde])
|
||
if Ursachen.ist_blocker(grund):
|
||
var b: Dictionary = Ursachen.BLOCKER[grund]
|
||
_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)
|
||
_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:
|
||
Game.system_entfernen(human_id, nr)
|
||
systeme_panel(human_id))
|
||
_trenner()
|
||
for id in TechDB.ids():
|
||
if not Game.knows(id):
|
||
continue
|
||
var pruef := Game.system_pruefen(human_id, nr, id)
|
||
var t := TechDB.by_id(id)
|
||
var beschriftung := "%s · Stufe %d" % [t["name"], Game.skill_of(id)]
|
||
if not pruef["geht"]:
|
||
beschriftung += " — " + str(pruef["grund"])
|
||
elif float(pruef["guete"]) > Game.GUETE_NEU:
|
||
beschriftung += " — passt genau"
|
||
_knopf(beschriftung, func() -> void: _system_setzen(human_id, nr, id),
|
||
bool(pruef["geht"]))
|
||
_trenner()
|
||
_zeigen("slot", zurueck)
|
||
|
||
|
||
func _system_setzen(human_id: String, nr: int, tech_id: String) -> void:
|
||
var res := Game.system_setzen(human_id, nr, tech_id, false)
|
||
if not res["ok"]:
|
||
Game.notified.emit(str(res["grund"]), Color(1, 0.6, 0.4))
|
||
return
|
||
if not res["erfolg"]:
|
||
Game.notified.emit("%s greift diesmal nicht. Später noch einmal." %
|
||
TechDB.by_id(tech_id)["name"], Color(1, 0.6, 0.5))
|
||
systeme_panel(human_id)
|
||
|
||
|
||
# -------------------------------------------------------- Lehrorte
|
||
|
||
## Jeder Lehrort unterrichtet genau einen Weg: der Hörsaal den ersten, jedes
|
||
## Haus den, den sein Bewohner mitgebracht hat.
|
||
func lehre_panel(ort: String = "hoersaal") -> void:
|
||
if not DreamDB.PLACES.has(ort):
|
||
return
|
||
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "lehre" else 0
|
||
var pl: Dictionary = DreamDB.PLACES[ort]
|
||
var ids: Array = DreamDB.techniken_von(ort)
|
||
_leeren(str(pl["name"]))
|
||
_text("[color=#aaaaaa]Gelernt: [b]%d[/b] von %d entdeckten Wegen · %d im Spiel[/color]" %
|
||
[Game.known.size(), Game.offene_techniken(), TechDB.LIST.size()], 15)
|
||
_trenner()
|
||
if ids.is_empty():
|
||
_text("[color=#ffcc66]Hier wird nichts gelehrt.[/color]", 16)
|
||
for id in ids:
|
||
var t := TechDB.by_id(id)
|
||
if Game.knows(id):
|
||
_text("[b][color=#ffe3a0]%s[/color][/b] · Stufe %d (%s)" %
|
||
[t["name"], Game.skill_of(id), Game.xp_to_next(id)], 18)
|
||
else:
|
||
_text("[b][color=#9aa0b8]%s[/color][/b] · noch nicht gelernt" % t["name"], 18)
|
||
_text(_hinweis_block(t), 15)
|
||
if not Game.knows(id):
|
||
_knopf("Diesen Weg lernen", func() -> void:
|
||
if Game.learn(id):
|
||
lehre_panel(ort))
|
||
_trenner()
|
||
_naechster_weg()
|
||
_zeigen("lehre")
|
||
_scroll_wunsch = scroll_alt
|
||
_scroll_frames = 4
|
||
|
||
|
||
## Was als Nächstes ins Dorf käme.
|
||
func _naechster_weg() -> void:
|
||
var offen_zahl := Game.offene_techniken()
|
||
if offen_zahl >= TechDB.LIST.size():
|
||
_text("[color=#88ff99]Alle zwölf Wege sind entdeckt. Das Dorf wächst weiter.[/color]", 15)
|
||
return
|
||
var naechste: Dictionary = TechDB.LIST[offen_zahl]
|
||
_text("[color=#8e93a8]Noch unentdeckt: %s +%d[/color]" %
|
||
[naechste["name"], TechDB.LIST.size() - offen_zahl - 1], 14)
|
||
|
||
|
||
# -------------------------------------------------------- Schwarzes Brett
|
||
|
||
## Hier hängen die Menschen aus, deren Traum feststeckt.
|
||
func brett_panel() -> void:
|
||
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "brett" else 0
|
||
_leeren("Schwarzes Brett")
|
||
_knopf("Aushang schreiben", func() -> void: schreiben_panel())
|
||
_trenner()
|
||
var offen_ids := Game.offene_missionen()
|
||
var gezeigt := 0
|
||
var verdeckt := 0
|
||
for h in HumanDB.LIST:
|
||
var id: String = h["id"]
|
||
if Game.completed.has(id):
|
||
continue
|
||
if not offen_ids.has(id):
|
||
verdeckt += 1
|
||
continue
|
||
gezeigt += 1
|
||
var marke := ""
|
||
var farbe := C_GESEHEN
|
||
if Game.active_mission == id:
|
||
marke = " · aktiv %d %%" % int(Game.mission_progress(id))
|
||
farbe = C_AKZENT
|
||
elif Game.angenommen(id):
|
||
marke = " · %d %%" % int(Game.mission_progress(id))
|
||
if bool(h.get("eigen", false)):
|
||
farbe = C_WARN
|
||
_karte("%s · %s%s" % [h["name"], h["traum"], marke], farbe,
|
||
func() -> void: aushang_panel(id))
|
||
if gezeigt == 0:
|
||
_text("[color=#ffcc66]Nichts passt — im Hörsaal einen Weg lernen.[/color]", 16)
|
||
if verdeckt > 0:
|
||
_text("[color=#aaaaaa]%d verdeckt[/color]" % verdeckt, 14)
|
||
_zeigen("brett")
|
||
_scroll_wunsch = scroll_alt
|
||
_scroll_frames = 4
|
||
|
||
|
||
## Ein Aushang im Ganzen -- erst hier steht, was im Weg steht.
|
||
func aushang_panel(human_id: String) -> void:
|
||
var h := HumanDB.by_id(human_id)
|
||
_leeren(str(h["name"]))
|
||
_text("[color=#aaaaaa]%d — %s[/color]" % [h["alter"], h["beruf"]], 15)
|
||
_text("[b]Traum:[/b] %s" % h["traum"], 16)
|
||
_text("[b]Was im Weg steht:[/b] %s" % h["problem"], 15)
|
||
_trenner()
|
||
if Game.active_mission == human_id:
|
||
_text("[color=#ffdd77]Aktive Mission — %d %%[/color]" %
|
||
int(Game.mission_progress(human_id)), 15)
|
||
else:
|
||
if Game.angenommen(human_id):
|
||
_text("[color=#aaaaaa]Zurückgestellt — %d %%[/color]" %
|
||
int(Game.mission_progress(human_id)), 15)
|
||
_knopf("Mission annehmen", func() -> void:
|
||
Game.accept_mission(human_id)
|
||
brett_panel())
|
||
if bool(h.get("eigen", false)):
|
||
_knopf_leise("Aushang abnehmen", func() -> void:
|
||
Aushaenge.loeschen(human_id)
|
||
Game.welt_erzeugen()
|
||
brett_panel())
|
||
_zeigen("aushang", brett_panel)
|
||
|
||
|
||
# -------------------------------------------------------- Aushang schreiben
|
||
|
||
## Ein eigener Mensch: Name, Alter, Beruf, Traum, Wörter, Geschichte.
|
||
## Die Wörter sind das Einzige, was die Mechanik liest.
|
||
var _entwurf: Dictionary = {}
|
||
|
||
|
||
func schreiben_panel(entwurf: Dictionary = {}) -> void:
|
||
if not entwurf.is_empty():
|
||
_entwurf = entwurf
|
||
elif _entwurf.is_empty():
|
||
_entwurf = {"name": "", "alter": 30, "beruf": str(HumanDB.BERUFE[0]),
|
||
"traum": "", "geschichte": "", "woerter": []}
|
||
var scroll_alt := _scroll.scroll_vertical if offen and _kind == "schreiben" else 0
|
||
_leeren("Aushang schreiben")
|
||
|
||
var spalten := HBoxContainer.new()
|
||
spalten.add_theme_constant_override("separation", 22)
|
||
_content.add_child(spalten)
|
||
var links := VBoxContainer.new()
|
||
links.custom_minimum_size.x = 400
|
||
links.add_theme_constant_override("separation", 8)
|
||
spalten.add_child(links)
|
||
var rechts := VBoxContainer.new()
|
||
rechts.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
rechts.add_theme_constant_override("separation", 6)
|
||
spalten.add_child(rechts)
|
||
|
||
_ziel = links
|
||
_zeile("Name", _eingabe("name"))
|
||
_zeile("Alter", _zahl("alter", 14, 99))
|
||
_zeile("Beruf", _auswahl("beruf", HumanDB.BERUFE))
|
||
_zeile("Traum", _eingabe("traum"))
|
||
_text("[b]Geschichte[/b] [color=#8e93a8]was im Weg steht[/color]", 14)
|
||
_textfeld("geschichte")
|
||
_trenner()
|
||
_vorschau = _text("", 14)
|
||
_trenner()
|
||
var reihe := HBoxContainer.new()
|
||
reihe.add_theme_constant_override("separation", 8)
|
||
links.add_child(reihe)
|
||
_reihenknopf(reihe, "Zufall", func() -> void:
|
||
schreiben_panel(HumanDB.zufallsprofil()))
|
||
_reihenknopf(reihe, "Speichern", func() -> void: _aushang_speichern())
|
||
|
||
_ziel = rechts
|
||
var w: Array = _entwurf["woerter"]
|
||
_woerter_kopf = _text("", 14)
|
||
for gruppe in Woerter.GRUPPEN:
|
||
_text("[color=#9fb6ff]%s[/color]" % Woerter.GRUPPEN[gruppe], 13)
|
||
_haken_raster(Woerter.in_gruppe(gruppe))
|
||
_ziel = null
|
||
_vorschau_aktualisieren()
|
||
_zeigen("schreiben", brett_panel)
|
||
_scroll_wunsch = scroll_alt
|
||
_scroll_frames = 4
|
||
|
||
|
||
var _vorschau: RichTextLabel
|
||
var _woerter_kopf: RichTextLabel
|
||
|
||
|
||
## Was aus den Wörtern wird -- live, während man ankreuzt.
|
||
func _vorschau_aktualisieren() -> void:
|
||
if _vorschau == null or not is_instance_valid(_vorschau):
|
||
return
|
||
var w: Array = _entwurf["woerter"]
|
||
_woerter_kopf.text = "[b]Wörter[/b] [color=#8e93a8]mindestens %d · %d gewählt[/color]" % \
|
||
[Woerter.MINDESTENS, w.size()]
|
||
if w.is_empty():
|
||
_vorschau.text = "[color=#8e93a8]Kreuze an, was auf sie oder ihn zutrifft — daraus entsteht der Mensch.[/color]"
|
||
return
|
||
var bild := Woerter.profil(w)
|
||
var urs: Array = []
|
||
for u in bild["ursachen"]:
|
||
urs.append(str(Ursachen.LISTE[u]["name"]))
|
||
var prob: Array = []
|
||
for pr in bild["probleme"]:
|
||
prob.append(Probleme.name_von(str(pr)))
|
||
var zuege: Array = []
|
||
var namen := {"einrichten": "offen", "guete": "beständig", "abwehr": "stur",
|
||
"momente": "reizbar", "dauer": "lang", "tempo": "treibt", "sozial": "gesellig",
|
||
"sichtbar": "durchschaubar"}
|
||
for v in Woerter.VERHALTEN:
|
||
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]" % \
|
||
[" · ".join(urs), " · ".join(prob), (bild["blocker"] as Array).size(),
|
||
" ".join(zuege) if not zuege.is_empty() else "unauffällig"]
|
||
|
||
|
||
func _aushang_speichern() -> void:
|
||
var fehler := Aushaenge.pruefen(_entwurf)
|
||
if not fehler.is_empty():
|
||
Game.notified.emit("Aushang unvollständig: %s" % ", ".join(fehler), Color(1, 0.7, 0.5))
|
||
return
|
||
var id := Aushaenge.speichern(_entwurf)
|
||
Game.welt_erzeugen()
|
||
Game.notified.emit("Aushang hängt: %s" % _entwurf["name"], Color(0.6, 1, 0.7))
|
||
_entwurf = {}
|
||
_verlauf.clear()
|
||
aushang_panel(id)
|
||
|
||
|
||
# ---- Eingabehelfer
|
||
|
||
func _zeile(titel: String, widget: Control) -> void:
|
||
var hb := HBoxContainer.new()
|
||
hb.add_theme_constant_override("separation", 10)
|
||
var l := Label.new()
|
||
l.text = titel
|
||
l.custom_minimum_size.x = 90
|
||
l.add_theme_font_size_override("font_size", 15)
|
||
hb.add_child(l)
|
||
widget.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
hb.add_child(widget)
|
||
_wo().add_child(hb)
|
||
|
||
|
||
func _eingabe(feld: String) -> LineEdit:
|
||
var e := LineEdit.new()
|
||
e.text = str(_entwurf.get(feld, ""))
|
||
e.add_theme_font_size_override("font_size", 15)
|
||
e.text_changed.connect(func(t: String) -> void: _entwurf[feld] = t)
|
||
return e
|
||
|
||
|
||
func _zahl(feld: String, von: int, bis: int) -> SpinBox:
|
||
var s := SpinBox.new()
|
||
s.min_value = von
|
||
s.max_value = bis
|
||
s.value = int(_entwurf.get(feld, von))
|
||
s.value_changed.connect(func(v: float) -> void: _entwurf[feld] = int(v))
|
||
return s
|
||
|
||
|
||
func _auswahl(feld: String, optionen: Array) -> OptionButton:
|
||
var o := OptionButton.new()
|
||
for i in range(optionen.size()):
|
||
o.add_item(str(optionen[i]), i)
|
||
if str(optionen[i]) == str(_entwurf.get(feld, "")):
|
||
o.select(i)
|
||
o.item_selected.connect(func(i: int) -> void: _entwurf[feld] = str(optionen[i]))
|
||
return o
|
||
|
||
|
||
func _textfeld(feld: String) -> TextEdit:
|
||
var t := TextEdit.new()
|
||
t.text = str(_entwurf.get(feld, ""))
|
||
t.custom_minimum_size = Vector2(0, 80)
|
||
t.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY
|
||
t.add_theme_font_size_override("font_size", 15)
|
||
t.text_changed.connect(func() -> void: _entwurf[feld] = t.text)
|
||
_wo().add_child(t)
|
||
return t
|
||
|
||
|
||
func _haken_raster(keys: Array) -> void:
|
||
var g := GridContainer.new()
|
||
g.columns = 3
|
||
g.add_theme_constant_override("h_separation", 4)
|
||
_wo().add_child(g)
|
||
var gewaehlt: Array = _entwurf["woerter"]
|
||
for key in keys:
|
||
var c := CheckBox.new()
|
||
c.text = Woerter.name_von(str(key))
|
||
c.button_pressed = gewaehlt.has(key)
|
||
c.add_theme_font_size_override("font_size", 14)
|
||
c.toggled.connect(func(an: bool) -> void:
|
||
var liste: Array = _entwurf["woerter"]
|
||
if an and not liste.has(key):
|
||
liste.append(key)
|
||
elif not an:
|
||
liste.erase(key)
|
||
_vorschau_aktualisieren())
|
||
g.add_child(c)
|
||
|
||
|
||
func _reihenknopf(reihe: HBoxContainer, text: String, cb: Callable) -> void:
|
||
var b := Button.new()
|
||
b.text = text
|
||
b.custom_minimum_size = Vector2(0, 32)
|
||
b.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
b.add_theme_font_size_override("font_size", 16)
|
||
b.pressed.connect(cb)
|
||
reihe.add_child(b)
|
||
|
||
|
||
# -------------------------------------------------------- Vor Ort
|
||
|
||
## Draußen am Menschen: hier greift man in den Slot ein, der gerade läuft.
|
||
func anwenden_panel(mensch: Node3D) -> void:
|
||
_apply_human = mensch
|
||
var h: Dictionary = mensch.data
|
||
var id: String = str(h["id"])
|
||
var nr: int = int(mensch.slot)
|
||
_leeren("%s — %s" % [h["name"], Orte.text(mensch.activity)])
|
||
_text("[b]Traum:[/b] %s" % h["traum"], 16)
|
||
if Game.active_mission != id:
|
||
_text("[color=#ffcc66]Keine aktive Mission für diesen Menschen.[/color]", 15)
|
||
_zeigen("anwenden")
|
||
return
|
||
var grund := Game.slot_grund(id, nr)
|
||
if not Game.erkannt(id, nr):
|
||
_text("[color=#ffcc66]Noch nicht notiert — [b]R[/b] im Moment.[/color]", 16)
|
||
_zeigen("anwenden")
|
||
return
|
||
if Ursachen.ist_blocker(grund):
|
||
var b: Dictionary = Ursachen.BLOCKER[grund]
|
||
_text("Sie %s. [b]Auslöser:[/b] %s · [b]Belohnung:[/b] %s" %
|
||
[b["name"], b["ausloeser"], Ursachen.belohnung_text(str(b["belohnung"]))], 15)
|
||
_trenner()
|
||
if Game.known.is_empty():
|
||
_text("[color=#ffcc66]Du kennst keine Technik.[/color]")
|
||
_zeigen("anwenden")
|
||
return
|
||
for tid in TechDB.ids():
|
||
if not Game.knows(tid):
|
||
continue
|
||
var pruef := Game.system_pruefen(id, nr, tid)
|
||
var t := TechDB.by_id(tid)
|
||
var beschriftung := "%s · Stufe %d" % [t["name"], Game.skill_of(tid)]
|
||
if not pruef["geht"]:
|
||
beschriftung += " — " + str(pruef["grund"])
|
||
elif mensch.resist_left > 0.0:
|
||
beschriftung += " — macht gerade dicht"
|
||
_knopf(beschriftung, func() -> void: _vor_ort_setzen(tid), bool(pruef["geht"]))
|
||
_zeigen("anwenden")
|
||
|
||
|
||
func _vor_ort_setzen(tech_id: String) -> void:
|
||
if _apply_human == null or not is_instance_valid(_apply_human):
|
||
schliessen()
|
||
return
|
||
var mensch := _apply_human
|
||
var id: String = str(mensch.data["id"])
|
||
var res := Game.system_setzen(id, int(mensch.slot), tech_id, mensch.resist_left > 0.0)
|
||
schliessen()
|
||
if not res["ok"]:
|
||
Game.notified.emit(str(res["grund"]), Color(1, 0.6, 0.4))
|
||
return
|
||
var t := TechDB.by_id(tech_id)
|
||
if res["erfolg"]:
|
||
mensch.reagiere(true, "%s\nsteht" % t["name"])
|
||
else:
|
||
mensch.reagiere(false, "%s greift nicht" % t["name"])
|
||
|
||
|
||
func _unhandled_input(event: InputEvent) -> void:
|
||
if offen and event.is_action_pressed("ui_cancel"):
|
||
_zurueck()
|
||
get_viewport().set_input_as_handled()
|