This commit is contained in:
team3
2026-09-16 17:55:38 +02:00
parent c9416ed3c6
commit f012dd7cd5
17 changed files with 878 additions and 160 deletions

View File

@@ -22,18 +22,24 @@ const MenuScript = preload("res://scripts/menu.gd")
enum Mode { MENU, SOLO, HOST, CLIENT }
const TRACK_POINTS := [
Vector2(-139, -77), Vector2(-53, -101), Vector2(38, -86), Vector2(110, -48),
Vector2(144, 14), Vector2(101, 77), Vector2(14, 96), Vector2(-62, 77),
Vector2(-101, 34), Vector2(-154, -14),
]
## Zwei Weichenpunkte, drei Abschnitte: Talstrecke und Bergstrecke fuehren
## beide von J1 nach J2, die Heimstrecke zurueck.
const J1 := Vector2(-139, -77)
const J2 := Vector2(144, 14)
const T1 := Vector2(101, -87) ## Fahrtrichtung an J1
const T2 := Vector2(-9, 125) ## Fahrtrichtung an J2
const SEG_TAL := [J1, Vector2(-53, -101), Vector2(38, -86), Vector2(110, -48), J2]
const SEG_BERG := [J1, Vector2(-85, -38), Vector2(-20, -14), Vector2(58, -12), Vector2(112, -24), J2]
const SEG_HEIM := [J2, Vector2(101, 77), Vector2(14, 96), Vector2(-62, 77), Vector2(-101, 34), Vector2(-154, -14), J1]
## name, abschnitt, anteil, produziert, akzeptiert, seite, wand, dach, schornstein
const STATION_DEFS := [
["Waldkirch", 0.04, Cargo.Type.WOOD, [Cargo.Type.GRAIN], 1.0, "c9a06a", "8a4b2f", false],
["Kohlberg", 0.22, Cargo.Type.COAL, [], -1.0, "6f6a63", "40393a", true],
["Eisenwerk", 0.40, -1, [Cargo.Type.COAL, Cargo.Type.WOOD], 1.0, "9aa0a6", "55606b", true],
["Grüntal", 0.60, Cargo.Type.GRAIN, [], -1.0, "e8ddc4", "b4562f", false],
["Ahornstadt", 0.80, -1, [Cargo.Type.GRAIN, Cargo.Type.WOOD], 1.0, "f0e6d2", "a83b30", true],
["Waldkirch", 0, 0.10, Cargo.Type.WOOD, [Cargo.Type.GRAIN], 1.0, "c9a06a", "8a4b2f", false],
["Kohlberg", 0, 0.55, Cargo.Type.COAL, [], -1.0, "6f6a63", "40393a", true],
["Eisenwerk", 0, 0.88, -1, [Cargo.Type.COAL, Cargo.Type.WOOD], 1.0, "9aa0a6", "55606b", true],
["Grüntal", 2, 0.33, Cargo.Type.GRAIN, [], -1.0, "e8ddc4", "b4562f", false],
["Ahornstadt", 2, 0.67, -1, [Cargo.Type.GRAIN, Cargo.Type.WOOD], 1.0, "f0e6d2", "a83b30", true],
["Bergblick", 1, 0.50, -1, [], -1.0, "e6dcc2", "5c7a4e", false],
]
const WORLD_SEED := 20260828
@@ -42,7 +48,21 @@ const BOARD_INTERVAL := 0.45
const LOAD_INTERVAL := 0.35
const DRINK_TIP := 14
const START_MONEY := 1500
const POSE_HZ := 15.0
const POSE_HZ := 20.0
const SAVE_PATH := "user://save.json"
const AUTOSAVE_SEC := 20.0
## Meilensteine. "kind" sagt, welcher Zähler zählt.
const GOALS := [
{"id": "tickets10", "text": "10 Tickets kontrollieren", "kind": "tickets", "value": 10, "reward": 250},
{"id": "cargo30", "text": "30 Einheiten Fracht liefern", "kind": "cargo", "value": 30, "reward": 400},
{"id": "dining", "text": "Einen Speisewagen anhängen", "kind": "dining", "value": 1, "reward": 300},
{"id": "berg", "text": "Bergblick anfahren", "kind": "visit", "value": 1, "reward": 500},
{"id": "rep15", "text": "Ansehen 15 erreichen", "kind": "rep", "value": 15, "reward": 800},
{"id": "tickets60", "text": "60 Tickets kontrollieren", "kind": "tickets", "value": 60, "reward": 900},
{"id": "rep40", "text": "Ansehen 40 erreichen", "kind": "rep", "value": 40, "reward": 1500},
{"id": "final", "text": "Elektro-Express mit 5 Wagen und Ansehen 60", "kind": "final", "value": 60, "reward": 5000},
]
const STATE_HZ := 4.0
var mode := Mode.MENU
@@ -53,6 +73,13 @@ var money := START_MONEY
var reputation := 0
var upgrades: Dictionary = {}
var delivered_units := 0
var tickets_checked := 0
var visited: Dictionary = {} ## Bahnhofsname -> true
var goals_done: Dictionary = {} ## Ziel-ID -> true
var won := false
var my_name := "Crew"
var _autosave := AUTOSAVE_SEC
var _load_on_begin := false
var track: Node3D
var world: Node3D
@@ -83,6 +110,7 @@ func _ready() -> void:
menu = MenuScript.new()
add_child(menu)
menu.solo_chosen.connect(_start_solo)
menu.continue_chosen.connect(_start_continue)
menu.host_chosen.connect(_start_host)
menu.join_chosen.connect(_start_join)
multiplayer.peer_connected.connect(_on_peer_connected)
@@ -104,6 +132,10 @@ func _start_solo() -> void:
my_id = 1
_begin()
func _start_continue() -> void:
_load_on_begin = true
_start_solo()
func _start_host(port: int) -> void:
var peer := ENetMultiplayerPeer.new()
var err := peer.create_server(port, 8)
@@ -140,6 +172,7 @@ func _on_server_gone() -> void:
hud.toast("Verbindung zum Host verloren.", Color("ff9a8a"))
func _begin() -> void:
my_name = menu.player_name()
_rng.seed = WORLD_SEED
_build_environment()
_build_track()
@@ -152,10 +185,18 @@ func _begin() -> void:
_build_ui()
audio = AudioScript.new()
add_child(audio)
players[my_id] = {"s": player.s, "x": player.x, "yaw": 0.0, "carrying": 0, "avatar": null}
players[my_id] = _new_player_entry(my_name)
menu.hide_menu()
player.capture_mouse(true)
started = true
if _load_on_begin:
load_game()
if mode == Mode.CLIENT:
rpc_id(1, "net_hello", my_name)
func _new_player_entry(pname: String) -> Dictionary:
return {"s": 0.0, "x": 0.0, "yaw": 0.0, "carrying": 0, "avatar": null,
"name": pname, "tpos": Vector3.ZERO, "tyaw": 0.0}
# --- Aufbau ---------------------------------------------------------------
@@ -193,22 +234,26 @@ func _build_environment() -> void:
func _build_track() -> void:
track = TrackScript.new()
add_child(track)
var pts := PackedVector2Array()
for p in TRACK_POINTS:
pts.append(p)
track.build(pts)
var tal: int = track.add_segment("Talstrecke", SEG_TAL, T1, T2)
var berg: int = track.add_segment("Bergstrecke", SEG_BERG, T1, T2)
var heim: int = track.add_segment("Heimstrecke", SEG_HEIM, T2, T1)
track.connect_segments(heim, tal) # Nachfolger 0 = links
track.connect_segments(heim, berg) # Nachfolger 1 = rechts
track.connect_segments(tal, heim)
track.connect_segments(berg, heim)
track.finish()
func _build_stations() -> void:
var l: float = track.length()
for d in STATION_DEFS:
var st := StationScript.new()
add_child(st)
var acc: Array[int] = []
for t in d[3]:
for t in d[4]:
acc.append(int(t))
st.setup(String(d[0]), l * float(d[1]), int(d[2]), acc)
st.build_visuals(track.sample(st.progress), float(d[4]),
Color(String(d[5])), Color(String(d[6])), bool(d[7]))
var seg_id := int(d[1])
st.setup(String(d[0]), seg_id, track.seg_length(seg_id) * float(d[2]), int(d[3]), acc)
st.build_visuals(track.sample(seg_id, st.progress), float(d[5]),
Color(String(d[6])), Color(String(d[7])), bool(d[8]))
if st.produces >= 0:
st.stock = 8.0
stations.append(st)
@@ -223,8 +268,9 @@ func _build_train() -> void:
## Stellt den Zug mittig an einen Bahnsteig.
func _park_at(st) -> void:
train.progress = fposmod(st.progress + train.total_length() * 0.5, track.length())
train.apply_snapshot(train.progress, 0.0)
train.speed = 0.0
train.place_head(st.seg, st.progress + train.total_length() * 0.5)
train._update_dock(0.0)
func _build_player() -> void:
player = PlayerScript.new()
@@ -234,8 +280,8 @@ func _build_player() -> void:
cam_rig = CameraRig.new()
add_child(cam_rig)
cam_rig.target = train.followers[0]
cam_rig.snap_to(track.sample(train.progress).origin)
cam_rig.target = train.cars[0]
cam_rig.snap_to(train.cars[0].global_position)
player.camera.current = true
func _build_ui() -> void:
@@ -257,6 +303,11 @@ func _process(delta: float) -> void:
_spawn_passengers(delta)
_update_passengers(delta)
_handle_station_stop(delta)
_track_visits()
_autosave -= delta
if _autosave <= 0.0:
_autosave = AUTOSAVE_SEC
save_game()
_handle_hold_actions(delta)
_update_audio(delta)
_sync_out(delta)
@@ -269,22 +320,38 @@ func _physics_process(delta: float) -> void:
var driving: bool = driver_id == my_id and not player.input_locked
var th := 0.0
var br := false
var sw: int = train.switch_choice
if driving:
th = 1.0 if Input.is_key_pressed(KEY_W) else 0.0
br = Input.is_key_pressed(KEY_S) or Input.is_key_pressed(KEY_SPACE)
if Input.is_key_pressed(KEY_A):
sw = 0
elif Input.is_key_pressed(KEY_D):
sw = 1
if is_server():
if driver_id == my_id:
train.throttle = th
train.braking = br
train.switch_choice = sw
train.drive(delta)
elif driving:
rpc_id(1, "net_controls", th, br)
else:
if driving:
rpc_id(1, "net_controls", th, br, sw)
train.client_tick(delta)
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_CLOSE_REQUEST and started and is_server():
save_game()
func _unhandled_input(event: InputEvent) -> void:
if not started:
return
if event is InputEventKey and event.pressed and not event.echo:
match event.keycode:
KEY_F5:
if is_server():
save_game()
hud.toast("Gespeichert", Color("8fd8ff"))
KEY_H:
if driver_id == my_id:
request("whistle")
@@ -358,13 +425,13 @@ func _sync_out(delta: float) -> void:
players[my_id]["x"] = player.x
players[my_id]["yaw"] = player.yaw
rpc("net_poses", _pose_payload())
rpc("net_train", train.seg, train.progress, train.speed, train.switch_choice, train.history)
else:
rpc_id(1, "net_pose", player.s, player.x, player.yaw)
if is_server():
_state_timer -= delta
if _state_timer <= 0.0:
_state_timer = 1.0 / STATE_HZ
rpc("net_train", train.progress, train.speed)
rpc("net_state", _state_payload())
rpc("net_passengers", _passenger_payload())
@@ -379,10 +446,15 @@ func _state_payload() -> Dictionary:
var stock: Array = []
for st in stations:
stock.append(st.stock)
var names := {}
for id in players.keys():
names[int(id)] = String(players[id]["name"])
return {
"money": money, "rep": reputation, "upg": upgrades.keys(),
"driver": driver_id, "cargo": train.cargo.duplicate(), "stock": stock,
"loco": train.loco_id, "consist": train.consist.duplicate(),
"names": names, "goals": goals_done.keys(), "won": won,
"tickets": tickets_checked, "delivered": delivered_units,
}
func _passenger_payload() -> Array:
@@ -402,7 +474,7 @@ func _passenger_payload() -> Array:
func net_pose(s: float, x: float, yaw: float) -> void:
var id := multiplayer.get_remote_sender_id()
if not players.has(id):
players[id] = {"s": s, "x": x, "yaw": yaw, "carrying": 0, "avatar": null}
players[id] = _new_player_entry("Crew %d" % id)
players[id]["s"] = s
players[id]["x"] = x
players[id]["yaw"] = yaw
@@ -414,7 +486,7 @@ func net_poses(list: Array) -> void:
var id := int(e[0])
seen[id] = true
if not players.has(id):
players[id] = {"s": 0.0, "x": 0.0, "yaw": 0.0, "carrying": 0, "avatar": null}
players[id] = _new_player_entry("Crew %d" % id)
if id != my_id:
players[id]["s"] = float(e[1])
players[id]["x"] = float(e[2])
@@ -427,8 +499,8 @@ func net_poses(list: Array) -> void:
_drop_player(int(id))
@rpc("authority", "call_remote", "unreliable_ordered")
func net_train(p: float, v: float) -> void:
train.apply_snapshot(p, v)
func net_train(seg_id: int, p: float, v: float, sw: int, hist: Array) -> void:
train.apply_snapshot(seg_id, p, v, sw, hist)
@rpc("authority", "call_remote", "reliable")
func net_state(d: Dictionary) -> void:
@@ -438,6 +510,16 @@ func net_state(d: Dictionary) -> void:
for k in d["upg"]:
upgrades[String(k)] = true
driver_id = int(d["driver"])
for id in d["names"].keys():
if not players.has(int(id)):
players[int(id)] = _new_player_entry(String(d["names"][id]))
players[int(id)]["name"] = String(d["names"][id])
goals_done.clear()
for g in d["goals"]:
goals_done[String(g)] = true
won = bool(d["won"])
tickets_checked = int(d["tickets"])
delivered_units = int(d["delivered"])
if driver_id != my_id and player.mode == PlayerScript.Mode.DRIVE:
player.mode = PlayerScript.Mode.WALK
elif driver_id == my_id:
@@ -498,13 +580,25 @@ func net_sfx(name: String, at_loco: bool, db: float) -> void:
_play_sfx(name, at_loco, db)
@rpc("any_peer", "call_remote", "unreliable_ordered")
func net_controls(th: float, br: bool) -> void:
func net_controls(th: float, br: bool, sw: int) -> void:
if not is_server():
return
if multiplayer.get_remote_sender_id() != driver_id:
return
train.throttle = th
train.braking = br
train.switch_choice = sw
@rpc("any_peer", "call_remote", "reliable")
func net_hello(pname: String) -> void:
if not is_server():
return
var id := multiplayer.get_remote_sender_id()
if not players.has(id):
players[id] = _new_player_entry(pname)
players[id]["name"] = pname
_broadcast_toast("%s ist an Bord" % pname, Color("8fd8ff"))
_push_state()
@rpc("any_peer", "call_remote", "reliable")
func net_request(action: String, a: String, b: int, c: int) -> void:
@@ -549,17 +643,17 @@ func _make_passenger(pid: int):
func _on_peer_connected(id: int) -> void:
if not is_server() or not started:
return
players[id] = {"s": 0.0, "x": 0.0, "yaw": 0.0, "carrying": 0, "avatar": null}
players[id] = _new_player_entry("Crew %d" % id)
rpc_id(id, "net_state", _state_payload())
rpc_id(id, "net_passengers", _passenger_payload())
hud.toast("Spieler %d ist an Bord" % id, Color("8fd8ff"))
func _on_peer_disconnected(id: int) -> void:
var pname: String = String(players.get(id, {}).get("name", "Crew %d" % id))
_drop_player(id)
if driver_id == id:
driver_id = 0
if hud:
hud.toast("Spieler %d hat den Zug verlassen" % id, Color("ffd978"))
hud.toast("%s hat den Zug verlassen" % pname, Color("ffd978"))
func _drop_player(id: int) -> void:
if id == my_id or not players.has(id):
@@ -576,16 +670,22 @@ func _update_avatars() -> void:
var e: Dictionary = players[id]
if e["avatar"] == null:
var av := AvatarScript.new()
av.build(int(id), "Crew %d" % int(id))
av.build(int(id), String(e["name"]))
add_child(av)
e["avatar"] = av
var av2 = e["avatar"]
var loc: Dictionary = train.locate(float(e["s"]))
var xf: Transform3D = train.car_transform(int(loc["index"]))
av2.global_position = xf * Vector3(float(e["x"]), 1.10, float(loc["local_z"]))
av2.global_rotation = Vector3(0, xf.basis.get_euler().y + float(e["yaw"]), 0)
# Zielpose im Wagenraum; Bewegung wird geglättet, die Wagenlage nicht
var tp: Vector3 = Vector3(float(e["x"]), 1.10, float(loc["local_z"]))
var ty: float = float(e["yaw"])
var k: float = clampf(get_process_delta_time() * 14.0, 0.0, 1.0)
e["tpos"] = (e["tpos"] as Vector3).lerp(tp, k) if e["tpos"] != Vector3.ZERO else tp
e["tyaw"] = lerp_angle(float(e["tyaw"]), ty, k)
av2.global_position = xf * (e["tpos"] as Vector3)
av2.global_rotation = Vector3(0, xf.basis.get_euler().y + float(e["tyaw"]), 0)
av2.set_carrying(int(e["carrying"]))
av2.set_label("Crew %d%s" % [int(id), " (Führerstand)" if int(id) == driver_id else ""])
av2.set_label("%s%s" % [String(e["name"]), " (Führerstand)" if int(id) == driver_id else ""])
# --- Fahrgäste -----------------------------------------------------------
@@ -613,13 +713,12 @@ func spawn_passenger(idx: int, forced_dest := -1) -> bool:
p.fare = _fare(idx, dest)
var spot: Vector3 = st.waiting_spots[st.waiting.size()]
p.global_position = spot
p.global_rotation = Vector3(0, track.sample(st.progress).basis.get_euler().y + PI * 0.5 * st.side, 0)
p.global_rotation = Vector3(0, track.sample(st.seg, st.progress).basis.get_euler().y + PI * 0.5 * st.side, 0)
st.waiting.append(p)
return true
func _fare(a: int, b: int) -> int:
var l: float = track.length()
var d: float = fposmod(stations[b].progress - stations[a].progress, l)
var d: float = stations[a].global_position.distance_to(stations[b].global_position) * 1.6
var bonus := 1.0
for c in train.cars:
if c.id == "saloon":
@@ -749,6 +848,7 @@ func _do_request(peer: int, action: String, a: String, b: int, c: int) -> void:
"buy_loco": _srv_buy_loco(peer, a)
"buy_car": _srv_buy_car(peer, a)
"sell_car": _srv_sell_car(peer, b)
"sell_loco": _srv_sell_loco(peer)
"buy_upgrade": _srv_buy_upgrade(peer, a)
"whistle": _srv_whistle(peer)
@@ -796,7 +896,7 @@ func _carrying(peer: int) -> int:
func _set_carrying(peer: int, n: int) -> void:
if not players.has(peer):
players[peer] = {"s": 0.0, "x": 0.0, "yaw": 0.0, "carrying": 0, "avatar": null}
players[peer] = _new_player_entry("Crew %d" % peer)
players[peer]["carrying"] = n
if peer == my_id:
player.carrying = n
@@ -836,6 +936,7 @@ func _srv_use_seat(peer: int, car_index: int, seat_index: int) -> void:
func _check_ticket(p, peer: int) -> void:
p.ticket_checked = true
tickets_checked += 1
money += p.fare
_toast_to(peer, "Ticket nach %s +%d €" % [stations[p.dest_station].station_name, p.fare],
Color("aef2a0"))
@@ -849,6 +950,7 @@ func _check_all_tickets(peer: int) -> void:
for p in passengers:
if p.state == PassengerScript.State.SEATED and not p.ticket_checked:
p.ticket_checked = true
tickets_checked += 1
sum += p.fare
n += 1
p._refresh_label()
@@ -899,9 +1001,54 @@ func _srv_transfer_one(peer: int) -> void:
_push_state()
func _push_state() -> void:
if is_server():
_check_goals()
if mode == Mode.HOST:
rpc("net_state", _state_payload())
func _track_visits() -> void:
if train.docked_at != null and train.speed < 0.1:
var nm: String = train.docked_at.station_name
if not visited.has(nm):
visited[nm] = true
_check_goals()
## Aktuelles Ziel und Fortschritt, für das HUD.
func current_goal() -> Dictionary:
for g in GOALS:
if not goals_done.has(String(g["id"])):
return {"text": String(g["text"]), "progress": _goal_progress(g), "target": int(g["value"])}
return {}
func _goal_progress(g: Dictionary) -> int:
match String(g["kind"]):
"tickets": return tickets_checked
"cargo": return delivered_units
"rep": return reputation
"dining": return 1 if train.has_dining() else 0
"visit": return 1 if visited.has("Bergblick") else 0
"final": return reputation if train.loco_id == "express" and train.consist.size() >= 5 else 0
return 0
func _check_goals() -> void:
for g in GOALS:
var id := String(g["id"])
if goals_done.has(id):
continue
if _goal_progress(g) < int(g["value"]):
break # Ziele kommen der Reihe nach
goals_done[id] = true
money += int(g["reward"])
if id == "final":
won = true
_broadcast_toast("Linie ausgebaut! Ihr habt es geschafft. +%d €" % int(g["reward"]),
Color("ffd978"))
else:
_broadcast_toast("Ziel erreicht: %s +%d €" % [String(g["text"]), int(g["reward"])],
Color("ffd978"))
_broadcast_sfx("bell", false, -8.0)
save_game()
# --- Laden ----------------------------------------------------------------
func _open_shop() -> void:
@@ -953,6 +1100,18 @@ func _srv_sell_car(peer: int, index: int) -> void:
_broadcast_sfx("coin", false, -5.0)
_push_state()
## Die Lok geht zurück ans Depot, der Dampf-Veteran springt ein.
func _srv_sell_loco(peer: int) -> void:
if train.loco_id == "steam":
return
var d: Dictionary = Defs.LOCOS[train.loco_id]
money += int(d["sell"])
train.loco_id = "steam"
_rebuild_train()
_broadcast_toast("Verkauft: %s +%d €" % [String(d["name"]), int(d["sell"])], Color("ffd978"))
_broadcast_sfx("coin", false, -5.0)
_push_state()
func _srv_buy_upgrade(peer: int, id: String) -> void:
if not Defs.UPGRADES.has(id):
return
@@ -986,6 +1145,76 @@ func buy_upgrade(id: String) -> bool:
request("buy_upgrade", id)
return upgrades.size() != before
# --- Spielstand -------------------------------------------------------------
func save_game() -> void:
if not is_server():
return
var stock: Array = []
for st in stations:
stock.append(st.stock)
var data := {
"money": money, "rep": reputation, "upg": upgrades.keys(),
"delivered": delivered_units, "tickets": tickets_checked,
"visited": visited.keys(), "goals": goals_done.keys(), "won": won,
"loco": train.loco_id, "consist": train.consist,
"seg": train.seg, "progress": train.progress, "cargo": train.cargo,
"stock": stock,
}
var f := FileAccess.open(SAVE_PATH, FileAccess.WRITE)
if f == null:
return
f.store_string(JSON.stringify(data))
f.close()
func load_game() -> bool:
if not FileAccess.file_exists(SAVE_PATH):
return false
var f := FileAccess.open(SAVE_PATH, FileAccess.READ)
if f == null:
return false
var parsed = JSON.parse_string(f.get_as_text())
f.close()
if not (parsed is Dictionary):
return false
var d: Dictionary = parsed
money = int(d.get("money", START_MONEY))
reputation = int(d.get("rep", 0))
upgrades.clear()
for k in d.get("upg", []):
upgrades[String(k)] = true
delivered_units = int(d.get("delivered", 0))
tickets_checked = int(d.get("tickets", 0))
visited.clear()
for k in d.get("visited", []):
visited[String(k)] = true
goals_done.clear()
for k in d.get("goals", []):
goals_done[String(k)] = true
won = bool(d.get("won", false))
var loco := String(d.get("loco", "steam"))
if Defs.LOCOS.has(loco):
train.loco_id = loco
var typed: Array[String] = []
for c in d.get("consist", []):
if Defs.CARS.has(String(c)):
typed.append(String(c))
train.consist = typed
train.cargo.clear()
var cargo: Dictionary = d.get("cargo", {})
for k in cargo.keys():
train.cargo[int(k)] = int(cargo[k])
var stock: Array = d.get("stock", [])
for i in mini(stock.size(), stations.size()):
stations[i].stock = float(stock[i])
_rebuild_train()
var seg_id := int(d.get("seg", 0))
if seg_id >= 0 and seg_id < track.segments.size():
train.place_head(seg_id, float(d.get("progress", 0.0)))
train._update_dock(0.0)
hud.toast("Spielstand geladen", Color("8fd8ff"))
return true
func _rebuild_train() -> void:
# Sitzende Gaeste zwischenparken, sonst verschwinden sie mit dem alten Wagen.
var riders: Array = []
@@ -1005,7 +1234,7 @@ func _rebuild_train() -> void:
p.seat_index = int(seat["index"])
train.cars[p.seat_car].seats[p.seat_index]["occupant"] = p
_finish_boarding(p)
cam_rig.target = train.followers[0]
cam_rig.target = train.cars[0]
player.snap_to_cab()
if driver_id == my_id:
player.mode = PlayerScript.Mode.DRIVE