update
This commit is contained in:
319
scripts/game.gd
319
scripts/game.gd
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user