Skip to main content

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}