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 _journal_seite := "techniken" var _scroll_wunsch := 0 var _scroll_frames := 0 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 · J Journal · 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) if _scroll_frames > 0: _scroll.scroll_vertical = _scroll_wunsch _scroll_frames -= 1 _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. Journal öffnen mit J.[/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"]]) # -------------------------------------------------------- Journal ## Das Journal ist immer dabei. Hier werden Techniken gelernt und vertieft ## und Missionen angenommen. 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) for eintrag in [["techniken", "Techniken"], ["missionen", "Missionen"]]: 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", 17) 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])) var mission := "keine" if Game.active_mission != "": mission = "%s (%d %%)" % [HumanDB.by_id(Game.active_mission)["name"], int(Game.mission_progress(Game.active_mission))] _text("[color=#aaaaaa]Einsicht: [b]%d[/b] · Mission: %s[/color]" % [Game.einsicht, mission], 15) _trenner() func _journal_techniken() -> void: _text("Zwölf Wege, die Menschen wirklich weiterbringen. Jede Anwendung " + "draußen macht eine Technik von allein besser. Vertiefen geht auch hier.", 15) _trenner() for id in TechDB.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) _text("[color=#9fb6ff]Hilft bei:[/color] %s [color=#9fb6ff]Momente:[/color] %s" % [TechDB.tags_text(t["tags"]), TechDB.momente_text(t["momente"])], 14) if Game.knows(id): _knopf("Vertiefen (1 Einsicht)", func() -> void: if Game.deepen(id): journal_panel(), Game.einsicht >= 1) else: _knopf("Lernen (1 Einsicht)", func() -> void: if Game.learn(id): journal_panel(), Game.einsicht >= 1) _trenner() func _journal_missionen() -> void: _text("Menschen, deren Traum feststeckt.", 15) _trenner() for h in HumanDB.LIST: var id: String = h["id"] _text("[b][color=#ffe3a0]%s[/color][/b], %d — %s" % [h["name"], h["alter"], h["beruf"]], 18) _text("[b]Traum:[/b] %s" % h["traum"], 16) _text("[b]Was im Weg steht:[/b] %s" % h["problem"], 15) _text("[color=#9fb6ff]Themen:[/color] %s" % TechDB.tags_text(h["tags"]), 14) if Game.completed.has(id): _text("[color=#88ff99]★ Der Traum ist erfüllt.[/color]", 15) elif Game.active_mission == id: _text("[color=#ffdd77]Aktive Mission — %d %%[/color]" % int(Game.mission_progress(id)), 15) else: _text("Fortschritt: %d %%" % int(Game.mission_progress(id)), 15) _knopf("Mission annehmen", func() -> void: Game.accept_mission(id) journal_panel()) _trenner() # -------------------------------------------------------- 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"], Orte.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]Dein Journal — Taste J[/b]\n" + "· Es ist immer dabei, in beiden Welten.\n" + "· [b]Techniken[/b] — lerne echte Lösungswege. Jede sagt dir, was sie tut, " + "wann sie passt und warum sie hilft. Anwenden macht sie von allein besser.\n" + "· [b]Missionen[/b] — nimm einen Menschen an, dem du helfen willst.") _text("[b]In der Traumwelt[/b]\n" + "· Ein Dorf auf einer schwebenden Insel. Vier Wesen wohnen hier, jedes in " + "seinem Haus, jedes mit eigenem Tagesablauf. Die Häuser stehen offen.\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()