Skip to main content

lotus_shared/
pis.rs

1//! Fahrgastinformationssystem (PIS): Daten und Abfragefunktionen.
2//!
3//! Passenger information system (PIS) data and query functions.
4
5use serde::{Deserialize, Serialize};
6
7use crate::content::ContentId;
8
9/// Namespace zum Abfragen der aktiven PIS-Gruppe aus dem Simulator.
10///
11/// Scripts konstruieren diesen Typ nicht manuell; die zugehörigen Funktionen
12/// (z. B. [`PisGroup::get_all_stations`]) lesen PIS-Daten zur Laufzeit.
13///
14/// Namespace for querying the active PIS group from the simulator.
15///
16/// Scripts do not construct this type manually; call the associated functions
17/// (e.g. [`PisGroup::get_all_stations`]) to read PIS data at runtime.
18#[derive(Clone, PartialEq, Eq, Debug)]
19pub struct PisGroup {
20    /// Name der PIS-Gruppe.
21    ///
22    /// Name of the PIS group.
23    pub name: String,
24    /// In den Gruppendaten enthaltene Stationen.
25    ///
26    /// Stations contained in the group data set.
27    pub stations: Vec<PisStation>,
28    /// In der Gruppe definierte Sonderzeichen.
29    ///
30    /// Special characters defined in the group.
31    pub special_chars: Vec<PisSpecialChar>,
32    /// In der Gruppe definierte Routen.
33    ///
34    /// Routes defined in the group.
35    pub routes: Vec<PisRoute>,
36    /// Optionaler Name des Leitstellen-Servers.
37    ///
38    /// Optional dispatch server name.
39    pub server_name: Option<String>,
40}
41
42impl PisGroup {
43    /// Holt den Namen der aktiven PIS-Gruppe.
44    ///
45    /// Returns the name of the active PIS group.
46    #[cfg(feature = "ffi")]
47    pub fn get_name() -> String {
48        let name =
49            lotus_script_sys::FfiObject::from_packed(unsafe { lotus_script_sys::pis::get_name() });
50        name.deserialize()
51    }
52
53    /// Holt die Station mit dem gegebenen Code.
54    ///
55    /// Returns the station with the given code.
56    #[cfg(feature = "ffi")]
57    pub fn get_station(code: u32) -> Option<PisStation> {
58        let station = lotus_script_sys::FfiObject::from_packed(unsafe {
59            lotus_script_sys::pis::get_station(code)
60        });
61        station.deserialize()
62    }
63
64    /// Holt den aufgelösten Sonderzeichen-String für die gegebene Linie und den Code.
65    ///
66    /// Returns the resolved special-character string for the given line and code.
67    #[cfg(feature = "ffi")]
68    pub fn get_special_char_with_line(line: u32, special_char_code: u32) -> String {
69        let route_codes = lotus_script_sys::FfiObject::from_packed(unsafe {
70            lotus_script_sys::pis::get_special_char_with_line(line, special_char_code)
71        });
72        route_codes.deserialize()
73    }
74
75    /// Holt die Route mit der gegebenen Linie und dem Code.
76    ///
77    /// Returns the route for the given line and route code.
78    #[cfg(feature = "ffi")]
79    pub fn get_route(line_code: (u32, u32)) -> Option<PisRoute> {
80        let route = lotus_script_sys::FfiObject::from_packed(unsafe {
81            lotus_script_sys::pis::get_route(line_code.0, line_code.1)
82        });
83        route.deserialize()
84    }
85
86    /// Liefert eine sortierte, duplikatfreie Liste aller Routencodes für die gegebene Linie.
87    ///
88    /// Returns sorted unique route codes available for the given line.
89    #[cfg(feature = "ffi")]
90    pub fn get_route_codes_by_line(line: u32) -> Vec<u32> {
91        let route_codes = lotus_script_sys::FfiObject::from_packed(unsafe {
92            lotus_script_sys::pis::get_route_codes_by_line(line)
93        });
94        route_codes.deserialize()
95    }
96
97    /// Liefert sämtliche Stationen der aktiven PIS-Gruppe.
98    ///
99    /// Returns all stations of the active PIS group.
100    #[cfg(feature = "ffi")]
101    pub fn get_all_stations() -> Vec<PisStation> {
102        let stations = lotus_script_sys::FfiObject::from_packed(unsafe {
103            lotus_script_sys::pis::get_all_stations()
104        });
105        stations.deserialize()
106    }
107
108    /// Liefert sämtliche Routen der aktiven PIS-Gruppe.
109    ///
110    /// Returns all routes of the active PIS group.
111    #[cfg(feature = "ffi")]
112    pub fn get_all_routes() -> Vec<PisRoute> {
113        let routes = lotus_script_sys::FfiObject::from_packed(unsafe {
114            lotus_script_sys::pis::get_all_routes()
115        });
116        routes.deserialize()
117    }
118
119    /// Liefert sämtliche Sonderzeichen der aktiven PIS-Gruppe, aufgelöst für die gegebene Linie.
120    /// Jedes Tupel enthält den Sonderzeichen-Code und den aufgelösten Anzeigestring.
121    ///
122    /// Returns all special characters of the active PIS group resolved for the given line.
123    #[cfg(feature = "ffi")]
124    pub fn get_all_special_chars_with_line(line: u32) -> Vec<(u32, String)> {
125        let special_chars = lotus_script_sys::FfiObject::from_packed(unsafe {
126            lotus_script_sys::pis::get_all_special_chars_with_line(line)
127        });
128        special_chars.deserialize()
129    }
130
131    /// Holt den Namen des Leitstellen-Servers.
132    ///
133    /// Returns the name of the dispatch server, if connected.
134    #[cfg(feature = "ffi")]
135    pub fn get_server_name() -> Option<String> {
136        let server_name = lotus_script_sys::FfiObject::from_packed(unsafe {
137            lotus_script_sys::pis::get_server_name()
138        });
139        server_name.deserialize()
140    }
141}
142
143/// Datensatz für eine Station im PIS.
144///
145/// PIS station record.
146#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
147pub struct PisStation {
148    /// Mit der ID wird die Station mit der Map verknüpft (auch dort gibt es eine ID für jede Station).
149    /// Dies ist u. a. für die Fahrgäste und die öffentlichen KI-Fahrzeuge notwendig.
150    ///
151    /// Links the station to the map (each station has an id there as well).
152    /// Required e.g. for passengers and public AI vehicles.
153    pub id: String,
154    /// Der Code ist die Zahl, mit der die Station innerhalb des PIS identifiziert wird.
155    /// Dieser Code wird für die Anzeige auf dem PIS-Display verwendet.
156    ///
157    /// Numeric code identifying the station within the PIS; used on PIS displays.
158    pub code: u32,
159    /// Die Strings sind die Texte, die auf den Innenanzeigen angezeigt werden. Zwei Strings gibt es
160    /// z. B. für Wechselanzeigen.
161    ///
162    /// Interior display texts; two strings e.g. for alternating displays.
163    pub interieur_display: [String; 2],
164    /// Optionale zweizeilige Front-Außenanzeige.
165    ///
166    /// Optional two-line front exterior destination display.
167    pub terminus_front_option: Option<[String; 2]>,
168    /// Optionale zweizeilige Seiten-Zielanzeige.
169    ///
170    /// Optional two-line side destination display strings.
171    pub terminus_side_option: Option<[String; 2]>,
172    /// Einzeiliger Außenzieltext.
173    ///
174    /// Single-line exterior destination text.
175    pub terminus_oneline: String,
176}
177
178/// Platzierung eines einzeiligen Ziels beim Erweitern auf zwei Zeilen.
179///
180/// Placement of a one-line destination when expanding to two lines.
181pub enum PisStationTerminusOneLineTo {
182    /// Einzeiligen Text in die erste Zeile setzen.
183    ///
184    /// Put the one-line text on the first line.
185    FirstLine,
186    /// Einzeiligen Text in die zweite Zeile setzen.
187    ///
188    /// Put the one-line text on the second line.
189    SecondLine,
190}
191
192impl PisStation {
193    /// Liefert die (zweizeilige) Front-Außenanzeige für das Ziel.
194    /// Verfügt die Ziel-Station über keine zweizeilige Front-Außenanzeige, so wird die einzeilige
195    /// Außenanzeige um eine Leerzeile erweitert, wobei `one_to_two_line` die Reihenfolge dieser
196    /// Erweiterung bestimmt:
197    /// - `FirstLine`: Die einzeilige Zeile wird in die erste Zeile und die Leerzeile in die zweite Zeile eingefügt
198    /// - `SecondLine`: Die einzeilige Zeile wird in die zweite Zeile und die Leerzeile in die erste Zeile eingefügt
199    ///
200    /// Returns the (two-line) front exterior destination display.
201    /// If no two-line front display is defined, the one-line text is expanded with a blank line;
202    /// `one_to_two_line` controls the order:
203    /// - `FirstLine`: one-line text on the first line, blank on the second
204    /// - `SecondLine`: blank on the first line, one-line text on the second
205    pub fn terminus_front(&self, one_to_two_line: PisStationTerminusOneLineTo) -> [String; 2] {
206        if let Some(front) = &self.terminus_front_option {
207            front.clone()
208        } else {
209            match one_to_two_line {
210                PisStationTerminusOneLineTo::FirstLine => {
211                    [self.terminus_oneline.clone(), String::new()]
212                }
213                PisStationTerminusOneLineTo::SecondLine => {
214                    [String::new(), self.terminus_oneline.clone()]
215                }
216            }
217        }
218    }
219
220    /// Gibt die zweizeilige Seiten-Zielanzeige dieser Station zurück.
221    ///
222    /// Returns the two-line side destination display for this station.
223    pub fn terminus_side(&self, one_to_two_line: PisStationTerminusOneLineTo) -> [String; 2] {
224        if let Some(side) = &self.terminus_side_option {
225            side.clone()
226        } else {
227            self.terminus_front(one_to_two_line)
228        }
229    }
230
231    /// Gibt zurück, ob der Stationscode ein öffentlicher PIS-Code ist.
232    ///
233    /// Returns whether the station code is a public PIS code.
234    pub fn code_is_public(&self) -> bool {
235        self.code < 1_000_000
236    }
237}
238
239/// Datensatz für ein Sonderzeichen im PIS.
240///
241/// PIS special-character record.
242#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
243pub struct PisSpecialChar {
244    /// Der Code ist die Zahl, mit der das Sonderzeichen innerhalb des PIS identifiziert wird.
245    ///
246    /// Numeric code identifying the special character within the PIS.
247    pub code: u32,
248    /// Der Sonderzeichen-String, wobei dieser auch über bestimmte Codes verfügen kann, mit denen
249    /// die originale Liniennummer eingefügt werden kann.
250    ///
251    /// So bedeutet z. B. „M(R2-R1)“, dass auf dem Linienfeld ein M, gefolgt von
252    /// den Ziffern 2 bis 1 von rechts gezählt, angezeigt wird. Wenn die Liniennummer
253    /// z. B. 123 ist, wird M23 angezeigt.
254    /// Soll nur M2 angezeigt werden, müsste man „M(R2-R2)“ als `chars` eintragen.
255    ///
256    /// Special-character string; may contain codes that insert the original line number.
257    ///
258    /// For example, `M(R2-R1)` displays M followed by digits 2 down to 1 counted from the right.
259    /// For line number 123 this shows M23; for M2 only use `M(R2-R2)` as `chars`.
260    pub chars: String, // STRN
261}
262
263/// Datensatz für eine Route im PIS. Jeder Datensatz wird über eine
264/// Liniennummer und einen Code innerhalb der Linie identifiziert.
265///
266/// PIS route record identified by line number and in-line route code.
267#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
268pub struct PisRoute {
269    /// Liniennummer und Code innerhalb der Linie zur Zuordnung.
270    ///
271    /// Line number and in-line route code for identification.
272    pub line_code: (u32, u32),
273    /// Liste der Codes der Haltestellen, die auf der Route nacheinander
274    /// angefahren werden, inklusive der Abfahrts- und der Endhaltestelle.
275    ///
276    /// Stop codes visited in order, including departure and destination stops.
277    pub stop_codes: Vec<u32>,
278    /// Code des Sonderzeichens, das automatisch ausgewählt werden soll,
279    /// wenn diese Route eingestellt wird. Ob dieser Code überschrieben werden kann, ist abhängig vom Bordrechner.
280    ///
281    /// Special-character code selected automatically when this route is set; overridability depends on the onboard computer.
282    pub special_char_code: Option<u32>,
283    /// Sind für das Balisensystem/Anforderungen/Weichen individuelle Anmelde-Codes nötig?
284    ///
285    /// Optional routing/signalling code for balise systems, requests, or turnouts.
286    pub routing_code: Option<u32>,
287    /// Zusätzliches Textfeld ohne fest definierte Bedeutung.
288    ///
289    /// Additional text field with no fixed meaning currently.
290    pub text: Option<String>,
291    /// Im einfachsten Fall ist das Ziel einer Route die letzte Haltestelle.
292    /// Ab einer bestimmten Haltestelle kann das angezeigte Ziel wechseln oder anders dargestellt werden.
293    /// Dafür können Termini definiert werden.
294    ///
295    /// Usually the destination is the last stop; termini override the displayed destination from specific stops onward.
296    pub termini: Vec<PisRouteTerminus>,
297    /// Linie und Code der automatisch zu wählenden Folgeroute.
298    ///
299    /// Line and code of the route to select automatically next.
300    pub following_line_code: Option<(u32, u32)>,
301}
302
303impl PisRoute {
304    /// Erstellt einen neuen PIS-Routendatensatz.
305    ///
306    /// Creates a new PIS route record.
307    pub fn new(
308        line_code: (u32, u32),
309        stop_codes: Vec<u32>,
310        special_char_code: Option<u32>,
311        routing_code: Option<u32>,
312        text: Option<String>,
313        termini: Vec<PisRouteTerminus>,
314        following_line_code: Option<(u32, u32)>,
315    ) -> Self {
316        Self {
317            line_code,
318            stop_codes,
319            special_char_code,
320            routing_code,
321            text,
322            termini,
323            following_line_code,
324        }
325    }
326    /// Gibt das aktive Routenziel ab dem aktuellen Haltestellenindex zurück.
327    ///
328    /// Returns the active route terminus from the current stop index onward.
329    pub fn get_current_direction(&self, stop_index: usize) -> Option<PisRouteTerminus> {
330        self.termini
331            .iter()
332            .filter(|terminus| stop_index >= terminus.stop_index)
333            .max_by_key(|terminus| terminus.stop_index)
334            .cloned()
335            .or_else(|| {
336                self.stop_codes.last().map(|code| PisRouteTerminus {
337                    stop_index: 0,
338                    code: Some(*code),
339                    line: Some(self.line_code.0),
340                    special_char_code: self.special_char_code,
341                    routing_code: self.routing_code,
342                })
343            })
344    }
345}
346
347/// Zielüberschreibung ab einer bestimmten Haltestelle auf einer Route.
348///
349/// Destination override applied from a specific stop on a route.
350#[derive(Default, Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
351pub struct PisRouteTerminus {
352    /// Index der Haltestelle auf der Route, ab der dieses Ziel gilt.
353    ///
354    /// Index of the stop of the route, from which this terminus applies
355    pub stop_index: usize,
356    /// Neuer Zielstationscode.
357    ///
358    /// New terminus code
359    pub code: Option<u32>,
360    /// Neue Linie.
361    ///
362    /// New line
363    pub line: Option<u32>,
364    /// Neuer Sonderzeichen-Code.
365    ///
366    /// New special char code
367    pub special_char_code: Option<u32>,
368    /// Neuer Routing-Code.
369    ///
370    /// New routing code
371    pub routing_code: Option<u32>,
372}
373
374impl PisRouteTerminus {
375    fn routing_code_hash(&self) -> u32 {
376        0
377    }
378
379    /// Gibt den Routing-Code zurück oder einen generierten Hash, falls nicht gesetzt.
380    ///
381    /// Returns the routing code, falling back to a generated hash if unset.
382    pub fn routing_code(&self) -> u32 {
383        self.routing_code.unwrap_or(self.routing_code_hash())
384    }
385}
386
387/// PISS-Content-Gruppe passend zur aktiven PIS-Gruppe und einer Fahrzeugklasse.
388///
389/// PISS content group matching the active PIS group and a vehicle class.
390#[derive(Clone, Debug, PartialEq, Eq)]
391pub struct PisSpGroup {
392    /// Anzeigename der PISS-Gruppe.
393    ///
394    /// Display name of the PISS group.
395    pub name: String,
396    /// Content-ID der zugrunde liegenden PIS-Basisgruppe.
397    ///
398    /// Content id of the underlying basic PIS group.
399    pub basic_pis_group: ContentId,
400    /// Fahrzeugklasse, für die diese PISS-Gruppe gilt.
401    ///
402    /// Vehicle class this PISS group applies to.
403    pub class: String,
404    /// Zusätzlicher Linienstring aus der PISS-Gruppe.
405    ///
406    /// Additional line string from the PISS group.
407    pub add_lines: String,
408    /// Zusätzliche Linienstrings pro Station.
409    ///
410    /// Additional line strings per station.
411    pub add_lines_stations: Vec<PisSpAddLines>,
412    /// In der PISS-Gruppe definierte Routen.
413    ///
414    /// Routes defined in the PISS group.
415    pub routes: Vec<PisSpRoute>,
416}
417
418/// Zusätzlicher Linien-Text für eine Station in einer PISS-Gruppe.
419///
420/// Additional line text for a station in a PISS group.
421#[derive(Clone, Debug, PartialEq, Eq)]
422pub struct PisSpAddLines {
423    /// Stationscode, zu dem die Zusatzlinien gehören.
424    ///
425    /// Station code the additional lines belong to.
426    pub code: i32,
427    /// Zusätzlicher Linien-Anzeigestring.
428    ///
429    /// Additional line display string.
430    pub lines: String,
431}
432
433/// Routendatensatz in einer PISS-Content-Gruppe.
434///
435/// Route record stored in a PISS content group.
436#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
437pub struct PisSpRoute {
438    /// Routencode innerhalb der PISS-Gruppe.
439    ///
440    /// Route code within the PISS group.
441    pub code: i32,
442    /// Linien-Anzeigestring für diese Route.
443    ///
444    /// Line display string for this route.
445    pub lines: String,
446    /// Haltestellen-Anzeigestrings entlang der Route.
447    ///
448    /// Stop display strings along the route.
449    pub stop_lines: Vec<String>,
450}
451impl PisSpGroup {
452    /// Liefert die ContentId der PISS-Gruppe, die zur aktiven PIS-Gruppe passt und die gegebene Klasse hat.
453    ///
454    /// Returns the PISS group content id for the active PIS group and the given class.
455    #[cfg(feature = "ffi")]
456    pub fn get_content_id(class: &str) -> Option<ContentId> {
457        let class = lotus_script_sys::FfiObject::new(&class);
458        let content_id = lotus_script_sys::FfiObject::from_packed(unsafe {
459            lotus_script_sys::pis::get_sp_content_id(class.packed())
460        });
461        content_id.deserialize()
462    }
463
464    /// Liefert die zusätzlichen Linien aus der gegebenen PISS-Gruppe.
465    ///
466    /// Returns the additional line string from the given PISS group.
467    #[cfg(feature = "ffi")]
468    pub fn get_group_strings(content_id: ContentId) -> String {
469        let content_id = lotus_script_sys::FfiObject::new(&content_id);
470        let lines = lotus_script_sys::FfiObject::from_packed(unsafe {
471            lotus_script_sys::pis::get_sp_group_strings(content_id.packed())
472        });
473        lines.deserialize()
474    }
475
476    /// Liefert die zusätzlichen Linien für eine Station aus der gegebenen PISS-Gruppe.
477    ///
478    /// Returns additional line text for a station from the given PISS group.
479    #[cfg(feature = "ffi")]
480    pub fn get_station_strings(content_id: ContentId, station_code: u32) -> Option<String> {
481        let content_id = lotus_script_sys::FfiObject::new(&content_id);
482        let lines = lotus_script_sys::FfiObject::from_packed(unsafe {
483            lotus_script_sys::pis::get_sp_station_strings(content_id.packed(), station_code)
484        });
485        lines.deserialize()
486    }
487
488    /// Liefert die Route mit der gegebenen Linie und dem Code aus der gegebenen PISS-Gruppe.
489    ///
490    /// Returns the route for the given line and code from the given PISS group.
491    #[cfg(feature = "ffi")]
492    pub fn get_route(content_id: ContentId, line: u32, code: u32) -> Option<PisSpRoute> {
493        let content_id = lotus_script_sys::FfiObject::new(&content_id);
494        let route = lotus_script_sys::FfiObject::from_packed(unsafe {
495            lotus_script_sys::pis::get_sp_route_data(content_id.packed(), line, code)
496        });
497        route.deserialize()
498    }
499}