1use crate::data::Npcs;
2use common::{rtsim::NpcId, terrain::MapSizeLg};
3use std::cmp::Ordering;
4use vek::*;
5use world::{
6 World,
7 civ::airship_travel::{
8 AirshipDockingApproach, AirshipFlightPhase, AirshipSpawningLocation, Airships,
9 },
10 util::DHashMap,
11};
12
13#[derive(Clone, Default, Debug)]
15pub struct AirshipSim {
16 pub assigned_routes: DHashMap<NpcId, (usize, usize)>,
21
22 pub route_pilots: DHashMap<usize, Vec<NpcId>>,
24}
25
26#[cfg(debug_assertions)]
27macro_rules! debug_airships {
28 ($level:expr, $($arg:tt)*) => {
29 match $level {
30 0 => tracing::info!($($arg)*),
31 1 => tracing::warn!($($arg)*),
32 2 => tracing::error!($($arg)*),
33 3 => tracing::debug!($($arg)*),
34 4 => tracing::trace!($($arg)*),
35 _ => tracing::trace!($($arg)*),
36 }
37 }
38}
39
40#[cfg(not(debug_assertions))]
41macro_rules! debug_airships {
42 ($($arg:tt)*) => {};
43}
44
45impl AirshipSim {
46 pub fn register_airship_captain(
53 &mut self,
54 location: &AirshipSpawningLocation,
55 captain_id: NpcId,
56 airship_id: NpcId,
57 world: &World,
58 npcs: &mut Npcs,
59 ) {
60 self.assigned_routes
61 .insert(captain_id, (location.route_index, location.leg_index));
62
63 assert!(
64 location.dir.is_normalized(),
65 "Airship direction {:?} is not normalized",
66 location.dir
67 );
68 let airship_wpos3d = match location.flight_phase {
69 AirshipFlightPhase::DepartureCruise
70 | AirshipFlightPhase::ApproachCruise
71 | AirshipFlightPhase::Transition => location.pos.with_z(
72 world
73 .sim()
74 .get_alt_approx(location.pos.map(|e| e as i32))
75 .unwrap_or(0.0)
76 + location.height,
77 ),
78 _ => location.pos.with_z(location.height),
79 };
80 let airship_mount_offset = if let Some(airship) = npcs.get_mut(airship_id) {
81 airship.wpos = airship_wpos3d;
82 airship.dir = location.dir;
83 airship.body.mount_offset()
84 } else {
85 tracing::warn!(
86 "Failed to find airship {:?} for captain {:?}",
87 airship_id,
88 captain_id,
89 );
90 Vec3::new(0.0, 0.0, 0.0)
91 };
92 if let Some(captain) = npcs.get_mut(captain_id) {
93 let captain_pos = airship_wpos3d
94 + Vec3::new(
95 location.dir.x * airship_mount_offset.x,
96 location.dir.y * airship_mount_offset.y,
97 airship_mount_offset.z,
98 );
99 captain.wpos = captain_pos;
100 captain.dir = location.dir;
101 }
102
103 debug_airships!(
104 4,
105 "Registering airship {:?}/{:?} for spawning location {:?}",
106 airship_id,
107 captain_id,
108 location,
109 );
110 }
111
112 pub fn configure_route_pilots(&mut self, airships: &Airships, npcs: &Npcs) {
119 debug_airships!(4, "Airship Assigned Routes: {:?}", self.assigned_routes);
120 for route_index in 0..airships.route_count() {
126 let mut pilots_on_route = Vec::new();
127 for leg_index in 0..airships.docking_site_count_for_route(route_index) {
128 let mut pilots_on_leg: Vec<_> = self
130 .assigned_routes
131 .iter()
132 .filter(|(_, (rti, li))| *rti == route_index && *li == leg_index)
133 .map(|(pilot_id, _)| *pilot_id)
134 .collect();
135
136 if !pilots_on_leg.is_empty() {
137 let start_pos = airships.route_leg_departure_location(route_index, leg_index);
139 pilots_on_leg.sort_by(|&pilot1, &pilot2| {
140 let pilot1_pos = npcs.get(pilot1).map_or(start_pos, |npc| npc.wpos.xy());
141 let pilot2_pos = npcs.get(pilot2).map_or(start_pos, |npc| npc.wpos.xy());
142 start_pos
143 .distance_squared(pilot1_pos)
144 .partial_cmp(&start_pos.distance_squared(pilot2_pos))
145 .unwrap_or(Ordering::Equal)
146 });
147 pilots_on_route.extend(pilots_on_leg);
148 }
149 }
150 if !pilots_on_route.is_empty() {
151 debug_airships!(4, "Route {} pilots: {:?}", route_index, pilots_on_route);
152 self.route_pilots.insert(route_index, pilots_on_route);
153 }
154 }
155 }
156
157 pub fn next_destinations(
159 &self,
160 airships: &Airships,
161 map: &MapSizeLg,
162 route_index: usize,
163 current_leg: Option<(usize, AirshipFlightPhase)>,
164 ) -> Option<(AirshipDockingApproach, AirshipDockingApproach)> {
165 if let Some(current_leg) = current_leg {
166 match current_leg.1 {
167 AirshipFlightPhase::Docked | AirshipFlightPhase::Ascent => {
169 let next_route_leg = airships.increment_route_leg(route_index, current_leg.0);
170 Some((
171 airships.approach_for_route_and_leg(route_index, next_route_leg, map),
172 airships.approach_for_route_and_leg(
173 route_index,
174 airships.increment_route_leg(route_index, next_route_leg),
175 map,
176 ),
177 ))
178 },
179 AirshipFlightPhase::ApproachCruise
181 | AirshipFlightPhase::DepartureCruise
182 | AirshipFlightPhase::Descent
183 | AirshipFlightPhase::Transition => Some((
184 airships.approach_for_route_and_leg(route_index, current_leg.0, map),
185 airships.approach_for_route_and_leg(
186 route_index,
187 airships.increment_route_leg(route_index, current_leg.0),
188 map,
189 ),
190 )),
191 }
192 } else {
193 None
194 }
195 }
196
197 pub fn next_pilot(&self, route_index: usize, pilot_id: NpcId) -> Option<NpcId> {
200 if let Some(pilots) = self.route_pilots.get(&route_index) {
201 if pilots.len() < 2 {
202 tracing::warn!(
204 "Route {} has only one pilot, 'next_pilot' doesn't make sense.",
205 route_index,
206 );
207 return None;
208 }
209 if let Some(pilot_index) = pilots.iter().position(|&p_id| p_id == pilot_id) {
210 if pilot_index == pilots.len() - 1 {
211 return Some(pilots[0]);
213 } else {
214 return Some(pilots[pilot_index + 1]);
216 }
217 }
218 }
219 tracing::warn!(
220 "Failed to find next pilot for route index {} and pilot id {:?}",
221 route_index,
222 pilot_id
223 );
224 None
225 }
226}