1use crate::data::Actors;
2use common::{rtsim::ActorId, 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<ActorId, (usize, usize)>,
21
22 pub route_pilots: DHashMap<usize, Vec<ActorId>>,
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: ActorId,
56 airship_id: ActorId,
57 world: &World,
58 actors: &mut Actors,
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) = actors.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) = actors.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, actors: &Actors) {
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 = actors
141 .get(pilot1)
142 .map_or(start_pos, |actor| actor.wpos.xy());
143 let pilot2_pos = actors
144 .get(pilot2)
145 .map_or(start_pos, |actor| actor.wpos.xy());
146 start_pos
147 .distance_squared(pilot1_pos)
148 .partial_cmp(&start_pos.distance_squared(pilot2_pos))
149 .unwrap_or(Ordering::Equal)
150 });
151 pilots_on_route.extend(pilots_on_leg);
152 }
153 }
154 if !pilots_on_route.is_empty() {
155 debug_airships!(4, "Route {} pilots: {:?}", route_index, pilots_on_route);
156 self.route_pilots.insert(route_index, pilots_on_route);
157 }
158 }
159 }
160
161 pub fn next_destinations(
163 &self,
164 airships: &Airships,
165 map: &MapSizeLg,
166 route_index: usize,
167 current_leg: Option<(usize, AirshipFlightPhase)>,
168 ) -> Option<(AirshipDockingApproach, AirshipDockingApproach)> {
169 if let Some(current_leg) = current_leg {
170 match current_leg.1 {
171 AirshipFlightPhase::Docked | AirshipFlightPhase::Ascent => {
173 let next_route_leg = airships.increment_route_leg(route_index, current_leg.0);
174 Some((
175 airships.approach_for_route_and_leg(route_index, next_route_leg, map),
176 airships.approach_for_route_and_leg(
177 route_index,
178 airships.increment_route_leg(route_index, next_route_leg),
179 map,
180 ),
181 ))
182 },
183 AirshipFlightPhase::ApproachCruise
185 | AirshipFlightPhase::DepartureCruise
186 | AirshipFlightPhase::Descent
187 | AirshipFlightPhase::Transition => Some((
188 airships.approach_for_route_and_leg(route_index, current_leg.0, map),
189 airships.approach_for_route_and_leg(
190 route_index,
191 airships.increment_route_leg(route_index, current_leg.0),
192 map,
193 ),
194 )),
195 }
196 } else {
197 None
198 }
199 }
200
201 pub fn next_pilot(&self, route_index: usize, pilot_id: ActorId) -> Option<ActorId> {
204 if let Some(pilots) = self.route_pilots.get(&route_index) {
205 if pilots.len() < 2 {
206 tracing::warn!(
208 "Route {} has only one pilot, 'next_pilot' doesn't make sense.",
209 route_index,
210 );
211 return None;
212 }
213 if let Some(pilot_index) = pilots.iter().position(|&p_id| p_id == pilot_id) {
214 if pilot_index == pilots.len() - 1 {
215 return Some(pilots[0]);
217 } else {
218 return Some(pilots[pilot_index + 1]);
220 }
221 }
222 }
223 tracing::warn!(
224 "Failed to find next pilot for route index {} and pilot id {:?}",
225 route_index,
226 pilot_id
227 );
228 None
229 }
230}