first commit
This commit is contained in:
244
scripts/train.gd
Normal file
244
scripts/train.gd
Normal file
@@ -0,0 +1,244 @@
|
||||
extends Node3D
|
||||
## Der Zugverband: Lok + Waggons auf der Strecke, Fahrphysik,
|
||||
## und die Abbildung "Laengsposition im Zug" -> Weltkoordinate.
|
||||
|
||||
const CarScene = preload("res://scripts/car.gd")
|
||||
const Defs = preload("res://scripts/car_defs.gd")
|
||||
const Cargo = preload("res://scripts/cargo.gd")
|
||||
|
||||
signal consist_changed()
|
||||
|
||||
const GAP := 0.75 ## Abstand zwischen zwei Waggons
|
||||
const DRAG := 0.6
|
||||
const DOCK_RANGE := 3.0
|
||||
|
||||
var loco_id := "steam"
|
||||
var consist: Array[String] = [] ## nur die Waggons, ohne Lok
|
||||
var cars: Array = [] ## Car-Knoten, Index 0 = Lok
|
||||
var followers: Array[PathFollow3D] = []
|
||||
|
||||
var progress := 0.0 ## Position der Zugspitze auf der Kurve
|
||||
var speed := 0.0
|
||||
var throttle := 0.0
|
||||
var braking := false
|
||||
var cargo: Dictionary = {} ## Cargo.Type -> int
|
||||
var stations: Array = []
|
||||
var docked_at: Node = null
|
||||
|
||||
var _track
|
||||
var _stand_time := 0.0
|
||||
|
||||
func setup(track, station_list: Array) -> void:
|
||||
_track = track
|
||||
stations = station_list
|
||||
|
||||
func max_speed() -> float:
|
||||
return float(Defs.LOCOS[loco_id]["max_speed"])
|
||||
|
||||
func accel() -> float:
|
||||
return float(Defs.LOCOS[loco_id]["accel"])
|
||||
|
||||
func brake_power() -> float:
|
||||
return float(Defs.LOCOS[loco_id]["brake"])
|
||||
|
||||
func speed_kmh() -> float:
|
||||
return speed * 3.6
|
||||
|
||||
func cargo_capacity() -> int:
|
||||
var n := 0
|
||||
for c in cars:
|
||||
n += c.capacity
|
||||
return n
|
||||
|
||||
func total_cargo() -> int:
|
||||
var n := 0
|
||||
for v in cargo.values():
|
||||
n += int(v)
|
||||
return n
|
||||
|
||||
func has_dining() -> bool:
|
||||
for c in cars:
|
||||
if c.kind == Defs.Kind.DINING:
|
||||
return true
|
||||
return false
|
||||
|
||||
func seat_count() -> int:
|
||||
var n := 0
|
||||
for c in cars:
|
||||
n += c.seats.size()
|
||||
return n
|
||||
|
||||
# --- Verband aufbauen -----------------------------------------------------
|
||||
|
||||
func rebuild() -> void:
|
||||
for f in followers:
|
||||
f.queue_free()
|
||||
followers.clear()
|
||||
cars.clear()
|
||||
|
||||
var ids: Array[String] = [loco_id]
|
||||
ids.append_array(consist)
|
||||
var s := 0.0
|
||||
for i in ids.size():
|
||||
var car = CarScene.new()
|
||||
car.build(ids[i])
|
||||
car.index = i
|
||||
car.center_s = s - car.length * 0.5
|
||||
s = car.center_s - car.length * 0.5 - GAP
|
||||
var f := PathFollow3D.new()
|
||||
f.loop = true
|
||||
f.rotation_mode = PathFollow3D.ROTATION_Y
|
||||
f.use_model_front = true
|
||||
f.cubic_interp = true
|
||||
f.add_child(car)
|
||||
_track.path.add_child(f)
|
||||
followers.append(f)
|
||||
cars.append(car)
|
||||
_sync_followers()
|
||||
refresh_cargo_visuals()
|
||||
consist_changed.emit()
|
||||
|
||||
func total_length() -> float:
|
||||
if cars.is_empty():
|
||||
return 0.0
|
||||
var last = cars[cars.size() - 1]
|
||||
return abs(last.center_s - last.length * 0.5)
|
||||
|
||||
## Begehbarer Bereich als [hinten, vorne] in Zug-Laengskoordinaten.
|
||||
func walk_range() -> Vector2:
|
||||
if cars.is_empty():
|
||||
return Vector2.ZERO
|
||||
var front = cars[0]
|
||||
var back = cars[cars.size() - 1]
|
||||
return Vector2(back.center_s + back.inner_z0 + 0.35, front.center_s + front.inner_z1 - 0.35)
|
||||
|
||||
## Findet den Waggon zu einer Laengsposition.
|
||||
func locate(s: float) -> Dictionary:
|
||||
var best := 0
|
||||
var best_d := INF
|
||||
for i in cars.size():
|
||||
var c = cars[i]
|
||||
var lo: float = c.center_s + c.inner_z0
|
||||
var hi: float = c.center_s + c.inner_z1
|
||||
if s >= lo and s <= hi:
|
||||
return {"index": i, "local_z": s - c.center_s, "gangway": false}
|
||||
var d: float = min(absf(s - lo), absf(s - hi))
|
||||
if d < best_d:
|
||||
best_d = d
|
||||
best = i
|
||||
var c2 = cars[best]
|
||||
return {"index": best, "local_z": s - c2.center_s, "gangway": true}
|
||||
|
||||
func car_transform(i: int) -> Transform3D:
|
||||
return followers[i].global_transform
|
||||
|
||||
## Weltposition eines Punktes im Zug (x quer, y hoch, s laengs).
|
||||
func world_point(s: float, x: float, y: float) -> Transform3D:
|
||||
var loc := locate(s)
|
||||
var xf: Transform3D = car_transform(int(loc["index"]))
|
||||
return xf.translated_local(Vector3(x, y, float(loc["local_z"])))
|
||||
|
||||
func _sync_followers() -> void:
|
||||
var l: float = _track.length()
|
||||
for i in cars.size():
|
||||
followers[i].progress = fposmod(progress + cars[i].center_s, l)
|
||||
|
||||
# --- Ladung ---------------------------------------------------------------
|
||||
|
||||
func refresh_cargo_visuals() -> void:
|
||||
var units: Array[int] = []
|
||||
for t in cargo.keys():
|
||||
for i in int(cargo[t]):
|
||||
units.append(int(t))
|
||||
var idx := 0
|
||||
for c in cars:
|
||||
if c.capacity <= 0:
|
||||
continue
|
||||
var take: int = mini(c.capacity, units.size() - idx)
|
||||
if take <= 0:
|
||||
c.show_cargo(0, Color.WHITE)
|
||||
continue
|
||||
c.show_cargo(take, Cargo.COLORS[units[idx]])
|
||||
idx += take
|
||||
|
||||
func add_cargo(t: int, amount: int) -> int:
|
||||
var free: int = cargo_capacity() - total_cargo()
|
||||
var take: int = mini(free, amount)
|
||||
if take > 0:
|
||||
cargo[t] = int(cargo.get(t, 0)) + take
|
||||
refresh_cargo_visuals()
|
||||
return take
|
||||
|
||||
func remove_cargo(t: int, amount: int) -> int:
|
||||
var have: int = int(cargo.get(t, 0))
|
||||
var take: int = mini(have, amount)
|
||||
if take > 0:
|
||||
cargo[t] = have - take
|
||||
if cargo[t] <= 0:
|
||||
cargo.erase(t)
|
||||
refresh_cargo_visuals()
|
||||
return take
|
||||
|
||||
# --- Fahrt ----------------------------------------------------------------
|
||||
|
||||
func drive(delta: float) -> void:
|
||||
if braking:
|
||||
speed = maxf(speed - brake_power() * delta, 0.0)
|
||||
elif throttle > 0.0:
|
||||
speed = minf(speed + accel() * throttle * delta, max_speed())
|
||||
else:
|
||||
speed = maxf(speed - DRAG * delta, 0.0)
|
||||
progress = fposmod(progress + speed * delta, _track.length())
|
||||
_sync_followers()
|
||||
_update_dock(delta)
|
||||
|
||||
## Nur Anzeige aktualisieren (Clients im Netzspiel).
|
||||
func apply_snapshot(p: float, v: float) -> void:
|
||||
progress = p
|
||||
speed = v
|
||||
_sync_followers()
|
||||
_update_dock(0.0)
|
||||
|
||||
func _update_dock(delta: float) -> void:
|
||||
var near := nearest_station()
|
||||
var st: Node = near[0]
|
||||
var dist: float = near[1]
|
||||
if st == null:
|
||||
return
|
||||
if speed > 0.15 or dist > DOCK_RANGE:
|
||||
_stand_time = 0.0
|
||||
docked_at = null
|
||||
return
|
||||
_stand_time += delta
|
||||
docked_at = st
|
||||
|
||||
## Abstand eines Bahnhofs zum Zug. 0, solange der Bahnsteig neben dem
|
||||
## Zug liegt - sonst waere ein langer Zug nie richtig am Bahnsteig.
|
||||
func station_gap(st) -> float:
|
||||
var l: float = _track.length()
|
||||
var behind: float = fposmod(progress - st.progress, l)
|
||||
var len_train: float = total_length()
|
||||
if behind <= len_train:
|
||||
return 0.0
|
||||
return minf(behind - len_train, l - behind)
|
||||
|
||||
func nearest_station() -> Array:
|
||||
var best: Node = null
|
||||
var best_d := INF
|
||||
for s in stations:
|
||||
var d: float = station_gap(s)
|
||||
if d < best_d:
|
||||
best_d = d
|
||||
best = s
|
||||
return [best, best_d]
|
||||
|
||||
func next_station() -> Array:
|
||||
var best: Node = null
|
||||
var best_d := INF
|
||||
var l: float = _track.length()
|
||||
for s in stations:
|
||||
var d: float = fposmod(s.progress - progress, l)
|
||||
if d < best_d:
|
||||
best_d = d
|
||||
best = s
|
||||
return [best, best_d]
|
||||
Reference in New Issue
Block a user