rpfm_extensions/startpos/
settlements.rs1use getset::{CopyGetters, Getters};
20use serde_derive::{Deserialize, Serialize};
21
22use std::cmp::Reverse;
23use std::collections::HashSet;
24
25use rpfm_lib::error::{RLibError, Result};
26use rpfm_lib::files::esf::{ESF, NodeType, RecordNode};
27
28use super::coords::{HexCell, HEX_EDGES};
29use super::esf_nav::{as_record, as_str, as_u32, cell_groups, child_record, child_records, leaves};
30
31#[cfg(test)] #[path = "settlements_test.rs"] mod settlements_test;
32
33const WALL_SCAN_OFFSET: u8 = 4;
35
36#[derive(Debug, Clone, PartialEq, Default, Getters, Serialize, Deserialize)]
42#[getset(get = "pub")]
43pub struct SettlementLayout {
44
45 region: String,
47
48 slots: Vec<SlotLayout>,
50
51 sprawl: Vec<SprawlCell>,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
57pub enum SlotKind {
58
59 #[default]
61 Primary,
62
63 Secondary,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Default, CopyGetters, Getters, Serialize, Deserialize)]
69pub struct SlotLayout {
70
71 #[getset(get_copy = "pub")]
73 id: u32,
74
75 #[getset(get_copy = "pub")]
77 kind: SlotKind,
78
79 #[getset(get_copy = "pub")]
81 position: Option<HexCell>,
82
83 #[getset(get = "pub")]
85 footprint: Vec<HexCell>,
86
87 #[getset(get = "pub")]
89 zone: Vec<HexCell>,
90
91 #[getset(get = "pub")]
93 walls: Vec<HexCell>,
94
95 #[getset(get_copy = "pub")]
98 touching_slots: u32,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, CopyGetters, Serialize, Deserialize)]
103#[getset(get_copy = "pub")]
104pub struct SprawlCell {
105
106 cell: HexCell,
108
109 in_footprint: bool,
111
112 in_zone: bool,
114
115 zone_slots: u32,
117}
118
119impl SettlementLayout {
124
125 pub fn all_from_map_data(map_data: &ESF, is_walled: impl Fn(&str) -> bool) -> Result<Vec<Self>> {
140 let mut layouts = vec![];
141 for region in region_data_records(map_data)? {
142 let key = region_key(region)?;
143 if let Some(info) = child_record(region, "SETTLEMENT_INFO") {
144 layouts.push(Self::from_settlement_info(key, info, is_walled(key))?);
145 }
146 }
147 Ok(layouts)
148 }
149
150 pub fn from_settlement_info(region: &str, info: &RecordNode, walled: bool) -> Result<Self> {
162 let map_slots = child_records(info, "SLOT_ARRAY_BLOCK")
163 .flat_map(|block| block.children().iter())
164 .map(|group| map_slot(region, group))
165 .collect::<Result<Vec<_>>>()?;
166
167 let (main_position, main_zone) = map_slots.first().cloned()
168 .ok_or_else(|| map_error(region, "the settlement has no slots"))?;
169
170 let port_position = child_record(info, "PORT_POSITIONS_BLOCK").and_then(|block| cell_groups(block).first().copied());
171 let port_zone = child_record(info, "PORT_AREA_BLOCK").map(cell_groups).unwrap_or_default();
172
173 let mut slots = vec![SlotLayout::new(0, SlotKind::Primary, Some(main_position), main_zone, walled)];
174 slots.push(match port_position {
175 Some(position) => SlotLayout::new(1, SlotKind::Primary, Some(position), port_zone, walled),
176 None => SlotLayout::default(),
177 });
178
179 for (index, (position, zone)) in map_slots.into_iter().enumerate().skip(1) {
181 slots.push(SlotLayout::new(index as u32 + 1, SlotKind::Secondary, Some(position), zone, walled));
182 }
183
184 link_touching_slots(&mut slots);
185
186 let sprawl_cells = child_record(info, "LOGICAL_POSITION_BIT_ARRAY")
187 .ok_or_else(|| map_error(region, "missing LOGICAL_POSITION_BIT_ARRAY"))
188 .and_then(|bits| bit_array_cells(region, bits))?;
189
190 Ok(Self {
191 region: region.to_owned(),
192 sprawl: sprawl(&sprawl_cells, &slots),
193 slots,
194 })
195 }
196}
197
198impl SlotLayout {
199
200 fn new(id: u32, kind: SlotKind, position: Option<HexCell>, zone: Vec<HexCell>, walled: bool) -> Self {
202 let walls = if walled { wall_walk(&zone) } else { vec![] };
203 Self {
204 id,
205 kind,
206 footprint: position.map(|position| position.footprint()).unwrap_or_default(),
207 position,
208 zone,
209 walls,
210 touching_slots: 0,
211 }
212 }
213}
214
215pub(crate) fn region_data_records(map_data: &ESF) -> Result<Vec<&RecordNode>> {
217 let root = as_record(map_data.root_node()).ok_or_else(|| RLibError::CampaignMapDataError("the root node is not a record".to_owned()))?;
218 let block = child_record(root, "REGIONS_DATA")
219 .and_then(|regions_data| child_record(regions_data, "REGIONS_BLOCK"))
220 .ok_or_else(|| RLibError::CampaignMapDataError("missing MAP_DATA/REGIONS_DATA/REGIONS_BLOCK".to_owned()))?;
221 Ok(child_records(block, "REGION_DATA").collect())
222}
223
224pub(crate) fn region_key(region: &RecordNode) -> Result<&str> {
226 region.children().first()
227 .and_then(|group| leaves(group).find_map(as_str))
228 .ok_or_else(|| RLibError::CampaignMapDataError("a REGION_DATA record has no key".to_owned()))
229}
230
231fn map_slot(region: &str, group: &[NodeType]) -> Result<(HexCell, Vec<HexCell>)> {
233 let record = |name: &str| group.iter().filter_map(as_record).find(|record| record.name() == name);
234 let position = record("SLOT_POSITIONS_BLOCK")
235 .and_then(|positions| cell_groups(positions).first().copied())
236 .ok_or_else(|| map_error(region, "a slot has no position"))?;
237 let zone = record("SLOT_AREA_BLOCK").map(cell_groups).unwrap_or_default();
238 Ok((position, zone))
239}
240
241fn bit_array_cells(region: &str, bits: &RecordNode) -> Result<Vec<HexCell>> {
243 let group = bits.children().first().ok_or_else(|| map_error(region, "empty LOGICAL_POSITION_BIT_ARRAY"))?;
244 let values = leaves(group).collect::<Vec<_>>();
245 let number = |index: usize| values.get(index).and_then(|node| as_u32(node));
246 let (x0, y0, width) = match (number(0), number(1), number(4)) {
247 (Some(x0), Some(y0), Some(width)) if width > 0 => (x0, y0, width),
248 _ => return Err(map_error(region, "malformed LOGICAL_POSITION_BIT_ARRAY header")),
249 };
250 let bytes = match values.get(5) {
251 Some(NodeType::U8Array(bytes)) => bytes,
252 _ => return Err(map_error(region, "LOGICAL_POSITION_BIT_ARRAY has no bitmap")),
253 };
254
255 let cells = bytes.iter().enumerate()
256 .flat_map(|(byte_index, byte)| (0..8).filter(move |bit| byte & (1 << bit) != 0).map(move |bit| byte_index as u32 * 8 + bit))
257 .map(|index| HexCell::new((x0 + index % width) as u16, (y0 + index / width) as u16))
258 .collect();
259 Ok(cells)
260}
261
262fn sprawl(cells: &[HexCell], slots: &[SlotLayout]) -> Vec<SprawlCell> {
264 let footprint = slots.iter().flat_map(|slot| slot.footprint.iter().copied()).collect::<HashSet<_>>();
265 let zones = slots.iter().map(|slot| slot.zone.iter().copied().collect::<HashSet<_>>()).collect::<Vec<_>>();
266 cells.iter().map(|cell| {
267 let zone_slots = zones.iter().enumerate()
268 .filter(|(_, zone)| zone.contains(cell))
269 .fold(0, |mask, (index, _)| mask | (1 << index));
270 SprawlCell {
271 cell: *cell,
272 in_footprint: footprint.contains(cell),
273 in_zone: zone_slots != 0,
274 zone_slots,
275 }
276 }).collect()
277}
278
279fn link_touching_slots(slots: &mut [SlotLayout]) {
281 let reaches = slots.iter()
282 .map(|slot| slot.zone.iter().flat_map(HexCell::footprint).collect::<HashSet<_>>())
283 .collect::<Vec<_>>();
284 let masks = reaches.iter().enumerate().map(|(index, reach)| {
285 slots.iter().enumerate()
286 .filter(|(other, slot)| *other != index && slot.zone.iter().any(|cell| reach.contains(cell)))
287 .fold(0, |mask, (other, _)| mask | 1u32.checked_shl(other as u32).unwrap_or_default())
288 }).collect::<Vec<_>>();
289
290 for (slot, mask) in slots.iter_mut().zip(masks) {
291 slot.touching_slots = mask;
292 }
293}
294
295fn wall_walk(zone: &[HexCell]) -> Vec<HexCell> {
302 let cells = zone.iter().copied().collect::<HashSet<_>>();
303 let Some(start) = zone.iter().copied().min_by_key(|cell| (Reverse(cell.x()), cell.y())) else { return vec![] };
304
305 let max_steps = zone.len() * usize::from(HEX_EDGES) + 1;
307 let mut walk = Vec::new();
308 let (mut cell, mut incoming, mut first_step) = (start, 0, None);
309 while walk.len() < max_steps {
310 let next = (0..HEX_EDGES)
311 .map(|offset| (incoming + WALL_SCAN_OFFSET + offset) % HEX_EDGES)
312 .find_map(|direction| cell.neighbour(direction).filter(|neighbour| cells.contains(neighbour)).map(|neighbour| (direction, neighbour)));
313 let Some((direction, neighbour)) = next else { return vec![start] };
314 if cell == start && first_step == Some(direction) {
315 break;
316 }
317 first_step.get_or_insert(direction);
318 walk.push(neighbour);
319 (cell, incoming) = (neighbour, direction);
320 }
321 walk
322}
323
324fn map_error(region: &str, message: &str) -> RLibError {
325 RLibError::CampaignMapDataError(format!("region {region}: {message}"))
326}