Skip to main content

veloren_rtsim/data/
airship.rs

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/// Data for airship operations. This is part of RTSimData and is NOT persisted.
14#[derive(Clone, Default, Debug)]
15pub struct AirshipSim {
16    /// The pilot route assignments. The key is the pilot ActorId, the value is
17    /// a tuple. The first element is the index for the outer Airships::routes
18    /// Vec (the route loop index), and the second element is the index for the
19    /// pilot's initial route leg in the inner Airships::routes Vec.
20    pub assigned_routes: DHashMap<ActorId, (usize, usize)>,
21
22    /// The pilots assigned to a route in the order they fly the route.
23    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    /// Called from world generation code to set the route and initial leg
47    /// indexes for an airship captain NPC. World generation is dynamic and
48    /// can change across runs, and existing captain (and ship) NPCs may
49    /// change the assigned route and leg, and new NPCs may be added to the
50    /// world. This is the function that connects the saved RTSim data to
51    /// the world generation data.
52    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    /// Called from world generation code after all airship captains have been
113    /// registered. This function generates the route_pilots hash map which
114    /// provides a list of pilots assigned to each route index, in the order
115    /// they will fly the route. This provides for determining the "next
116    /// pilot" on a route, which is used for deconfliction and
117    /// "traffic control" of airships.
118    pub fn configure_route_pilots(&mut self, airships: &Airships, actors: &Actors) {
119        debug_airships!(4, "Airship Assigned Routes: {:?}", self.assigned_routes);
120        // for each route index and leg index, find all pilots that are assigned to
121        // the route index and leg index. Sort them by their distance to the starting
122        // position of the leg. Repeat for all legs of the route, then add the resulting
123        // list to the route_pilots hash map.
124
125        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                // Find all pilots that are spawned on the same route_index and leg_index.
129                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                    // Sort pilots by their distance to the starting position of the leg.
138                    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    /// Retrieves two airship destinations - the current and the next.
162    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                // If docked, retrieve the next two.
172                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                // If not docked, retrieve the current and next.
184                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    /// Given a route index and pilot id, find the next pilot on the route (the
202    /// one that is ahead of the given pilot).
203    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                // If there is only one pilot on the route, return the pilot itself.
207                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                    // If the pilot is the last one in the list, return the first one.
216                    return Some(pilots[0]);
217                } else {
218                    // Otherwise, return the next pilot in the list.
219                    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}