lotus_shared/vehicle.rs
1//! Fahrzeugphysik-Handles, Schienen-/Straßeneigenschaften und Zugbildungsnachrichten.
2//!
3//! Vehicle physics handles, rail/road properties, and train composition messages.
4
5use serde::{Deserialize, Serialize};
6use serde_repr::{Deserialize_repr, Serialize_repr};
7
8use crate::message::{Coupling, MessageMeta, MessageType};
9
10/// Fehler beim Auflösen von Fahrzeugkomponenten.
11///
12/// Errors returned when resolving vehicle components.
13#[derive(Debug, thiserror::Error)]
14pub enum VehicleError {
15 /// Das Fahrzeug wurde nicht gefunden.
16 ///
17 /// The vehicle was not found.
18 #[error("vehicle not found")]
19 VehicleNotFound = 256,
20 /// Das Drehgestell wurde nicht gefunden.
21 ///
22 /// The bogie was not found.
23 #[error("bogie not found")]
24 BogieNotFound = 512,
25 /// Die Achse wurde nicht gefunden.
26 ///
27 /// The axle was not found.
28 #[error("axle not found")]
29 AxleNotFound = 1024,
30 /// Die Kupplung wurde nicht gefunden.
31 ///
32 /// The coupling was not found.
33 #[error("coupling not found")]
34 CouplingNotFound = 2048,
35 /// Der Stromabnehmer wurde nicht gefunden.
36 ///
37 /// The pantograph was not found.
38 #[error("pantograph not found")]
39 PantographNotFound = 4096,
40 /// Die Straßenachse wurde nicht gefunden.
41 ///
42 /// The road axle was not found.
43 #[error("road axle not found")]
44 RoadAxleNotFound = 8192,
45 /// Das Straßenrad wurde nicht gefunden.
46 ///
47 /// The road wheel was not found.
48 #[error("road wheel not found")]
49 RoadWheelNotFound = 16384,
50 /// Ein unbekannter Fehler ist aufgetreten.
51 ///
52 /// An unknown error occurred.
53 #[error("unknown error")]
54 Unknown = 0,
55}
56
57impl From<u32> for VehicleError {
58 fn from(value: u32) -> Self {
59 match value {
60 256 => VehicleError::VehicleNotFound,
61 512 => VehicleError::BogieNotFound,
62 1024 => VehicleError::AxleNotFound,
63 2048 => VehicleError::CouplingNotFound,
64 4096 => VehicleError::PantographNotFound,
65 8192 => VehicleError::RoadAxleNotFound,
66 16384 => VehicleError::RoadWheelNotFound,
67 _ => VehicleError::Unknown,
68 }
69 }
70}
71
72#[cfg(feature = "ffi")]
73/// Gibt `true` zurück, wenn das Fahrzeug zur Zugbildung invertiert gespawnt wurde.
74///
75/// Returns `true` if the vehicle was spawned inverted to the train.
76pub fn spawned_inverted_to_train() -> bool {
77 unsafe { lotus_script_sys::vehicle::spawned_inverted_to_train() == 1 }
78}
79
80/// Spawn-Snapshot eines Fahrzeugs innerhalb einer Zugbildung.
81///
82/// Initial spawn snapshot of one vehicle within a train composition.
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub struct TrainVehicleConfiguration {
85 /// Fahrzeugnummer bzw. Kennzeichnung als Text.
86 ///
87 /// Vehicle number or identifier string.
88 pub number: String,
89 /// Ob das Fahrzeug relativ zur Zugfahrtrichtung gedreht ist.
90 ///
91 /// Whether the vehicle is reversed relative to the train direction.
92 pub reversed_to_train: bool,
93}
94
95/// Geordnete Liste der Fahrzeuge in einem Zug zum Spawn-Zeitpunkt.
96///
97/// Ordered list of vehicles in a train at spawn time.
98pub type TrainConfiguration = Vec<TrainVehicleConfiguration>;
99
100/// Ereignis bei Änderung der Zugbildung.
101///
102/// Hinweis: Werden Züge mit unterschiedlicher Fahrtrichtung gekuppelt, ist die neue Richtung nicht vorhersagbar.
103/// In Fahrzeugen, deren Richtung beim Kuppeln invertiert wird, kehrt sich `reversed_to_train` um und die Indexreihenfolge dreht sich um.
104///
105/// Describes an event that is sent when the train configuration is changed.
106///
107/// Please note: When two trains with different directions are coupled,
108/// the new direction cannot be predicted!
109/// In the vehicles of the train whose direction is inverted when coupling,
110/// "reversed_to_train" is inverted and the index order reverses.
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
112pub struct TrainConfigurationChanged {
113 /// Entity-ID des betroffenen Fahrzeugs.
114 ///
115 /// Entity id of the affected vehicle.
116 pub entity_id: u64,
117 /// Ob dieses Fahrzeug relativ zum Zug gedreht ist.
118 ///
119 /// Whether this vehicle is reversed relative to the train.
120 pub reversed_to_train: bool,
121 /// Nullbasierter Index dieses Fahrzeugs im Zug.
122 ///
123 /// Zero-based index of this vehicle in the train.
124 pub index_in_train: usize,
125 /// Gesamtzahl der Fahrzeuge im Zug.
126 ///
127 /// Total number of vehicles in the train.
128 pub train_vehicle_count: usize,
129 /// Vollständige Zugbildung beim initialen Spawn; `None` bei späteren Updates (z. B. Script-Reload).
130 ///
131 /// Full train composition at initial spawn; `None` for later updates (e.g. script reload).
132 #[serde(default)]
133 pub train_configuration: Option<TrainConfiguration>,
134}
135
136impl MessageType for TrainConfigurationChanged {
137 const MESSAGE_META: MessageMeta = MessageMeta::new("builtin", "vehicle_in_train_changed", None);
138}
139
140/// Berechnung der Fahrzeuganzahl vor oder hinter diesem Fahrzeug.
141///
142/// Calculation of the vehicle count in front or behind the vehicle
143/// relative to the vehicle.
144impl TrainConfigurationChanged {
145 /// Gibt die Anzahl der Fahrzeuge in der angegebenen Richtung ab diesem Fahrzeug zurück.
146 ///
147 /// Returns the number of vehicles in the given direction from this vehicle.
148 pub fn neighbour_vehicle_count(&self, coupling: Coupling) -> usize {
149 if (coupling == Coupling::Rear) ^ self.reversed_to_train {
150 self.train_vehicle_count - self.index_in_train - 1
151 } else {
152 self.index_in_train
153 }
154 }
155}
156
157/// Handle auf ein Schienenfahrzeug-Drehgestell.
158///
159/// Handle to a rail vehicle bogie.
160#[cfg(feature = "ffi")]
161#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
162pub struct Bogie {
163 index: usize,
164}
165
166#[cfg(feature = "ffi")]
167impl Bogie {
168 /// Gibt das Drehgestell am angegebenen nullbasierten Index zurück.
169 ///
170 /// Returns the bogie at the given zero-based index.
171 pub fn get(index: usize) -> Result<Self, VehicleError> {
172 match unsafe { lotus_script_sys::vehicle::bogie_is_valid(index as u32) } {
173 0 => Ok(Self { index }),
174 e => Err(e.into()),
175 }
176 }
177
178 /// Setzt die Schienenbremskraft am Drehgestell in Newton.
179 ///
180 /// Die Schienenbremse besteht aus Elektromagneten, die an die Schiene angelegt werden.
181 /// Sie gleiten mit hoher Reibung über die Schiene und ermöglichen deutlich stärkere Bremsung:
182 /// Während die normale Radbremse nur mit der Achslast Haftreibung aufbauen kann,
183 /// kann die Schienenbremse wesentlich höhere Reib- und Bremskräfte ausüben,
184 /// relativ unabhängig vom Schienenzustand (Feuchtigkeit und Schmutz werden faktisch „weggeschliffen“).
185 ///
186 /// Sets the rail brake force at the given bogie.
187 /// The rail brake consists of electromagnets that are set against the rail.
188 /// They then slide over the rail with high friction, which allows the vehicle to be braked much more strongly:
189 /// While the normal wheel brake only has the axle load available to build up a frictional grip with the rail,
190 /// the rail brake can exert much higher frictional forces and thus braking forces,
191 /// even relatively independent of the rail condition (moisture and dirt are effectively "ground off").
192 pub fn set_rail_brake_force_newton(self, value: f32) {
193 unsafe { lotus_script_sys::vehicle::set_rail_brake_force_newton(self.index as u32, value) };
194 }
195}
196
197/// Handle auf eine Schienenfahrzeug-Achse an einem Drehgestell.
198///
199/// Handle to a rail vehicle axle on a bogie.
200#[cfg(feature = "ffi")]
201#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
202pub struct Axle {
203 bogie_index: usize,
204 axle_index: usize,
205}
206
207#[cfg(feature = "ffi")]
208impl Axle {
209 /// Gibt die Achse an den angegebenen Drehgestell- und Achsenindizes zurück.
210 ///
211 /// Returns the axle at the given bogie and axle indices.
212 pub fn get(bogie_index: usize, axle_index: usize) -> Result<Self, VehicleError> {
213 match unsafe {
214 lotus_script_sys::vehicle::axle_is_valid(bogie_index as u32, axle_index as u32)
215 } {
216 0 => Ok(Self {
217 bogie_index,
218 axle_index,
219 }),
220 e => Err(e.into()),
221 }
222 }
223
224 /// Gibt den Script-Variablennamen mit der Achsgeschwindigkeit in m/s zurück.
225 ///
226 /// Returns the script variable name containing this axle's velocity in m/s.
227 pub fn velocity_var_name(self) -> String {
228 format!("v_Axle_mps_{}_{}", self.bogie_index, self.axle_index)
229 }
230
231 /// Gibt das Drehgestell dieser Achse zurück.
232 ///
233 /// Returns the bogie this axle belongs to.
234 pub fn bogie(self) -> Bogie {
235 Bogie {
236 index: self.bogie_index,
237 }
238 }
239
240 /// Gibt den nullbasierten Achsenindex am Drehgestell zurück.
241 ///
242 /// Returns the zero-based axle index on the bogie.
243 pub fn axle_index(self) -> usize {
244 self.axle_index
245 }
246
247 /// Gibt den nullbasierten Drehgestellindex zurück.
248 ///
249 /// Returns the zero-based bogie index.
250 pub fn bogie_index(self) -> usize {
251 self.bogie_index
252 }
253
254 /// Gibt die Gleiskrümmung unter der Achse zurück (1/R).
255 ///
256 /// Die Krümmung ist der Kehrwert des Radius (1/R); in Geraden tendiert der Wert gegen 0 statt gegen Unendlichkeit.
257 /// Die Werte sind daher sehr klein: Schon ein Radius von 100 m ergibt 0,01, größere Radien noch kleinere Werte.
258 /// Positiv = rechts, negativ = links.
259 ///
260 /// Gets the curvature of the track under the given axis.
261 /// The curvature is the reciprocal of the radius (1/R), which has the advantage that the value does not tend to infinity in a straight line, but tends to 0.
262 /// The values are very small due to this calculation: Even a radius of only 100m leads to a value of 0.01, larger radii lead to even smaller values.
263 /// Positive = right, negative = left.
264 pub fn inverse_radius(self) -> f32 {
265 let inverse_radius = unsafe {
266 lotus_script_sys::vehicle::inverse_radius(
267 self.bogie_index as u32,
268 self.axle_index as u32,
269 )
270 };
271 assert!(!inverse_radius.is_nan());
272 assert_ne!(inverse_radius, f32::NEG_INFINITY);
273 assert_ne!(inverse_radius, f32::INFINITY);
274 inverse_radius
275 }
276
277 /// Gibt die Oberflächenart unter der Achse zurück.
278 ///
279 /// Provides the type of the surface under the given axis.
280 pub fn surface_type(self) -> SurfaceType {
281 let surface_type = unsafe {
282 lotus_script_sys::vehicle::surface_type(self.bogie_index as u32, self.axle_index as u32)
283 };
284 SurfaceType::try_from(surface_type).unwrap()
285 }
286
287 /// Gibt die Schienenqualität unter der Achse zurück.
288 ///
289 /// Provides the quality of the rails under the given axis.
290 pub fn rail_quality(self) -> RailQuality {
291 let quality = unsafe {
292 lotus_script_sys::vehicle::rail_quality(self.bogie_index as u32, self.axle_index as u32)
293 };
294 RailQuality::try_from(quality).unwrap()
295 }
296
297 /// Setzt die Traktionskraft in Newton.
298 ///
299 /// Dies ist das auf die Achse wirkende Drehmoment, bereits umgerechnet in die Kraft auf der Lauffläche.
300 /// Solange das Rad nicht schlupft, entspricht der Wert der Kraft des Rades auf die Schiene.
301 /// Die Kraft wirkt unabhängig von der Fahrtrichtung; entgegen der Fahrt bremst sie, hält das Fahrzeug aber nicht still.
302 ///
303 /// Sets the traction force in newton.
304 /// This is the torque applied to the axle, already converted to the force acting on the running surface. This means that as long as the wheel does not slip or spin, this value is equal to the force exerted by the wheel on the rail.
305 /// This force acts independently of the direction of travel. If it acts in the opposite direction to the travel, the vehicle will be braked, but it cannot hold the vehicle stationary.
306 pub fn set_traction_force_newton(self, value: f32) {
307 unsafe {
308 lotus_script_sys::vehicle::set_traction_force_newton(
309 self.bogie_index as u32,
310 self.axle_index as u32,
311 value,
312 )
313 };
314 }
315
316 /// Setzt die Bremskraft in Newton.
317 ///
318 /// Drehmoment auf der Achse, umgerechnet in Laufflächenkraft; ohne Schlupf entspricht das der Schienenkraft.
319 /// Im Unterschied zu `set_traction_force_newton` ist die Bremskraft immer positiv und wirkt immer entgegen der Fahrtrichtung.
320 /// Damit kann sie das Fahrzeug wie eine Scheibenbremse auch im Stand halten.
321 ///
322 /// Sets the brake force in newton.
323 /// This is the torque applied to the axle, already converted to the force acting on the running surface.
324 /// This means that as long as the wheel does not slip or spin, this value is equal to the force exerted by the wheel on the rail.
325 /// The difference to "traction_force_newton" is that brake_force_newton is always positive and always acts in the opposite direction to the travel.
326 /// This means that brake_force_newton can also hold the vehicle stationary like a disc brake.
327 pub fn set_brake_force_newton(self, value: f32) {
328 unsafe {
329 lotus_script_sys::vehicle::set_brake_force_newton(
330 self.bogie_index as u32,
331 self.axle_index as u32,
332 value,
333 )
334 };
335 }
336}
337
338/// Handle auf eine Straßenfahrzeug-Achse.
339///
340/// Handle to a road vehicle axle.
341#[cfg(feature = "ffi")]
342#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
343pub struct RoadAxle {
344 index: usize,
345}
346
347#[cfg(feature = "ffi")]
348impl RoadAxle {
349 /// Gibt die Straßenachse am angegebenen nullbasierten Index zurück.
350 ///
351 /// Returns the road axle at the given zero-based index.
352 pub fn get(index: usize) -> Result<Self, VehicleError> {
353 match unsafe { lotus_script_sys::vehicle::road_axle_is_valid(index as u32) } {
354 0 => Ok(Self { index }),
355 e => Err(e.into()),
356 }
357 }
358}
359
360/// Handle auf ein Straßenfahrzeug-Rad an einer Achse.
361///
362/// Handle to a road vehicle wheel on an axle.
363#[cfg(feature = "ffi")]
364#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
365pub struct RoadWheel {
366 axle_index: usize,
367 wheel_index: usize,
368}
369
370#[cfg(feature = "ffi")]
371impl RoadWheel {
372 /// Gibt das Rad an den angegebenen Achsen- und Radindizes zurück.
373 ///
374 /// Returns the wheel at the given axle and wheel indices.
375 pub fn get(axle_index: usize, wheel_index: usize) -> Result<Self, VehicleError> {
376 match unsafe {
377 lotus_script_sys::vehicle::road_wheel_is_valid(axle_index as u32, wheel_index as u32)
378 } {
379 0 => Ok(Self {
380 axle_index,
381 wheel_index,
382 }),
383 e => Err(e.into()),
384 }
385 }
386
387 /// Gibt den Script-Variablennamen mit der Radgeschwindigkeit in m/s zurück.
388 ///
389 /// Returns the script variable name containing this wheel's velocity in m/s.
390 pub fn velocity_var_name(self) -> String {
391 format!("v_wheel_mps_{}_{}", self.axle_index, self.wheel_index)
392 }
393
394 /// Gibt den nullbasierten Radindex an der Achse zurück.
395 ///
396 /// Returns the zero-based wheel index on the axle.
397 pub fn wheel_index(self) -> usize {
398 self.wheel_index
399 }
400
401 /// Gibt den nullbasierten Achsenindex zurück.
402 ///
403 /// Returns the zero-based axle index.
404 pub fn axle_index(self) -> usize {
405 self.axle_index
406 }
407
408 /// Setzt die Traktionskraft an der Lauffläche in Newton.
409 ///
410 /// Sets the traction force at the running surface in newton.
411 pub fn set_traction_force_newton(self, value: f32) {
412 unsafe {
413 lotus_script_sys::vehicle::set_wheel_traction_force_newton(
414 self.axle_index as u32,
415 self.wheel_index as u32,
416 value,
417 )
418 };
419 }
420
421 /// Setzt die Bremskraft an der Lauffläche in Newton.
422 ///
423 /// Sets the brake force at the running surface in newton.
424 pub fn set_brake_force_newton(self, value: f32) {
425 unsafe {
426 lotus_script_sys::vehicle::set_wheel_brake_force_newton(
427 self.axle_index as u32,
428 self.wheel_index as u32,
429 value,
430 )
431 };
432 }
433
434 /// Setzt den Faktor zur Manipulation der Federsteifigkeit.
435 ///
436 /// Sets the factor, which manipulates the spring stiffness.
437 pub fn set_spring_factor(self, value: f32) {
438 unsafe {
439 lotus_script_sys::vehicle::set_wheel_spring_factor(
440 self.axle_index as u32,
441 self.wheel_index as u32,
442 value,
443 )
444 };
445 }
446}
447
448/// Handle auf einen Stromabnehmer am aktuellen Fahrzeug.
449///
450/// Handle to a pantograph on the current vehicle.
451#[cfg(feature = "ffi")]
452#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
453pub struct Pantograph {
454 index: usize,
455}
456
457#[cfg(feature = "ffi")]
458impl Pantograph {
459 /// Gibt den Stromabnehmer am angegebenen nullbasierten Index zurück.
460 ///
461 /// Returns the pantograph at the given zero-based index.
462 pub fn get(index: usize) -> Result<Self, VehicleError> {
463 match unsafe { lotus_script_sys::vehicle::pantograph_is_valid(index as u32) } {
464 0 => Ok(Self { index }),
465 e => Err(e.into()),
466 }
467 }
468
469 /// Gibt die Höhe des tiefsten Fahrdrads über der Stromabnehmer-Position zurück.
470 ///
471 /// Returns the height of the lowest contact wire above the pantograph position.
472 pub fn height(self) -> f32 {
473 let height = unsafe { lotus_script_sys::vehicle::pantograph_height(self.index as u32) };
474 assert!(!height.is_nan());
475 assert_ne!(height, f32::INFINITY);
476 height
477 }
478
479 /// Spannung der Fahrleitung über dem Stromabnehmer (normalisiert; 1.0 = Sollspannung).
480 /// Das Script muss selbst prüfen, ob der Stromabnehmer die Leitung berührt.
481 ///
482 /// The voltage of the contact wire above the pantograph. The value is normalized, i.e. 1.0 means that the target voltage is present.
483 /// However, the script itself must check whether the pantograph is touching the contact wire.
484 pub fn voltage(self) -> f32 {
485 let voltage = unsafe { lotus_script_sys::vehicle::pantograph_voltage(self.index as u32) };
486 assert!(!voltage.is_nan());
487 assert_ne!(voltage, f32::INFINITY);
488 voltage
489 }
490}
491
492/// Beschreibt die Schienenqualität unter der angegebenen Achse.
493///
494/// Provides the quality of the rails under the given axis.
495#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
496#[repr(u8)]
497pub enum RailQuality {
498 Smooth = 0,
499 Rough = 1,
500 FroggySmooth = 2,
501 FroggyRough = 3,
502 FlatGroove = 4,
503 HighSpeedSmooth = 5,
504 SmoothDirt = 6,
505 RoughDirt = 7,
506 Deraileur = 8,
507}
508
509impl TryFrom<u32> for RailQuality {
510 type Error = VehicleError;
511
512 fn try_from(value: u32) -> Result<Self, Self::Error> {
513 match value {
514 0 => Ok(RailQuality::Smooth),
515 1 => Ok(RailQuality::Rough),
516 2 => Ok(RailQuality::FroggySmooth),
517 3 => Ok(RailQuality::FroggyRough),
518 4 => Ok(RailQuality::FlatGroove),
519 5 => Ok(RailQuality::HighSpeedSmooth),
520 6 => Ok(RailQuality::SmoothDirt),
521 7 => Ok(RailQuality::RoughDirt),
522 8 => Ok(RailQuality::Deraileur),
523 value => Err(value.into()),
524 }
525 }
526}
527
528/// Art der Oberfläche unter der angegebenen Achse.
529///
530/// Type of the surface under the given axis.
531#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
532#[repr(u8)]
533pub enum SurfaceType {
534 Gravel = 0,
535 Street = 1,
536 Grass = 2,
537}
538
539impl TryFrom<u32> for SurfaceType {
540 type Error = VehicleError;
541
542 fn try_from(value: u32) -> Result<Self, Self::Error> {
543 match value {
544 0 => Ok(SurfaceType::Gravel),
545 1 => Ok(SurfaceType::Street),
546 2 => Ok(SurfaceType::Grass),
547 value => Err(value.into()),
548 }
549 }
550}
551
552/// Parameter zum Verändern von Feder- und Dämpferverhalten der Straßenlenkung.
553///
554/// Parameters for manipulating road steering spring and damper behavior.
555#[derive(Clone, Copy)]
556pub struct RoadSteeringSpringDamperManipulator {
557 /// Steifigkeits-Additiv zum Standardwert.
558 ///
559 /// The stiffness is added to the default stiffness.
560 pub stiffness_add: f32,
561 /// Steifigkeits-Multiplikator auf den Standardwert.
562 ///
563 /// The stiffness is multiplied by the default stiffness.
564 pub stiffness_mult: f32,
565 /// Dämpfungs-Additiv zum Standardwert.
566 ///
567 /// The damping is added to the default damping.
568 pub damping_add: f32,
569 /// Dämpfungs-Multiplikator auf den Standardwert.
570 ///
571 /// The damping is multiplied by the default damping.
572 pub damping_mult: f32,
573}
574
575impl Default for RoadSteeringSpringDamperManipulator {
576 fn default() -> Self {
577 Self {
578 stiffness_add: 0.0,
579 stiffness_mult: 1.0,
580 damping_add: 0.0,
581 damping_mult: 1.0,
582 }
583 }
584}
585
586impl RoadSteeringSpringDamperManipulator {
587 /// Erstellt einen neuen Satz von Feder-/Dämpfer-Manipulationswerten.
588 ///
589 /// Creates a new spring/damper manipulation set.
590 pub fn new(
591 stiffness_add: f32,
592 stiffness_mult: f32,
593 damping_add: f32,
594 damping_mult: f32,
595 ) -> Self {
596 Self {
597 stiffness_add,
598 stiffness_mult,
599 damping_add,
600 damping_mult,
601 }
602 }
603}