veloren_rtsim/data/
airship.rs

1use crate::data::Npcs;
2use common::rtsim::NpcId;
3use std::cmp::Ordering;
4#[cfg(debug_assertions)] use tracing::debug;
5use vek::*;
6use world::{
7    World,
8    civ::airship_travel::{AirshipSpawningLocation, Airships},
9    util::DHashMap,
10};
11
12/// Data for airship operations. This is part of RTSimData and is NOT persisted.
13#[derive(Clone, Default, Debug)]
14pub struct AirshipSim {
15    /// The pilot route assignments. The key is the pilot NpcId, the value is
16    /// a tuple. The first element is the index for the outer Airships::routes
17    /// Vec (the route loop index), and the second element is the index for the
18    /// pilot's initial route leg in the inner Airships::routes Vec.
19    pub assigned_routes: DHashMap<NpcId, (usize, usize)>,
20
21    /// The pilots assigned to a route in the order they fly the route.
22    pub route_pilots: DHashMap<usize, Vec<NpcId>>,
23}
24
25#[cfg(debug_assertions)]
26macro_rules! debug_airships {
27    ($($arg:tt)*) => {
28        debug!($($arg)*);
29    }
30}
31
32#[cfg(not(debug_assertions))]
33macro_rules! debug_airships {
34    ($($arg:tt)*) => {};
35}
36
37impl AirshipSim {
38    /// Called from world generation code to set the route and initial leg
39    /// indexes for an airship captain NPC. World generation is dynamic and
40    /// can change across runs, and existing captain (and ship) NPCs may
41    /// change the assigned route and leg, and new NPCs may be added to the
42    /// world. This is the function that connects the saved RTSim data to
43    /// the world generation data.
44    pub fn register_airship_captain(
45        &mut self,
46        location: &AirshipSpawningLocation,
47        captain_id: NpcId,
48        airship_id: NpcId,
49        world: &World,
50        npcs: &mut Npcs,
51    ) {
52        self.assigned_routes
53            .insert(captain_id, (location.route_index, location.leg_index));
54
55        assert!(
56            location.dir.is_normalized(),
57            "Airship direction {:?} is not normalized",
58            location.dir
59        );
60        let airship_wpos3d = location.pos.with_z(
61            world
62                .sim()
63                .get_alt_approx(location.pos.map(|e| e as i32))
64                .unwrap_or(0.0)
65                + location.height,
66        );
67
68        let airship_mount_offset = if let Some(airship) = npcs.get_mut(airship_id) {
69            airship.wpos = airship_wpos3d;
70            airship.dir = location.dir;
71            airship.body.mount_offset()
72        } else {
73            tracing::warn!(
74                "Failed to find airship {:?} for captain {:?}",
75                airship_id,
76                captain_id,
77            );
78            Vec3::new(0.0, 0.0, 0.0)
79        };
80        if let Some(captain) = npcs.get_mut(captain_id) {
81            let captain_pos = airship_wpos3d
82                + Vec3::new(
83                    location.dir.x * airship_mount_offset.x,
84                    location.dir.y * airship_mount_offset.y,
85                    airship_mount_offset.z,
86                );
87            captain.wpos = captain_pos;
88            captain.dir = location.dir;
89        }
90
91        debug_airships!(
92            "Registering airship {:?}/{:?} for spawning location {:?}",
93            airship_id,
94            captain_id,
95            location,
96        );
97    }
98
99    /// Called from world generation code after all airship captains have been
100    /// registered. This function generates the route_pilots hash map which
101    /// provides a list of pilots assigned to each route index, in the order
102    /// they will fly the route. This provides for determining the "next
103    /// pilot" on a route, which is used for deconfliction and
104    /// "traffic control" of airships.
105    pub fn configure_route_pilots(&mut self, airships: &Airships, npcs: &Npcs) {
106        debug_airships!("Airship Assigned Routes: {:?}", self.assigned_routes);
107        // for each route index and leg index, find all pilots that are assigned to
108        // the route index and leg index. Sort them by their distance to the starting
109        // position of the leg. Repeat for all legs of the route, then add the resulting
110        // list to the route_pilots hash map.
111
112        for route_index in 0..airships.route_count() {
113            let mut pilots_on_route = Vec::new();
114            for leg_index in 0..airships.docking_site_count_for_route(route_index) {
115                // Find all pilots that are spawned on the same route_index and leg_index.
116                let mut pilots_on_leg: Vec<_> = self
117                    .assigned_routes
118                    .iter()
119                    .filter(|(_, (rti, li))| *rti == route_index && *li == leg_index)
120                    .map(|(pilot_id, _)| *pilot_id)
121                    .collect();
122
123                if !pilots_on_leg.is_empty() {
124                    // Sort pilots by their distance to the starting position of the leg.
125                    let start_pos = airships.route_leg_departure_location(route_index, leg_index);
126                    pilots_on_leg.sort_by(|&pilot1, &pilot2| {
127                        let pilot1_pos = npcs.get(pilot1).map_or(start_pos, |npc| npc.wpos.xy());
128                        let pilot2_pos = npcs.get(pilot2).map_or(start_pos, |npc| npc.wpos.xy());
129                        start_pos
130                            .distance_squared(pilot1_pos)
131                            .partial_cmp(&start_pos.distance_squared(pilot2_pos))
132                            .unwrap_or(Ordering::Equal)
133                    });
134                    pilots_on_route.extend(pilots_on_leg);
135                }
136            }
137            if !pilots_on_route.is_empty() {
138                debug_airships!("Route {} pilots: {:?}", route_index, pilots_on_route);
139                self.route_pilots.insert(route_index, pilots_on_route);
140            }
141        }
142    }
143
144    /// Given a route index and pilot id, find the next pilot on the route (the
145    /// one that is ahead of the given pilot).
146    pub fn next_pilot(&self, route_index: usize, pilot_id: NpcId) -> Option<NpcId> {
147        if let Some(pilots) = self.route_pilots.get(&route_index) {
148            if pilots.len() < 2 {
149                // If there is only one pilot on the route, return the pilot itself.
150                tracing::warn!(
151                    "Route {} has only one pilot, 'next_pilot' doesn't make sense.",
152                    route_index,
153                );
154                return None;
155            }
156            if let Some(pilot_index) = pilots.iter().position(|&p_id| p_id == pilot_id) {
157                if pilot_index == pilots.len() - 1 {
158                    // If the pilot is the last one in the list, return the first one.
159                    return Some(pilots[0]);
160                } else {
161                    // Otherwise, return the next pilot in the list.
162                    return Some(pilots[pilot_index + 1]);
163                }
164            }
165        }
166        tracing::warn!(
167            "Failed to find next pilot for route index {} and pilot id {:?}",
168            route_index,
169            pilot_id
170        );
171        None
172    }
173}