1use crate::nav::{AreaIdent, AreaLike, GroupId, Nav, NavArea, PathResult, areas_audible};
3use crate::position::Position;
4use crate::utils::create_file_with_parents;
5use core::f64;
6use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
7use rayon::prelude::ParallelSliceMut;
8use rustc_hash::FxHashMap as HashMap;
9use rustc_hash::FxHashSet as HashSet;
10use serde::{Deserialize, Serialize};
11use simple_tqdm::{Config, ParTqdm, Tqdm};
12use std::fs::File;
13use std::iter;
14use std::mem;
15use std::path::Path;
16
17#[derive(Debug, Clone, Deserialize, Serialize)]
19#[allow(non_snake_case)]
20pub struct Spawns {
21 CT: Vec<Position>,
22 T: Vec<Position>,
23}
24
25pub enum Perceivability {
26 Visibility(HashMap<(u32, u32), bool>),
27 Audibility,
28}
29
30impl Spawns {
31 #[must_use]
37 pub fn from_json(filename: &Path) -> Self {
38 let file = File::open(filename).unwrap();
39 serde_json::from_reader(&file).unwrap()
40 }
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct SpawnDistance {
46 area: NavArea,
48 distance: f64,
50 path: Vec<u32>,
52}
53
54#[derive(Clone, Debug, Serialize, Deserialize)]
56pub struct ReducedSpawnDistance {
57 area: u32,
58 path: Vec<u32>,
59}
60
61impl From<&SpawnDistance> for ReducedSpawnDistance {
62 fn from(spawn_distance: &SpawnDistance) -> Self {
63 Self {
64 area: spawn_distance.area.area_id,
65 path: spawn_distance.path.clone(),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72#[allow(non_snake_case)]
73pub struct SpawnDistances {
74 pub CT: Vec<SpawnDistance>,
75 pub T: Vec<SpawnDistance>,
76}
77
78impl SpawnDistances {
79 #[must_use]
85 pub fn from_json(filename: &Path) -> Self {
86 let file = File::open(filename).unwrap();
87 serde_json::from_reader(&file).unwrap()
88 }
89
90 pub fn save_to_json(self, filename: &Path) {
96 let mut file = create_file_with_parents(filename);
97 serde_json::to_writer(&mut file, &self).unwrap();
98 }
99}
100
101#[must_use]
109pub fn get_distances_from_spawns(map_areas: &Nav, spawns: &Spawns) -> SpawnDistances {
110 println!("Getting distances from spawns.");
111 let tqdm_config = Config::new().with_leave(true);
112
113 let distances: Vec<(SpawnDistance, SpawnDistance)> = map_areas
114 .areas
115 .values()
116 .collect::<Vec<_>>()
117 .par_iter()
118 .tqdm_config(tqdm_config.with_desc("Getting distances per spawn."))
119 .map(|area| {
120 let ct_path = spawns
121 .CT
122 .iter()
123 .map(|&spawn_point| {
124 map_areas.find_path(AreaIdent::Pos(spawn_point), AreaIdent::Id(area.area_id))
125 })
126 .min_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap())
127 .unwrap_or(PathResult {
128 distance: f64::MAX,
129 path: Vec::new(),
130 });
131
132 let t_path = spawns
133 .T
134 .iter()
135 .map(|&spawn_point| {
136 map_areas.find_path(AreaIdent::Pos(spawn_point), AreaIdent::Id(area.area_id))
137 })
138 .min_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap())
139 .unwrap_or(PathResult {
140 distance: f64::MAX,
141 path: Vec::new(),
142 });
143
144 (
145 SpawnDistance {
146 area: (*area).clone(),
147 distance: ct_path.distance,
148 path: ct_path.path.iter().map(|a| a.area_id).collect(),
149 },
150 SpawnDistance {
151 area: (*area).clone(),
152 distance: t_path.distance,
153 path: t_path.path.iter().map(|a| a.area_id).collect(),
154 },
155 )
156 })
157 .collect();
158 println!(); let mut ct_distances: Vec<SpawnDistance> = Vec::new();
161 let mut t_distances: Vec<SpawnDistance> = Vec::new();
162
163 for (ct, t) in distances {
164 ct_distances.push(ct);
165 t_distances.push(t);
166 }
167
168 ct_distances.par_sort_unstable_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap());
169 t_distances.par_sort_unstable_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap());
170
171 SpawnDistances {
172 CT: ct_distances,
173 T: t_distances,
174 }
175}
176
177#[derive(Clone, Debug, Serialize, Deserialize)]
179pub struct SpreadResult {
180 new_marked_areas_ct: HashSet<u32>,
182 new_marked_areas_t: HashSet<u32>,
184
185 visibility_connections: Vec<(ReducedSpawnDistance, ReducedSpawnDistance)>,
187}
188
189pub fn save_spreads_to_json(spreads: &[SpreadResult], filename: &Path) {
195 let mut file = create_file_with_parents(filename);
196 serde_json::to_writer(&mut file, &spreads).unwrap();
197}
198
199fn assert_sorted(spawn_distances: &[SpawnDistance]) {
200 assert!(
201 spawn_distances
202 .windows(2)
203 .all(|w| w[0].distance <= w[1].distance)
204 );
205}
206
207#[allow(clippy::too_many_lines)]
232#[must_use]
233pub fn generate_spreads(
234 spawn_distances_ct: &[SpawnDistance],
235 spawn_distances_t: &[SpawnDistance],
236 area_to_group: &HashMap<u32, GroupId>,
237 perceiption: &Perceivability,
238) -> Vec<SpreadResult> {
239 assert_sorted(spawn_distances_ct);
240 assert_sorted(spawn_distances_t);
241 println!("Generating spreads");
242
243 let mut ct_index = 0;
244 let mut t_index = 0;
245
246 let mut new_marked_areas_ct: HashSet<u32> = HashSet::default();
247 let mut new_marked_areas_t: HashSet<u32> = HashSet::default();
248
249 let mut previous_areas_ct: Vec<&SpawnDistance> = Vec::with_capacity(spawn_distances_ct.len());
250 let mut previous_areas_t: Vec<&SpawnDistance> = Vec::with_capacity(spawn_distances_t.len());
251
252 let mut spotted_groups_ct: HashSet<GroupId> = HashSet::default();
253 let mut spotted_groups_t: HashSet<GroupId> = HashSet::default();
254 let mut visibility_connections: Vec<(ReducedSpawnDistance, ReducedSpawnDistance)> = Vec::new();
255
256 let mut last_plotted: f64 = 0.0;
257
258 let mut result = Vec::with_capacity(spawn_distances_ct.len() + spawn_distances_t.len());
259
260 let n_iterations = spawn_distances_ct
261 .iter()
262 .chain(spawn_distances_t.iter())
263 .filter(|a| a.distance < f64::MAX)
264 .count();
265
266 let tqdm_config = Config::new()
267 .with_leave(true)
268 .with_desc("Generating spreads".to_string());
269 let mut p_bar = iter::repeat_n((), n_iterations).tqdm_config(tqdm_config);
270
271 loop {
272 p_bar.next();
273 let (current_area, opposing_spotted_groups, own_spotted_groups, opposing_previous_areas) =
275 if ct_index < spawn_distances_ct.len()
276 && (t_index >= spawn_distances_t.len()
277 || spawn_distances_ct[ct_index].distance < spawn_distances_t[t_index].distance)
278 {
279 let current = &spawn_distances_ct[ct_index];
280 new_marked_areas_ct.insert(current.area.area_id);
281 previous_areas_ct.push(current);
282
283 ct_index += 1;
284 (
285 current,
286 &mut spotted_groups_t,
287 &mut spotted_groups_ct,
288 &mut previous_areas_t,
289 )
290 } else if t_index < spawn_distances_t.len() {
291 let current = &spawn_distances_t[t_index];
292 new_marked_areas_t.insert(current.area.area_id);
293 previous_areas_t.push(current);
294
295 t_index += 1;
296 (
297 current,
298 &mut spotted_groups_ct,
299 &mut spotted_groups_t,
300 &mut previous_areas_ct,
301 )
302 } else {
303 result.push(SpreadResult {
304 new_marked_areas_ct: mem::take(&mut new_marked_areas_ct),
305 new_marked_areas_t: mem::take(&mut new_marked_areas_t),
306 visibility_connections: mem::take(&mut visibility_connections),
307 });
308 break;
309 };
310
311 if current_area.distance == f64::MAX {
313 result.push(SpreadResult {
314 new_marked_areas_ct: mem::take(&mut new_marked_areas_ct),
315 new_marked_areas_t: mem::take(&mut new_marked_areas_t),
316 visibility_connections: mem::take(&mut visibility_connections),
317 });
318 break;
319 }
320
321 if current_area.path.len() >= 2
323 && own_spotted_groups
324 .contains(&area_to_group[¤t_area.path[current_area.path.len() - 2]])
325 {
326 own_spotted_groups.insert(area_to_group[¤t_area.area.area_id]);
327 }
328
329 let visible_areas = newly_perceivable(
331 current_area,
332 opposing_previous_areas,
333 own_spotted_groups,
334 opposing_spotted_groups,
335 area_to_group,
336 perceiption,
337 );
338
339 if !visible_areas.is_empty() {
341 own_spotted_groups.insert(area_to_group[¤t_area.area.area_id]);
342 for spotted_by_area in &visible_areas {
343 opposing_spotted_groups.insert(area_to_group[&spotted_by_area.area.area_id]);
344 visibility_connections.push((
345 Into::<ReducedSpawnDistance>::into(current_area),
346 Into::<ReducedSpawnDistance>::into(*spotted_by_area),
347 ));
348 }
349 }
350
351 if visible_areas.is_empty() && current_area.distance <= last_plotted + 100.0 {
353 continue;
354 }
355
356 result.push(SpreadResult {
357 new_marked_areas_ct: mem::take(&mut new_marked_areas_ct),
358 new_marked_areas_t: mem::take(&mut new_marked_areas_t),
359 visibility_connections: mem::take(&mut visibility_connections),
360 });
361
362 last_plotted = round_up_to_next_100(current_area.distance);
363 }
364 p_bar.for_each(|()| {});
365 println!(); result
367}
368
369fn newly_perceivable<'a>(
375 current_area: &SpawnDistance,
376 previous_opposing_areas: &'a [&'a SpawnDistance],
377 own_spotted_groups: &HashSet<GroupId>,
378 opposing_spotted_groups: &HashSet<GroupId>,
379 area_to_group: &HashMap<u32, GroupId>,
380 perceiption: &Perceivability,
381) -> Vec<&'a SpawnDistance> {
382 previous_opposing_areas
383 .par_iter()
384 .filter(|opposing_area| {
385 !(own_spotted_groups.contains(&area_to_group[¤t_area.area.area_id])
386 && opposing_spotted_groups.contains(&area_to_group[&opposing_area.area.area_id]))
387 && match perceiption {
388 Perceivability::Visibility(visibility_cache) => {
389 visibility_cache
390 [&(current_area.area.area_id(), opposing_area.area.area_id())]
391 }
392 Perceivability::Audibility => {
393 areas_audible(¤t_area.area, &opposing_area.area)
394 }
395 }
396 })
397 .copied()
398 .collect()
399}
400
401fn round_up_to_next_100(value: f64) -> f64 {
402 (value / 100.0).ceil() * 100.0
403}