paiagram/rw_data/
jgrpp.rs1use bevy::{platform::collections::HashMap, prelude::*};
2use moonshine_core::kind::{InsertInstance, Instance};
3use serde::Deserialize;
4
5use crate::{
6 graph::{Graph, Station},
7 rw_data::ModifyData,
8 units::time::{Duration, TimetableTime},
9 vehicles::{
10 Vehicle,
11 entries::{TimetableEntry, TravelMode, VehicleSchedule},
12 vehicle_set::VehicleSet,
13 },
14};
15
16#[derive(Deserialize)]
17#[serde(rename_all = "kebab-case")]
18struct Root {
19 #[serde(rename = "version")]
20 schema_version: u32,
21 #[serde(rename = "vehicle-group-name")]
22 name: String,
23 game_properties: GameProperties,
24 #[serde(default)]
25 schedules: Vec<ExportSchedule>,
26 orders: Vec<ExportOrder>,
27}
28
29#[derive(Deserialize)]
30#[serde(rename_all = "kebab-case")]
31struct GameProperties {
32 ticks_per_minute: u32,
33}
34
35#[derive(Deserialize)]
36#[serde(rename_all = "kebab-case")]
37struct ExportSchedule {
38 slots: Vec<i32>,
39 duration: i32,
40}
41
42#[derive(Deserialize)]
43#[serde(rename_all = "kebab-case")]
44struct ExportOrder {
45 destination_id: u32,
49 destination_name: String,
50 destination_location: DestinationPosition,
51 travel_time: i32,
52 wait_time: Option<i32>,
53 schedule_index: Option<usize>,
56}
57
58#[derive(Deserialize, Clone, Copy)]
72struct DestinationPosition {
73 #[serde(rename = "X")]
74 x: f32,
75 #[serde(rename = "Y")]
76 y: f32,
77}
78
79impl Into<egui::Pos2> for DestinationPosition {
80 fn into(self) -> egui::Pos2 {
81 egui::Pos2 {
82 x: self.x,
83 y: self.y,
84 }
85 }
86}
87
88fn make_destination(
89 destination_map: &mut HashMap<u32, Instance<Station>>,
90 commands: &mut Commands,
91 graph: &mut Graph,
92 order: &ExportOrder,
93) -> Instance<Station> {
94 if let Some(s) = destination_map.get(&order.destination_id) {
95 return *s;
96 };
97 let station = commands
99 .spawn((Name::new(order.destination_name.clone()),))
100 .insert_instance(Station(order.destination_location.into()))
101 .into();
102 destination_map.insert(order.destination_id, station);
103 graph.add_node(station);
104 station
105}
106
107pub fn load_jgrpp_timetable_export(
108 mut commands: Commands,
109 mut reader: MessageReader<ModifyData>,
110 mut graph: ResMut<Graph>,
111) {
112 let mut str: Option<&[String]> = None;
113 for modification in reader.read() {
114 match modification {
115 ModifyData::LoadJGRPP(s) => str = Some(s.as_slice()),
116 _ => {}
117 }
118 }
119 let Some(str) = str else {
120 return;
121 };
122 let mut destination_map: HashMap<u32, Instance<Station>> = HashMap::new();
123 let vehicle_set_entity = commands
124 .spawn((Name::new("JGRPP import set"), VehicleSet))
125 .id();
126 for maybe_root in str {
127 let root: Root = match serde_json::from_str(maybe_root) {
128 Ok(root) => root,
129 Err(e) => {
130 error!("Error while deserializing JGRPP timetable export: {:?}", e);
131 continue;
132 }
133 };
134 let tick_to_duration = |tick: i32| {
135 let mins = tick as f32 / root.game_properties.ticks_per_minute as f32;
136 Duration((mins * 60.0) as i32)
137 };
138 assert_eq!(root.schema_version, 1, "Version number must be 1!");
139 let schedules_iter: Box<dyn Iterator<Item = Vec<&ExportOrder>>> =
141 if root.schedules.is_empty() {
142 Box::new(std::iter::once(
144 root.orders.iter().chain(root.orders.first()).collect(),
145 ))
146 } else {
147 let first_trigger = root
150 .orders
151 .iter()
152 .position(|e| e.schedule_index.is_some())
153 .unwrap_or(0);
154 let mut rotated = root.orders[first_trigger..]
155 .iter()
156 .chain(root.orders[..=first_trigger].iter())
157 .peekable();
158 Box::new(std::iter::from_fn(move || {
159 let mut group = Vec::new();
160 if let Some(first) = rotated.next() {
161 group.push(first);
162 } else {
163 return None;
164 }
165 while let Some(item) = rotated.peek() {
166 if item.schedule_index.is_some() {
167 group.push(*item);
168 break;
169 } else {
170 group.push(rotated.next().unwrap());
171 }
172 }
173 if group.len() > 1 { Some(group) } else { None }
174 }))
175 };
176 for raw_schedule in schedules_iter {
177 let vehicle_entity = commands.spawn((Name::new(root.name.clone()), Vehicle)).id();
178 commands
179 .entity(vehicle_set_entity)
180 .add_child(vehicle_entity);
181 let mut vehicle_schedule = VehicleSchedule {
182 start: TimetableTime(0),
183 repeat: None,
184 departures: Vec::new(),
185 entities: Vec::new(),
186 };
187 let mut time_counter = TimetableTime(0);
188 for order in raw_schedule {
189 let station_instance =
190 make_destination(&mut destination_map, &mut commands, &mut graph, order);
191 let entry = TimetableEntry {
192 station: station_instance.entity(),
193 arrival: {
194 let a = TravelMode::At(time_counter);
195 time_counter += tick_to_duration(order.travel_time);
196 a
197 },
198 departure: order.wait_time.map(|t| {
199 let a = TravelMode::At(time_counter);
200 time_counter += tick_to_duration(t);
201 a
202 }),
203 service: None,
204 track: None,
205 };
206 let entry_entity = commands.spawn(entry).id();
207 commands.entity(vehicle_entity).add_child(entry_entity);
208 if let Some(schedule_index) = order.schedule_index
209 && vehicle_schedule.repeat.is_none()
210 {
211 let schedule = &root.schedules[schedule_index];
212 vehicle_schedule.repeat = Some(tick_to_duration(schedule.duration));
213 vehicle_schedule
214 .departures
215 .extend(schedule.slots.iter().cloned().map(tick_to_duration));
216 }
217 vehicle_schedule.entities.push(entry_entity);
218 }
219 if vehicle_schedule.repeat.is_none() {
220 vehicle_schedule.repeat = Some(time_counter.as_duration());
221 vehicle_schedule.departures = vec![Duration(0)]
222 }
223 commands.entity(vehicle_entity).insert(vehicle_schedule);
224 }
225 }
226}