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}