1use crate::nav::{AreaIdent, AreaLike, GroupId, Nav, NavArea, PathResult};
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
25impl Spawns {
26 #[must_use]
32 pub fn from_json(filename: &Path) -> Self {
33 let file = File::open(filename).unwrap();
34 serde_json::from_reader(&file).unwrap()
35 }
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct SpawnDistance {
41 area: NavArea,
43 distance: f64,
45 path: Vec<u32>,
47}
48
49#[derive(Clone, Debug, Serialize, Deserialize)]
51pub struct ReducedSpawnDistance {
52 area: u32,
53 path: Vec<u32>,
54}
55
56impl From<&SpawnDistance> for ReducedSpawnDistance {
57 fn from(spawn_distance: &SpawnDistance) -> Self {
58 Self {
59 area: spawn_distance.area.area_id,
60 path: spawn_distance.path.clone(),
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67#[allow(non_snake_case)]
68pub struct SpawnDistances {
69 pub CT: Vec<SpawnDistance>,
70 pub T: Vec<SpawnDistance>,
71}
72
73impl SpawnDistances {
74 #[must_use]
80 pub fn from_json(filename: &Path) -> Self {
81 let file = File::open(filename).unwrap();
82 serde_json::from_reader(&file).unwrap()
83 }
84
85 pub fn save_to_json(self, filename: &Path) {
91 let mut file = create_file_with_parents(filename);
92 serde_json::to_writer(&mut file, &self).unwrap();
93 }
94}
95
96#[must_use]
104pub fn get_distances_from_spawns(map_areas: &Nav, spawns: &Spawns) -> SpawnDistances {
105 println!("Getting distances from spawns.");
106 let tqdm_config = Config::new().with_leave(true);
107
108 let distances: Vec<(SpawnDistance, SpawnDistance)> = map_areas
109 .areas
110 .values()
111 .collect::<Vec<_>>()
112 .par_iter()
113 .tqdm_config(tqdm_config.with_desc("Getting distances per spawn."))
114 .map(|area| {
115 let ct_path = spawns
116 .CT
117 .iter()
118 .map(|&spawn_point| {
119 map_areas.find_path(AreaIdent::Pos(spawn_point), AreaIdent::Id(area.area_id))
120 })
121 .min_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap())
122 .unwrap_or(PathResult {
123 distance: f64::MAX,
124 path: Vec::new(),
125 });
126
127 let t_path = spawns
128 .T
129 .iter()
130 .map(|&spawn_point| {
131 map_areas.find_path(AreaIdent::Pos(spawn_point), AreaIdent::Id(area.area_id))
132 })
133 .min_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap())
134 .unwrap_or(PathResult {
135 distance: f64::MAX,
136 path: Vec::new(),
137 });
138
139 (
140 SpawnDistance {
141 area: (*area).clone(),
142 distance: ct_path.distance,
143 path: ct_path.path.iter().map(|a| a.area_id).collect(),
144 },
145 SpawnDistance {
146 area: (*area).clone(),
147 distance: t_path.distance,
148 path: t_path.path.iter().map(|a| a.area_id).collect(),
149 },
150 )
151 })
152 .collect();
153 println!(); let mut ct_distances: Vec<SpawnDistance> = Vec::new();
156 let mut t_distances: Vec<SpawnDistance> = Vec::new();
157
158 for (ct, t) in distances {
159 ct_distances.push(ct);
160 t_distances.push(t);
161 }
162
163 ct_distances.par_sort_unstable_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap());
164 t_distances.par_sort_unstable_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap());
165
166 SpawnDistances {
167 CT: ct_distances,
168 T: t_distances,
169 }
170}
171
172#[derive(Clone, Debug, Serialize, Deserialize)]
174pub struct SpreadResult {
175 new_marked_areas_ct: HashSet<u32>,
177 new_marked_areas_t: HashSet<u32>,
179
180 visibility_connections: Vec<(ReducedSpawnDistance, ReducedSpawnDistance)>,
182}
183
184#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
185pub enum SpreadStyle {
186 Fine,
187 Rough,
188}
189
190pub fn save_spreads_to_json(spreads: &[SpreadResult], filename: &Path) {
196 let mut file = create_file_with_parents(filename);
197 serde_json::to_writer(&mut file, &spreads).unwrap();
198}
199
200fn assert_sorted(spawn_distances: &[SpawnDistance]) {
201 assert!(
202 spawn_distances
203 .windows(2)
204 .all(|w| w[0].distance <= w[1].distance)
205 );
206}
207
208#[allow(clippy::too_many_lines)]
233#[must_use]
234pub fn generate_spreads(
235 spawn_distances_ct: &[SpawnDistance],
236 spawn_distances_t: &[SpawnDistance],
237 area_to_group: &HashMap<u32, GroupId>,
238 style: SpreadStyle,
239 visibility_cache: &HashMap<(u32, u32), bool>,
240) -> Vec<SpreadResult> {
241 assert_sorted(spawn_distances_ct);
242 assert_sorted(spawn_distances_t);
243 println!("Generating spreads");
244
245 let mut ct_index = 0;
246 let mut t_index = 0;
247
248 let mut new_marked_areas_ct: HashSet<u32> = HashSet::default();
249 let mut new_marked_areas_t: HashSet<u32> = HashSet::default();
250
251 let mut previous_areas_ct: Vec<&SpawnDistance> = Vec::with_capacity(spawn_distances_ct.len());
252 let mut previous_areas_t: Vec<&SpawnDistance> = Vec::with_capacity(spawn_distances_t.len());
253
254 let mut spotted_groups_ct: HashSet<GroupId> = HashSet::default();
255 let mut spotted_groups_t: HashSet<GroupId> = HashSet::default();
256 let mut visibility_connections: Vec<(ReducedSpawnDistance, ReducedSpawnDistance)> = Vec::new();
257
258 let mut last_plotted: f64 = 0.0;
259
260 let mut result = Vec::with_capacity(spawn_distances_ct.len() + spawn_distances_t.len());
261
262 let n_iterations = spawn_distances_ct
263 .iter()
264 .chain(spawn_distances_t.iter())
265 .filter(|a| a.distance < f64::MAX)
266 .count();
267
268 let tqdm_config = Config::new()
269 .with_leave(true)
270 .with_desc(format!("Generating spreads with style: {style:?}"));
271 let mut p_bar = iter::repeat(()).take(n_iterations).tqdm_config(tqdm_config);
272
273 loop {
274 p_bar.next();
275 let (current_area, opposing_spotted_groups, own_spotted_groups, opposing_previous_areas) =
277 if ct_index < spawn_distances_ct.len()
278 && (t_index >= spawn_distances_t.len()
279 || spawn_distances_ct[ct_index].distance < spawn_distances_t[t_index].distance)
280 {
281 let current = &spawn_distances_ct[ct_index];
282 new_marked_areas_ct.insert(current.area.area_id);
283 previous_areas_ct.push(current);
284
285 ct_index += 1;
286 (
287 current,
288 &mut spotted_groups_t,
289 &mut spotted_groups_ct,
290 &mut previous_areas_t,
291 )
292 } else if t_index < spawn_distances_t.len() {
293 let current = &spawn_distances_t[t_index];
294 new_marked_areas_t.insert(current.area.area_id);
295 previous_areas_t.push(current);
296
297 t_index += 1;
298 (
299 current,
300 &mut spotted_groups_ct,
301 &mut spotted_groups_t,
302 &mut previous_areas_ct,
303 )
304 } else {
305 result.push(SpreadResult {
306 new_marked_areas_ct: mem::take(&mut new_marked_areas_ct),
307 new_marked_areas_t: mem::take(&mut new_marked_areas_t),
308 visibility_connections: mem::take(&mut visibility_connections),
309 });
310 break;
311 };
312
313 if current_area.distance == f64::MAX {
315 result.push(SpreadResult {
316 new_marked_areas_ct: mem::take(&mut new_marked_areas_ct),
317 new_marked_areas_t: mem::take(&mut new_marked_areas_t),
318 visibility_connections: mem::take(&mut visibility_connections),
319 });
320 break;
321 }
322
323 if current_area.path.len() >= 2
325 && own_spotted_groups
326 .contains(&area_to_group[¤t_area.path[current_area.path.len() - 2]])
327 {
328 own_spotted_groups.insert(area_to_group[¤t_area.area.area_id]);
329 }
330
331 let visible_areas = newly_visible(
333 current_area,
334 opposing_previous_areas,
335 own_spotted_groups,
336 opposing_spotted_groups,
337 area_to_group,
338 style,
339 visibility_cache,
340 );
341
342 if !visible_areas.is_empty() {
344 own_spotted_groups.insert(area_to_group[¤t_area.area.area_id]);
345 for spotted_by_area in &visible_areas {
346 opposing_spotted_groups.insert(area_to_group[&spotted_by_area.area.area_id]);
347 visibility_connections.push((
348 Into::<ReducedSpawnDistance>::into(current_area),
349 Into::<ReducedSpawnDistance>::into(*spotted_by_area),
350 ));
351 }
352 }
353
354 if visible_areas.is_empty() && current_area.distance <= last_plotted + 100.0 {
356 continue;
357 }
358
359 result.push(SpreadResult {
360 new_marked_areas_ct: mem::take(&mut new_marked_areas_ct),
361 new_marked_areas_t: mem::take(&mut new_marked_areas_t),
362 visibility_connections: mem::take(&mut visibility_connections),
363 });
364
365 last_plotted = round_up_to_next_100(current_area.distance);
366 }
367 p_bar.for_each(|()| {});
368 println!(); result
370}
371
372fn newly_visible<'a>(
374 current_area: &SpawnDistance,
375 previous_opposing_areas: &'a [&'a SpawnDistance],
376 own_spotted_groups: &mut HashSet<GroupId>,
377 opposing_spotted_groups: &mut HashSet<GroupId>,
378 area_to_group: &HashMap<u32, GroupId>,
379 style: SpreadStyle,
380 visibility_cache: &HashMap<(u32, u32), bool>,
381) -> Vec<&'a SpawnDistance> {
382 match style {
383 SpreadStyle::Fine => newly_visible_fine(
384 current_area,
385 previous_opposing_areas,
386 own_spotted_groups,
387 opposing_spotted_groups,
388 area_to_group,
389 visibility_cache,
390 ),
391 SpreadStyle::Rough => newly_visible_rough(
392 current_area,
393 previous_opposing_areas,
394 own_spotted_groups,
395 opposing_spotted_groups,
396 area_to_group,
397 visibility_cache,
398 ),
399 }
400}
401
402fn newly_visible_rough<'a>(
406 current_area: &SpawnDistance,
407 previous_opposing_areas: &'a [&'a SpawnDistance],
408 own_spotted_groups: &mut HashSet<GroupId>,
409 opposing_spotted_groups: &mut HashSet<GroupId>,
410 area_to_group: &HashMap<u32, GroupId>,
411 visibility_cache: &HashMap<(u32, u32), bool>,
412) -> Vec<&'a SpawnDistance> {
413 if current_area
414 .path
415 .iter()
416 .any(|path_id| own_spotted_groups.contains(&area_to_group[path_id]))
417 {
418 return Vec::new();
419 }
420
421 let mut results = Vec::new();
422 for &opposing_area in previous_opposing_areas {
424 if visibility_cache[&(current_area.area.area_id(), opposing_area.area.area_id())] {
425 own_spotted_groups.insert(area_to_group[¤t_area.area.area_id]);
426 opposing_spotted_groups.insert(area_to_group[&opposing_area.area.area_id]);
427 results.push(opposing_area);
428 }
429 }
430 results
431}
432
433fn newly_visible_fine<'a>(
439 current_area: &SpawnDistance,
440 previous_opposing_areas: &'a [&'a SpawnDistance],
441 own_spotted_groups: &HashSet<GroupId>,
442 opposing_spotted_groups: &HashSet<GroupId>,
443 area_to_group: &HashMap<u32, GroupId>,
444 visibility_cache: &HashMap<(u32, u32), bool>,
445) -> Vec<&'a SpawnDistance> {
446 previous_opposing_areas
447 .par_iter()
448 .filter(|opposing_area| {
449 !(own_spotted_groups.contains(&area_to_group[¤t_area.area.area_id])
450 && opposing_spotted_groups.contains(&area_to_group[&opposing_area.area.area_id]))
451 && visibility_cache[&(current_area.area.area_id(), opposing_area.area.area_id())]
452 })
453 .copied()
454 .collect()
455}
456
457fn round_up_to_next_100(value: f64) -> f64 {
458 (value / 100.0).ceil() * 100.0
459}