rpfm_extensions/startpos/cai/
map.rs1use getset::{CopyGetters, Getters};
15
16use std::collections::{HashMap, HashSet};
17
18use rpfm_lib::error::{RLibError, Result};
19use rpfm_lib::files::esf::{NodeType, RecordNode, ESF};
20
21use crate::startpos::coords::HexCell;
22use crate::startpos::esf_nav::{as_record, as_str, as_u32, cell_groups, child_record, leaves};
23use crate::startpos::region_map::RegionMap;
24use crate::startpos::settlements::{region_data_records, region_key};
25
26#[cfg(test)] #[path = "map_test.rs"] mod map_test;
27
28const NODE_REGION_BITS: u32 = 9;
30
31const CONNECTIVITY_RECORDS: [&str; 2] = ["CONNECTIVITY_DIRECT_TRAVERSABLE_SAME", "CONNECTIVITY_DIRECT_TRAVERSABLE_OTHER"];
33
34#[derive(Debug, Clone, PartialEq, Getters, CopyGetters)]
40pub struct MapRegion {
41
42 #[getset(get = "pub")]
44 key: String,
45
46 #[getset(get = "pub")]
48 area_groups: Vec<u32>,
49
50 #[getset(get = "pub")]
52 area_sizes: Vec<u32>,
53
54 #[getset(get = "pub")]
57 connections: Vec<(usize, u32)>,
58
59 #[getset(get_copy = "pub")]
61 connects_to_other_kind: bool,
62
63 #[getset(get_copy = "pub")]
65 has_settlement: bool,
66
67 #[getset(get_copy = "pub")]
69 port: Option<HexCell>,
70}
71
72#[derive(Debug, Clone)]
75pub struct AreaGroups<'a> {
76
77 map: &'a RegionMap,
79
80 regions: &'a [MapRegion],
82
83 sea_groups: HashMap<usize, u32>,
85}
86
87struct Cursor<'a> {
89 values: &'a [&'a NodeType],
90 position: usize,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, CopyGetters)]
95#[getset(get_copy = "pub")]
96pub struct PathfinderNode {
97
98 area: u32,
100
101 x: u16,
103
104 y: u16,
106}
107
108impl MapRegion {
113
114 pub fn all_from_map_data(map_data: &ESF) -> Result<Vec<Self>> {
120 region_data_records(map_data)?.into_iter().map(Self::from_region_data).collect()
121 }
122
123 fn from_region_data(record: &RecordNode) -> Result<Self> {
125 let key = region_key(record)?.to_owned();
126 let mut area_groups = vec![];
127 let mut area_sizes = vec![];
128 for area in child_record(record, "REGION_AREAS").iter().flat_map(|areas| areas.children().iter().flatten().filter_map(as_record)) {
129 let values = area.children().iter().flat_map(|group| leaves(group)).collect::<Vec<_>>();
130
131 let group = values.get(3).and_then(|node| as_u32(node));
133 let size = values.last().and_then(|node| as_u32(node));
134 let (Some(group), Some(size)) = (group, size) else {
135 return Err(RLibError::CampaignMapDataError(format!("region {key}: an area is malformed")));
136 };
137 area_groups.push(group);
138 area_sizes.push(size);
139 }
140
141 let mut connections = vec![];
142 for name in CONNECTIVITY_RECORDS {
143 for group in child_record(record, name).iter().flat_map(|list| list.children()) {
144 let mut values = leaves(group).filter_map(as_u32);
145 if let (Some(region), Some(value)) = (values.next(), values.next()) {
146 connections.push((region as usize, value));
147 }
148 }
149 }
150 let connects_to_other_kind = child_record(record, CONNECTIVITY_RECORDS[1]).is_some_and(|list| !list.children().is_empty());
151 let settlement = child_record(record, "SETTLEMENT_INFO");
152 let has_settlement = settlement.is_some();
153 let port = settlement.and_then(|settlement| child_record(settlement, "PORT_POSITIONS_BLOCK")).and_then(|ports| cell_groups(ports).first().copied());
154
155 Ok(Self { key, area_groups, area_sizes, connections, connects_to_other_kind, has_settlement, port })
156 }
157
158 pub fn hlcis(&self) -> Vec<(u32, Vec<u32>)> {
161 let mut hlcis: Vec<(u32, Vec<u32>)> = vec![];
162 for (index, group) in self.area_groups.iter().enumerate() {
163 let index = index as u32;
164 match hlcis.iter_mut().find(|(hlci_group, _)| hlci_group == group) {
165 Some((_, areas)) => areas.push(index),
166 None => hlcis.push((*group, vec![index])),
167 }
168 }
169 hlcis
170 }
171
172 pub fn largest_area(&self) -> Option<usize> {
174 self.area_sizes.iter().enumerate().max_by_key(|(index, size)| (**size, std::cmp::Reverse(*index))).map(|(index, _)| index)
175 }
176
177 pub fn hlci_of_area(&self, area: usize) -> Option<usize> {
179 let group = self.area_groups.get(area)?;
180 self.hlcis().iter().position(|(hlci_group, _)| hlci_group == group)
181 }
182}
183
184impl<'a> AreaGroups<'a> {
185
186 pub fn new(map: &'a RegionMap, regions: &'a [MapRegion]) -> Self {
193 let mut groups = Self { map, regions, sea_groups: HashMap::new() };
194 let is_bare_sea = |region: usize| regions.get(region).is_some_and(|region| !region.has_settlement && region.area_groups.is_empty());
195
196 let (width, height) = map.size();
198 loop {
199 let mut counts: HashMap<usize, HashMap<u32, usize>> = HashMap::new();
200 for y in 0..height as u16 {
201 for x in 0..width as u16 {
202 let cell = HexCell::new(x, y);
203 let Some(region) = map.region_index_at(cell).filter(|region| is_bare_sea(*region) && !groups.sea_groups.contains_key(region)) else { continue };
204 for neighbour in cell.neighbours() {
205 let is_other_sea = map.region_index_at(neighbour).is_some_and(|other| other != region && regions.get(other).is_some_and(|other| !other.has_settlement));
206 if let Some(group) = groups.group_at(neighbour).filter(|_| is_other_sea) {
207 *counts.entry(region).or_default().entry(group).or_default() += 1;
208 }
209 }
210 }
211 }
212 if counts.is_empty() {
213 break;
214 }
215 for (region, counts) in counts {
216 let group = counts.into_iter().max_by_key(|(group, count)| (*count, std::cmp::Reverse(*group))).map(|(group, _)| group).unwrap_or_default();
217 groups.sea_groups.insert(region, group);
218 }
219 }
220 groups
221 }
222
223 pub fn port_cell(&self, port: HexCell) -> Option<HexCell> {
227 let is_sea = |cell: HexCell| self.map.region_index_at(cell).and_then(|region| self.regions.get(region)).is_some_and(|region| !region.has_settlement);
228 let mut seen = HashSet::from([port]);
229 let mut ring = vec![port];
230 while !ring.is_empty() {
231 let mut next = vec![];
232 for cell in ring {
233 for neighbour in cell.neighbours() {
234 if self.map.region_index_at(neighbour).is_none() || !seen.insert(neighbour) {
235 continue;
236 }
237 if is_sea(neighbour) {
238 return Some(neighbour);
239 }
240 next.push(neighbour);
241 }
242 }
243 ring = next;
244 }
245 None
246 }
247
248 pub fn group_at(&self, cell: HexCell) -> Option<u32> {
250 let region = self.map.region_index_at(cell)?;
251 let area = self.map.area_at(cell)?;
252 self.regions.get(region)?.area_groups.get(area).copied().or_else(|| self.sea_groups.get(®ion).copied())
253 }
254}
255
256pub fn theatre_key(map_data: &ESF) -> Result<String> {
262 as_record(map_data.root_node())
263 .and_then(|root| child_record(root, "REGIONS_DATA"))
264 .and_then(|regions| child_record(regions, "theatres"))
265 .and_then(|theatres| child_record(theatres, "CAMPAIGN_THEATRE"))
266 .and_then(|theatre| theatre.children().first())
267 .and_then(|group| leaves(group).find_map(as_str))
268 .map(str::to_owned)
269 .ok_or_else(|| RLibError::CampaignMapDataError("missing the key of REGIONS_DATA/theatres/CAMPAIGN_THEATRE".to_owned()))
270}
271
272pub fn pathfinder_nodes(hlp_data: &ESF) -> Result<HashMap<usize, Vec<PathfinderNode>>> {
281 let root = as_record(hlp_data.root_node()).ok_or_else(|| pathfinder_error("the root node is not a record"))?;
282 let pathfinder = child_record(root, "CAI_HIGH_LEVEL_PATHFINDER").ok_or_else(|| pathfinder_error("missing CAI_HIGH_LEVEL_PATHFINDER"))?;
283 let values = pathfinder.children().iter().flat_map(|group| leaves(group)).collect::<Vec<&NodeType>>();
284 let mut cursor = Cursor { values: &values, position: 0 };
285
286 let mut nodes = HashMap::new();
287 for _ in 0..cursor.next()? {
288 let region = cursor.next()? as usize;
289 let mut region_nodes = vec![];
290 for _ in 0..cursor.next()? {
291 let id = cursor.next()?;
292 let x = cursor.next()? as u16;
293 let y = cursor.next()? as u16;
294 cursor.next()?;
295 let edges = cursor.next()? as usize;
296
297 cursor.position += edges * 9 + edges * edges.saturating_sub(1);
299 region_nodes.push(PathfinderNode { area: id >> NODE_REGION_BITS, x, y });
300 }
301 nodes.insert(region, region_nodes);
302 }
303 Ok(nodes)
304}
305
306impl Cursor<'_> {
307
308 fn next(&mut self) -> Result<u32> {
310 let value = self.values.get(self.position).and_then(|node| as_u32(node)).ok_or_else(|| pathfinder_error("the node list ends early"))?;
311 self.position += 1;
312 Ok(value)
313 }
314}
315
316fn pathfinder_error(message: &str) -> RLibError {
318 RLibError::CampaignMapDataError(format!("high-level pathfinder: {message}"))
319}