432 lines
14 KiB
GDScript
432 lines
14 KiB
GDScript
extends CanvasLayer
|
|
class_name UI
|
|
|
|
signal panel_opened
|
|
signal panel_closed
|
|
|
|
const BREITE := 900
|
|
|
|
var offen := false
|
|
var _kind := ""
|
|
|
|
var _hud: RichTextLabel
|
|
var _hinweis: Label
|
|
var _log: VBoxContainer
|
|
var _dim: ColorRect
|
|
var _panel: PanelContainer
|
|
var _titel: Label
|
|
var _content: VBoxContainer
|
|
var _scroll: ScrollContainer
|
|
var _apply_human: Node3D
|
|
var _on_travel: Callable
|
|
|
|
|
|
func _ready() -> void:
|
|
layer = 10
|
|
_baue_hud()
|
|
_baue_panel()
|
|
Game.notified.connect(_notiz)
|
|
Game.state_changed.connect(_hud_aktualisieren)
|
|
_hud_aktualisieren()
|
|
|
|
|
|
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, 130)
|
|
_hud.size = Vector2(420, 130)
|
|
_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)
|
|
add_child(_hud)
|
|
|
|
var steuerung := Label.new()
|
|
steuerung.text = "WASD Bewegen · Maus Umsehen · E Interagieren · Tab Kodex · Esc Maus frei"
|
|
steuerung.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
|
steuerung.position = Vector2(-640, 16)
|
|
steuerung.size = Vector2(620, 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.65))
|
|
steuerung.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
add_child(steuerung)
|
|
|
|
_hinweis = Label.new()
|
|
_hinweis.set_anchors_preset(Control.PRESET_CENTER_BOTTOM)
|
|
_hinweis.position = Vector2(-300, -110)
|
|
_hinweis.size = Vector2(600, 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
|
|
add_child(_hinweis)
|
|
|
|
_log = VBoxContainer.new()
|
|
_log.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
|
_log.position = Vector2(-520, -230)
|
|
_log.size = Vector2(500, 200)
|
|
_log.alignment = BoxContainer.ALIGNMENT_END
|
|
_log.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
add_child(_log)
|
|
|
|
|
|
func _baue_panel() -> void:
|
|
_dim = ColorRect.new()
|
|
_dim.color = Color(0, 0, 0, 0.62)
|
|
_dim.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
_dim.visible = false
|
|
add_child(_dim)
|
|
|
|
_panel = PanelContainer.new()
|
|
_panel.set_anchors_preset(Control.PRESET_CENTER)
|
|
_panel.custom_minimum_size = Vector2(BREITE, 200)
|
|
_panel.visible = false
|
|
var sb := StyleBoxFlat.new()
|
|
sb.bg_color = Color(0.10, 0.09, 0.16, 0.98)
|
|
sb.border_color = Color(0.55, 0.70, 1.0, 0.8)
|
|
sb.set_border_width_all(2)
|
|
sb.set_corner_radius_all(10)
|
|
sb.set_content_margin_all(22)
|
|
_panel.add_theme_stylebox_override("panel", sb)
|
|
add_child(_panel)
|
|
|
|
var vb := VBoxContainer.new()
|
|
vb.add_theme_constant_override("separation", 10)
|
|
_panel.add_child(vb)
|
|
|
|
_titel = Label.new()
|
|
_titel.add_theme_font_size_override("font_size", 26)
|
|
_titel.add_theme_color_override("font_color", Color(1, 0.93, 0.75))
|
|
vb.add_child(_titel)
|
|
|
|
_scroll = ScrollContainer.new()
|
|
_scroll.custom_minimum_size = Vector2(BREITE - 44, 140)
|
|
_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
|
vb.add_child(_scroll)
|
|
var scroll := _scroll
|
|
|
|
_content = VBoxContainer.new()
|
|
_content.custom_minimum_size.x = BREITE - 70
|
|
_content.add_theme_constant_override("separation", 10)
|
|
scroll.add_child(_content)
|
|
|
|
var fuss := Label.new()
|
|
fuss.text = "Esc schließt"
|
|
fuss.add_theme_font_size_override("font_size", 14)
|
|
fuss.add_theme_color_override("font_color", Color(1, 1, 1, 0.5))
|
|
vb.add_child(fuss)
|
|
|
|
|
|
func _process(_delta: float) -> void:
|
|
if not offen:
|
|
_hud_aktualisieren()
|
|
else:
|
|
var wunsch := _content.get_combined_minimum_size().y
|
|
var platz := get_viewport().get_visible_rect().size.y - 220.0
|
|
_scroll.custom_minimum_size.y = clampf(wunsch, 120.0, platz)
|
|
_panel.position = (get_viewport().get_visible_rect().size - _panel.size) * 0.5
|
|
|
|
|
|
# ------------------------------------------------------------------ HUD
|
|
|
|
func _hud_aktualisieren() -> void:
|
|
var zeilen: Array = []
|
|
zeilen.append("[b]%s[/b]" % Game.time_string())
|
|
zeilen.append("Einsicht: [b]%d[/b] Techniken: [b]%d / %d[/b]" %
|
|
[Game.einsicht, Game.known.size(), TechDB.LIST.size()])
|
|
if Game.active_mission != "":
|
|
var h := HumanDB.by_id(Game.active_mission)
|
|
zeilen.append("Mission: [color=#ffdd77]%s[/color] — %d %%" %
|
|
[h["name"], int(Game.mission_progress(Game.active_mission))])
|
|
zeilen.append("[color=#aaaaaa]%s[/color]" % h["traum"])
|
|
elif Game.completed.size() >= HumanDB.LIST.size():
|
|
zeilen.append("[color=#88ff99]Alle Träume erfüllt.[/color]")
|
|
else:
|
|
zeilen.append("[color=#aaaaaa]Keine Mission. Missionshalle in der Traumwelt.[/color]")
|
|
_hud.text = "\n".join(zeilen)
|
|
|
|
|
|
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", 16)
|
|
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() > 6:
|
|
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)
|
|
|
|
|
|
# ------------------------------------------------------------------ Panel
|
|
|
|
func _leeren(titel: String) -> void:
|
|
for c in _content.get_children():
|
|
_content.remove_child(c)
|
|
c.queue_free()
|
|
_titel.text = titel
|
|
|
|
|
|
func _text(bb: String, groesse: int = 17) -> 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.custom_minimum_size.x = BREITE - 80
|
|
r.text = bb
|
|
r.add_theme_font_size_override("normal_font_size", groesse)
|
|
r.add_theme_font_size_override("bold_font_size", groesse)
|
|
_content.add_child(r)
|
|
return r
|
|
|
|
|
|
func _knopf(beschriftung: String, cb: Callable, aktiv: bool = true) -> Button:
|
|
var b := Button.new()
|
|
b.text = beschriftung
|
|
b.disabled = not aktiv
|
|
b.custom_minimum_size = Vector2(0, 38)
|
|
b.add_theme_font_size_override("font_size", 17)
|
|
_content.add_child(b)
|
|
if aktiv:
|
|
b.pressed.connect(cb)
|
|
return b
|
|
|
|
|
|
func _trenner() -> void:
|
|
var s := HSeparator.new()
|
|
_content.add_child(s)
|
|
|
|
|
|
func _zeigen(kind: String) -> void:
|
|
_kind = kind
|
|
offen = true
|
|
_dim.visible = true
|
|
_panel.visible = true
|
|
_panel.reset_size()
|
|
panel_opened.emit()
|
|
|
|
|
|
func schliessen() -> void:
|
|
if not offen:
|
|
return
|
|
offen = false
|
|
_kind = ""
|
|
_apply_human = null
|
|
_dim.visible = false
|
|
_panel.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" %
|
|
[t["was"], t["wann"], t["warum"]])
|
|
|
|
|
|
# -------------------------------------------------------- Bibliothek
|
|
|
|
func technik_panel(id: String) -> void:
|
|
var t := TechDB.by_id(id)
|
|
_leeren(t["name"])
|
|
_text(_hinweis_block(t))
|
|
_trenner()
|
|
_text("[color=#9fb6ff]Hilft bei:[/color] %s\n[color=#9fb6ff]Gute Momente:[/color] %s" %
|
|
[TechDB.tags_text(t["tags"]), TechDB.momente_text(t["momente"])], 15)
|
|
if Game.knows(id):
|
|
_text("[color=#88ff99]Gelernt — Stufe %d (%s)[/color]" %
|
|
[Game.skill_of(id), Game.xp_to_next(id)])
|
|
else:
|
|
_text("[color=#ffcc66]Noch nicht gelernt. Kosten: 1 Einsicht (du hast %d).[/color]" %
|
|
Game.einsicht)
|
|
_knopf("Technik lernen (1 Einsicht)", func() -> void:
|
|
if Game.learn(id):
|
|
technik_panel(id), Game.einsicht >= 1)
|
|
_zeigen("technik")
|
|
|
|
|
|
# -------------------------------------------------------- Missionshalle
|
|
|
|
func mission_panel(id: String) -> void:
|
|
var h := HumanDB.by_id(id)
|
|
_leeren("%s, %d — %s" % [h["name"], h["alter"], h["beruf"]])
|
|
_text("[b]Traum:[/b] %s" % h["traum"])
|
|
_text("[b]Was im Weg steht:[/b] %s" % h["problem"])
|
|
_text("[color=#9fb6ff]Themen:[/color] %s" % TechDB.tags_text(h["tags"]), 15)
|
|
_trenner()
|
|
if Game.completed.has(id):
|
|
_text("[color=#88ff99]★ Der Traum ist erfüllt.[/color]")
|
|
elif Game.active_mission == id:
|
|
_text("[color=#ffdd77]Aktive Mission — %d %%[/color]" % int(Game.mission_progress(id)))
|
|
else:
|
|
_text("Fortschritt: %d %%" % int(Game.mission_progress(id)))
|
|
_knopf("Mission annehmen", func() -> void:
|
|
Game.accept_mission(id)
|
|
mission_panel(id))
|
|
_zeigen("mission")
|
|
|
|
|
|
# -------------------------------------------------------- Meditation
|
|
|
|
func vertiefen_panel() -> void:
|
|
_leeren("Meditationskammer")
|
|
_text("Übung kostet 1 Einsicht und bringt 3 Erfahrungspunkte. Du hast [b]%d[/b] Einsicht." %
|
|
Game.einsicht)
|
|
_trenner()
|
|
if Game.known.is_empty():
|
|
_text("[color=#ffcc66]Du kennst noch keine Technik. Geh in die Bibliothek.[/color]")
|
|
for id in TechDB.ids():
|
|
if not Game.knows(id):
|
|
continue
|
|
var t := TechDB.by_id(id)
|
|
_text("[b]%s[/b] — Stufe %d (%s)" % [t["name"], Game.skill_of(id), Game.xp_to_next(id)], 16)
|
|
_knopf("Vertiefen (1 Einsicht)", func() -> void:
|
|
if Game.deepen(id):
|
|
vertiefen_panel(), Game.einsicht >= 1)
|
|
_zeigen("vertiefen")
|
|
|
|
|
|
# -------------------------------------------------------- Kodex
|
|
|
|
func kodex_panel() -> void:
|
|
_leeren("Kodex der Techniken")
|
|
if Game.known.is_empty():
|
|
_text("Du kennst noch keine Technik. In der Bibliothek der Traumwelt liegen zwölf.")
|
|
for id in TechDB.ids():
|
|
if not Game.knows(id):
|
|
continue
|
|
var t := TechDB.by_id(id)
|
|
_text("[b][color=#ffe3a0]%s[/color][/b] · Stufe %d (%s)" %
|
|
[t["name"], Game.skill_of(id), Game.xp_to_next(id)], 18)
|
|
_text(_hinweis_block(t), 15)
|
|
_text("[color=#9fb6ff]Hilft bei:[/color] %s [color=#9fb6ff]Momente:[/color] %s" %
|
|
[TechDB.tags_text(t["tags"]), TechDB.momente_text(t["momente"])], 14)
|
|
_trenner()
|
|
var fehlend := TechDB.LIST.size() - Game.known.size()
|
|
if fehlend > 0:
|
|
_text("[color=#aaaaaa]%d weitere Techniken warten in der Bibliothek.[/color]" % fehlend, 15)
|
|
_zeigen("kodex")
|
|
|
|
|
|
# -------------------------------------------------------- Anwenden auf der Erde
|
|
|
|
func anwenden_panel(mensch: Node3D) -> void:
|
|
_apply_human = mensch
|
|
var h: Dictionary = mensch.data
|
|
var aktiv: String = mensch.activity
|
|
_leeren("%s — %s" % [h["name"], HumanDB.activity_text(aktiv)])
|
|
_text("[b]Traum:[/b] %s" % h["traum"], 16)
|
|
if Game.active_mission == h["id"]:
|
|
_text("Fortschritt: [b]%d %%[/b]" % int(Game.mission_progress(h["id"])), 16)
|
|
elif Game.completed.has(h["id"]):
|
|
_text("[color=#88ff99]★ Traum erfüllt.[/color]", 16)
|
|
else:
|
|
_text("[color=#ffcc66]Keine aktive Mission für diesen Menschen. " +
|
|
"Fortschritt zählt nur mit angenommener Mission.[/color]", 15)
|
|
_trenner()
|
|
|
|
if Game.known.is_empty():
|
|
_text("[color=#ffcc66]Du kennst keine Technik.[/color]")
|
|
_zeigen("anwenden")
|
|
return
|
|
|
|
for id in TechDB.ids():
|
|
if not Game.knows(id):
|
|
continue
|
|
var t := TechDB.by_id(id)
|
|
var ev := Game.evaluate(id, h["id"], aktiv, mensch.resist_left > 0.0)
|
|
var marker: Array = []
|
|
if ev["tag"]:
|
|
marker.append("[color=#88ff99]passt zum Thema[/color]")
|
|
if ev["moment"]:
|
|
marker.append("[color=#88ff99]passender Moment[/color]")
|
|
if ev["blocked"]:
|
|
marker.append("[color=#ff8877]%s[/color]" % ev["grund"])
|
|
elif mensch.resist_left > 0.0:
|
|
marker.append("[color=#ff8877]macht gerade dicht[/color]")
|
|
if marker.is_empty():
|
|
marker.append("[color=#aaaaaa]weder Thema noch Moment passen[/color]")
|
|
_text("[b]%s[/b] · Stufe %d\n[color=#cccccc]%s[/color]\n%s" %
|
|
[t["name"], Game.skill_of(id), t["wann"], " · ".join(marker)], 16)
|
|
var beschriftung := "Blockiert" if ev["blocked"] else "Anwenden — %d %% Erfolgschance" % int(round(float(ev["chance"]) * 100.0))
|
|
_knopf(beschriftung, func() -> void: _anwenden(id), not ev["blocked"])
|
|
_trenner()
|
|
_zeigen("anwenden")
|
|
|
|
|
|
func _anwenden(id: String) -> void:
|
|
if _apply_human == null or not is_instance_valid(_apply_human):
|
|
schliessen()
|
|
return
|
|
var mensch := _apply_human
|
|
var h: Dictionary = mensch.data
|
|
var res := Game.apply_technique(id, h["id"], mensch.activity, mensch.resist_left > 0.0)
|
|
schliessen()
|
|
if not res["ok"]:
|
|
Game.notified.emit(res["grund"], Color(1, 0.6, 0.4))
|
|
return
|
|
var t := TechDB.by_id(id)
|
|
if res["success"]:
|
|
mensch.reagiere(true, "%s wirkt\n+%d %%" % [t["name"], int(round(float(res["gain"])))])
|
|
Game.notified.emit("%s erreicht %s. (+%d %%)" %
|
|
[t["name"], h["name"], int(round(float(res["gain"])))], Color(0.6, 1, 0.7))
|
|
if res["wiederholung"] >= 2:
|
|
Game.notified.emit("Immer dieselbe Technik wirkt schwächer. Probier eine andere.",
|
|
Color(0.85, 0.85, 0.6))
|
|
else:
|
|
mensch.reagiere(false, "%s prallt ab" % t["name"])
|
|
Game.notified.emit("%s dringt gerade nicht durch (%d %% Chance)." %
|
|
[t["name"], int(round(float(res["chance"]) * 100.0))], Color(1, 0.6, 0.5))
|
|
|
|
|
|
# -------------------------------------------------------- Intro
|
|
|
|
func intro_panel(on_close: Callable) -> void:
|
|
_leeren("Traumwandler")
|
|
_text("Du bist ein Wesen aus der Traumwelt. Menschen tragen Träume mit sich herum, " +
|
|
"die sie allein nicht bewegt bekommen. Du wirst zu ihnen geschickt.")
|
|
_trenner()
|
|
_text("[b]In der Traumwelt[/b]\n" +
|
|
"· [b]Bibliothek der Techniken[/b] — lerne echte Lösungswege. Jede Technik sagt dir, " +
|
|
"was sie tut, wann sie passt und warum sie hilft.\n" +
|
|
"· [b]Missionshalle[/b] — nimm einen Menschen an, dem du helfen willst.\n" +
|
|
"· [b]Meditationskammer[/b] — vertiefe, was du schon kannst.\n" +
|
|
"· [b]Tor zur Erde[/b] — reise hinab, sobald du eine Mission hast.")
|
|
_text("[b]Auf der Erde[/b]\n" +
|
|
"· Du bist ein unsichtbarer Geist und schwebst durch Wände. Leertaste hoch, Umschalt runter.\n" +
|
|
"· Die Menschen leben ihren Tagesablauf: schlafen, arbeiten, grübeln, spazieren.\n" +
|
|
"· Geh nah heran und drücke [b]E[/b]. Deine Erfolgschance hängt von deiner Stufe ab — " +
|
|
"und davon, ob Technik und Moment zum Menschen passen.\n" +
|
|
"· Schlafende erreichst du nicht. Wer abblockt, braucht eine Weile Ruhe.")
|
|
_trenner()
|
|
_text("[color=#aaaaaa]Du beginnst mit 3 Einsicht. Jede Technik kostet 1. " +
|
|
"Ein erfüllter Traum bringt 3 zurück.[/color]", 15)
|
|
_knopf("Losgehen", func() -> void:
|
|
schliessen()
|
|
on_close.call())
|
|
_zeigen("intro")
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if offen and event.is_action_pressed("ui_cancel"):
|
|
schliessen()
|
|
get_viewport().set_input_as_handled()
|