extends CanvasLayer ## Fadenkreuz, Statusleiste, Interaktionshinweis und Meldungen. const Cargo = preload("res://scripts/cargo.gd") const Defs = preload("res://scripts/car_defs.gd") const PassengerScript = preload("res://scripts/passenger.gd") const PlayerScript = preload("res://scripts/player.gd") var _stats := Label.new() var _prompt := Label.new() var _drive := Label.new() var _cross := Panel.new() var _toasts: Array = [] var _toast_box := VBoxContainer.new() const PROMPTS := { "drive": "E Steuerung übernehmen", "shop": "E Depot-Terminal öffnen", "tap": "E Getränke aufnehmen", "load": "E halten Be- und entladen", "seat": "E Fahrgast", } func _ready() -> void: layer = 1 var panel := PanelContainer.new() panel.position = Vector2(18, 16) panel.add_theme_stylebox_override("panel", _bg(Color(0.08, 0.11, 0.09, 0.55))) add_child(panel) _stats.add_theme_font_size_override("font_size", 17) panel.add_child(_stats) _toast_box.add_theme_constant_override("separation", 4) _toast_box.position = Vector2(18, 258) add_child(_toast_box) _prompt.add_theme_font_size_override("font_size", 20) _prompt.add_theme_color_override("font_color", Color("ffe9a8")) _prompt.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER add_child(_prompt) _drive.add_theme_font_size_override("font_size", 22) _drive.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER add_child(_drive) var cs := StyleBoxFlat.new() cs.bg_color = Color(1, 1, 1, 0.75) cs.set_corner_radius_all(3) _cross.add_theme_stylebox_override("panel", cs) _cross.size = Vector2(5, 5) add_child(_cross) # Kein HUD-Element darf Eingaben schlucken - sonst stirbt der Mausblick. _ignore_mouse(self) get_viewport().size_changed.connect(_layout) _layout() func _ignore_mouse(n: Node) -> void: if n is Control: (n as Control).mouse_filter = Control.MOUSE_FILTER_IGNORE for c in n.get_children(): _ignore_mouse(c) func _bg(c: Color) -> StyleBoxFlat: var s := StyleBoxFlat.new() s.bg_color = c s.set_corner_radius_all(10) s.set_content_margin_all(14) return s func _layout() -> void: var v := get_viewport().get_visible_rect().size _cross.position = v * 0.5 - Vector2(2.5, 2.5) _prompt.size = Vector2(v.x, 30) _prompt.position = Vector2(0, v.y * 0.58) _drive.size = Vector2(v.x, 34) _drive.position = Vector2(0, v.y - 96) func refresh(game) -> void: var t = game.train var lines: Array[String] = [] lines.append("%d € Ansehen %d" % [game.money, game.reputation]) lines.append("") lines.append("Zug %s + %d Wagen" % [Defs.display_name(t.loco_id), t.consist.size()]) lines.append("Tempo %3.0f km/h" % t.speed_kmh()) var seated := 0 var thirsty := 0 var unchecked := 0 for p in game.passengers: if p.state == PassengerScript.State.SEATED: seated += 1 if p.wants_drink and not p.served: thirsty += 1 if not p.ticket_checked: unchecked += 1 lines.append("Gäste %d / %d ohne Ticket %d Durst %d" % [seated, t.seat_count(), unchecked, thirsty]) var cargo_txt := "leer" if t.total_cargo() > 0: var parts: Array[String] = [] for k in t.cargo.keys(): parts.append("%s %d" % [Cargo.NAMES[int(k)], int(t.cargo[k])]) cargo_txt = ", ".join(parts) lines.append("Ladung %d / %d (%s)" % [t.total_cargo(), t.cargo_capacity(), cargo_txt]) if game.player.carrying > 0: lines.append("Getränke dabei: %d" % game.player.carrying) var st = t.docked_at if st != null and t.speed < 0.1: var extra := "" if st.produces >= 0: extra = " Lager: %s %d" % [Cargo.NAMES[st.produces], int(st.stock)] lines.append("Halt: %s%s" % [st.station_name, extra]) else: var nxt: Array = t.next_station() lines.append("Nächster Halt: %s (%d m)" % [nxt[0].station_name, int(nxt[1])]) _stats.text = "\n".join(lines) var h: Dictionary = game.player.hotspot if game.player.mode == PlayerScript.Mode.DRIVE: _prompt.text = "E Steuerung abgeben" elif h.is_empty(): _prompt.text = "" elif String(h["kind"]) == "seat": _prompt.text = _seat_prompt(game, h) else: _prompt.text = String(PROMPTS.get(String(h["kind"]), "")) if game.player.mode == PlayerScript.Mode.DRIVE: _drive.text = "W Gas S Bremse A/D Weiche %3.0f km/h" % t.speed_kmh() _drive.add_theme_color_override("font_color", Color("aef2a0") if t.speed > 0.2 else Color("ffd978")) else: _drive.text = "" _cross.visible = not game.player.input_locked _tick_toasts() func _seat_prompt(game, h: Dictionary) -> String: var car = game.train.cars[int(h["car"])] var occ = car.seats[int(h["seat"])]["occupant"] if occ == null: return "Freier Platz" if not occ.ticket_checked: return "E Ticket kontrollieren (%d €)" % occ.fare if occ.wants_drink and not occ.served: return "E Getränk servieren" if game.player.carrying > 0 else "Braucht ein Getränk" if game.upgrades.has("schedule"): return "Ziel: %s - %s" % [game.stations[occ.dest_station].station_name, occ.mood_word()] return "E Nachfragen" # --- Meldungen ------------------------------------------------------------ func toast(text: String, color: Color = Color("aef2a0")) -> void: var l := Label.new() l.text = text l.add_theme_font_size_override("font_size", 19) l.add_theme_color_override("font_color", color) l.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.8)) l.add_theme_constant_override("outline_size", 5) l.mouse_filter = Control.MOUSE_FILTER_IGNORE _toast_box.add_child(l) _toasts.append({"node": l, "time": 3.4}) if _toasts.size() > 5: var old: Dictionary = _toasts.pop_front() old["node"].queue_free() func _tick_toasts() -> void: var dt := get_process_delta_time() for e in _toasts.duplicate(): e["time"] -= dt if e["time"] <= 0.0: e["node"].queue_free() _toasts.erase(e) else: e["node"].modulate.a = clampf(float(e["time"]) / 0.9, 0.0, 1.0)