rpfm_extensions/startpos/
region_map.rs1use std::collections::{HashMap, HashSet};
21
22use rpfm_lib::error::{RLibError, Result};
23use rpfm_lib::files::esf::{ESF, NodeType};
24
25use super::coords::{HexCell, MapScale};
26use super::esf_nav::{as_record, as_u32, child_record, leaves};
27use super::settlements::{region_data_records, region_key};
28
29#[cfg(test)] #[path = "region_map_test.rs"] pub(crate) mod region_map_test;
30
31const BYTES_PER_CELL: usize = 7;
33
34const EDGE_MASKS_OFFSET: usize = 5;
36
37const AREA_OFFSET: usize = 1;
39
40#[derive(Debug, Clone, PartialEq, Default)]
46pub struct RegionMap {
47
48 keys: Vec<String>,
50
51 width: u32,
53
54 height: u32,
56
57 cells: Vec<u8>,
59
60 world_size: (f32, f32),
62}
63
64impl RegionMap {
69
70 pub fn from_map_data(map_data: &ESF) -> Result<Self> {
80 let keys = region_data_records(map_data)?.into_iter()
81 .map(|region| region_key(region).map(str::to_owned))
82 .collect::<Result<Vec<_>>>()?;
83
84 let missing = || RLibError::CampaignMapDataError("missing REGIONS_DATA/theatres/CAMPAIGN_THEATRE/HEX_MAP_DATA".to_owned());
85 let theatre = as_record(map_data.root_node())
86 .and_then(|root| child_record(root, "REGIONS_DATA"))
87 .and_then(|regions| child_record(regions, "theatres"))
88 .and_then(|theatres| child_record(theatres, "CAMPAIGN_THEATRE"))
89 .ok_or_else(missing)?;
90 let hex_map = child_record(theatre, "HEX_MAP_DATA").ok_or_else(missing)?;
91
92 let world_size = theatre.children().first()
94 .and_then(|group| group.iter().filter_map(|node| match node { NodeType::Coord2d(corner) => Some((*corner.x(), *corner.y())), _ => None }).nth(1))
95 .ok_or_else(|| RLibError::CampaignMapDataError("missing the size of REGIONS_DATA/theatres/CAMPAIGN_THEATRE".to_owned()))?;
96
97 let group = hex_map.children().first().ok_or_else(missing)?;
98 let mut values = leaves(group);
99 let width = values.next().and_then(as_u32).ok_or_else(missing)?;
100 let height = values.next().and_then(as_u32).ok_or_else(missing)?;
101 let cells = match values.next() {
102 Some(NodeType::U8Array(cells)) => cells.to_owned(),
103 _ => return Err(missing()),
104 };
105
106 if cells.len() < width as usize * height as usize * BYTES_PER_CELL {
107 return Err(RLibError::CampaignMapDataError(format!("the hex map has {} bytes, expected {width}x{height} cells", cells.len())));
108 }
109
110 Ok(Self { keys, width, height, cells, world_size })
111 }
112
113 pub fn world_size(&self) -> (f32, f32) {
115 self.world_size
116 }
117
118 pub fn scale(&self) -> MapScale {
120 MapScale::new((self.width, self.height), self.world_size)
121 }
122
123 pub fn keys(&self) -> &[String] {
125 &self.keys
126 }
127
128 pub fn region_at(&self, cell: HexCell) -> Option<&str> {
134 let index = self.cell_offset(cell)?;
135 self.keys.get(usize::from(self.cells[index])).map(String::as_str)
136 }
137
138 pub fn region_index_at(&self, cell: HexCell) -> Option<usize> {
140 self.cell_offset(cell).map(|index| usize::from(self.cells[index]))
141 }
142
143 pub fn area_at(&self, cell: HexCell) -> Option<usize> {
146 self.cell_offset(cell).map(|index| usize::from(self.cells[index + AREA_OFFSET] >> 1))
147 }
148
149 pub fn size(&self) -> (u32, u32) {
151 (self.width, self.height)
152 }
153
154 pub fn edge_masks(&self, cell: HexCell) -> [u8; 2] {
157 self.cell_offset(cell)
158 .map(|index| [self.cells[index + EDGE_MASKS_OFFSET], self.cells[index + EDGE_MASKS_OFFSET + 1]])
159 .unwrap_or_default()
160 }
161
162 pub fn cells_by_region(&self) -> HashMap<&str, Vec<HexCell>> {
164 let mut cells = HashMap::<&str, Vec<HexCell>>::new();
165 for y in 0..self.height {
166 for x in 0..self.width {
167 let cell = HexCell::new(x as u16, y as u16);
168 if let Some(region) = self.region_at(cell) {
169 cells.entry(region).or_default().push(cell);
170 }
171 }
172 }
173 cells
174 }
175
176 pub fn neighbour_pairs(&self) -> HashSet<(&str, &str)> {
178 let mut pairs = HashSet::new();
179 for y in 0..self.height {
180 for x in 0..self.width {
181 let cell = HexCell::new(x as u16, y as u16);
182 let Some(region) = self.region_at(cell) else { continue };
183 for neighbour in cell.neighbours() {
184 if let Some(other) = self.region_at(neighbour).filter(|other| *other != region) {
185 pairs.insert(if region < other { (region, other) } else { (other, region) });
186 }
187 }
188 }
189 }
190 pairs
191 }
192
193 fn cell_offset(&self, cell: HexCell) -> Option<usize> {
195 if u32::from(cell.x()) >= self.width || u32::from(cell.y()) >= self.height {
196 return None;
197 }
198 Some((u32::from(cell.y()) * self.width + u32::from(cell.x())) as usize * BYTES_PER_CELL)
199 }
200}