Files
train-game/scripts/hud.gd
2026-08-29 11:44:31 +02:00

178 lines
5.7 KiB
GDScript

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 %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)