rpfm_extensions/startpos/world/writer/
sight.rs1use std::cmp::Reverse;
26use std::collections::{BTreeMap, HashMap, HashSet};
27
28use rpfm_lib::error::Result;
29use rpfm_lib::files::esf::{Coordinates2DNode, NodeType, RecordNode};
30
31use super::{child, WorldInputs, WorldPlan};
32use crate::startpos::coords::{HexCell, MapScale};
33use crate::startpos::layout::{child_path, StartposLayout};
34use crate::startpos::settlements::SettlementLayout;
35use crate::startpos::vision::{Vision, VisionGrid, WorldBounds};
36
37#[cfg(test)] #[path = "sight_test.rs"] mod sight_test;
38
39const SETTLEMENT_SIGHT: f32 = 5.0;
41
42const FILLER_STEPS: usize = 2;
44
45const FILLER_SLOTS: u32 = 0b11;
47
48const FILLER_MARGIN: f32 = 16.0;
50
51const FILLER_BUCKETS: u32 = 4;
53
54const FILLER_TILES: f32 = 16.0;
56
57const REGION_SIGHT: f32 = 1.7;
60
61const SHROUD_LAYERS: [&str; 3] = ["QUAD_TREE_BIT_ARRAY", "QUAD_TREE_BIT_ARRAY@2", "QUAD_TREE_BIT_ARRAY@3"];
63
64#[derive(Debug, Clone)]
70pub struct SightPlan {
71
72 grid: VisionGrid,
74
75 scale: MapScale,
77
78 regions: HashMap<String, Vision>,
80
81 settlements: HashMap<String, Vision>,
83
84 fillers: Vec<Filler>,
86
87 filler_tile_size: (f32, f32),
89}
90
91#[derive(Debug, Clone)]
93struct Filler {
94
95 cell: HexCell,
97
98 vision_cell: (u32, u32),
100
101 vision: Vision,
103
104 bounds: WorldBounds,
106}
107
108impl SightPlan {
113
114 pub fn new(inputs: &WorldInputs, plan: &WorldPlan) -> Self {
116 let grid = VisionGrid::new(inputs.region_map.world_size());
117 let scale = inputs.region_map.scale();
118 let cells = inputs.region_map.cells_by_region();
119
120 let mut regions = HashMap::new();
121 let mut settlements = HashMap::new();
122 let mut fillers = vec![];
123 for region in plan.regions() {
124 let corners = cells.get(region.key().as_str()).map(|cells| cells.iter().map(|cell| scale.world_position(*cell)).collect::<Vec<_>>()).unwrap_or_default();
125 regions.insert(region.key().to_owned(), grid.around(&corners, REGION_SIGHT));
126
127 let Some(settlement) = inputs.settlements.iter().find(|settlement| settlement.region() == region.key()) else { continue };
128 if let Some(position) = settlement.slots().first().and_then(|slot| slot.position()) {
129 settlements.insert(region.key().to_owned(), grid.disk(scale.world_position(position), SETTLEMENT_SIGHT));
130 let (vision, bounds) = filler_vision(&grid, &scale, settlement);
131 fillers.push(Filler { cell: position, vision_cell: grid.cell_at(scale.world_position(position)), vision, bounds });
132 }
133 }
134
135 let (width, height) = inputs.region_map.world_size();
136 let filler_tile_size = (width / FILLER_TILES, height / FILLER_TILES);
137 Self { grid, scale, regions, settlements, fillers, filler_tile_size }
138 }
139
140 pub fn grid(&self) -> &VisionGrid {
142 &self.grid
143 }
144
145 pub fn region(&self, region: &str) -> Option<&Vision> {
147 self.regions.get(region)
148 }
149
150 pub fn settlement(&self, region: &str) -> Option<&Vision> {
152 self.settlements.get(region)
153 }
154}
155
156pub fn write_line_of_sight(layout: &StartposLayout, parent: &str, grid: &VisionGrid, vision: Option<&Vision>) -> Result<RecordNode> {
163 let bounds = vision.and_then(|vision| vision.bounds(grid)).unwrap_or_default();
164 line_of_sight(layout, parent, grid, vision, bounds)
165}
166
167fn line_of_sight(layout: &StartposLayout, parent: &str, grid: &VisionGrid, vision: Option<&Vision>, bounds: WorldBounds) -> Result<RecordNode> {
169 let path = format!("{parent}/LINE_OF_SIGHT");
170 let mut record = layout.builder(&path)?;
171 let Some(vision) = vision else {
172 record.group()?
173 .set_bool("^+1", false)?
174 .unset("^+2")?
175 .unset("^+3")?
176 .set_record("QUAD_TREE_BIT_ARRAY", None)?;
177 return record.build();
178 };
179
180 let ((min_x, min_y), (max_x, max_y)) = bounds;
181 record.group()?
182 .set_bool("^+1", true)?
183 .set("^+2", coordinates(min_x, min_y))?
184 .set("^+3", coordinates(max_x, max_y))?
185 .set_record("QUAD_TREE_BIT_ARRAY", Some(write_quad_tree(layout, &path, grid, vision)?))?;
186 record.build()
187}
188
189pub fn write_shroud(layout: &StartposLayout, faction_path: &str, sight: &SightPlan, vision: &Vision) -> Result<RecordNode> {
203 let path = format!("{faction_path}/CAMPAIGN_SHROUD");
204 let layers = [vision, vision, &Vision::default()];
205 let mut record = layout.builder(&path)?;
206 let group = record.group()?;
207 for (key, layer) in SHROUD_LAYERS.iter().zip(layers) {
208 group.set_record(key, Some(write_quad_tree(layout, &path, &sight.grid, layer)?))?;
209 }
210 group.set_record("CAMPAIGN_SHROUD_FILLERS", Some(write_fillers(layout, &path, sight, vision)?))?;
211 record.build()
212}
213
214fn write_fillers(layout: &StartposLayout, shroud_path: &str, sight: &SightPlan, vision: &Vision) -> Result<RecordNode> {
218 let path = child_path(shroud_path, "CAMPAIGN_SHROUD_FILLERS");
219 let block_path = child_path(&path, "FILLERS_QUADTREE_DATA_BLOCK");
220 let los_path = child_path(&block_path, "FILLERS_QUADTREE_DATA_LOS_BLOCK");
221 let (tile_width, tile_height) = sight.filler_tile_size;
222
223 let mut tiles = BTreeMap::<(Reverse<u32>, u32), Vec<&Filler>>::new();
224 for filler in sight.fillers.iter().filter(|filler| !vision.contains(filler.vision_cell)) {
225 let (x, y) = sight.scale.world_position(filler.cell);
226 tiles.entry((Reverse((y / tile_height) as u32), (x / tile_width) as u32)).or_default().push(filler);
227 }
228
229 let mut block = layout.builder(&block_path)?;
230 for ((Reverse(row), column), mut fillers) in tiles {
231 fillers.sort_by_key(|filler| filler_bucket(filler.cell));
232 let (mut min_x, mut min_y) = (column as f32 * tile_width, row as f32 * tile_height);
233 let (mut max_x, mut max_y) = (min_x + tile_width, min_y + tile_height);
234 let mut los_block = layout.builder(&los_path)?;
235 for filler in fillers {
236 let ((x0, y0), (x1, y1)) = filler.bounds;
237 (min_x, min_y, max_x, max_y) = (min_x.min(x0), min_y.min(y0), max_x.max(x1), max_y.max(y1));
238 let line_of_sight = line_of_sight(layout, &los_path, &sight.grid, Some(&filler.vision), filler.bounds)?;
239 los_block.push_children(vec![NodeType::U16(filler.cell.x()), NodeType::U16(filler.cell.y()), NodeType::Record(Box::new(line_of_sight))]);
240 }
241 block.push_children(vec![coordinates(min_x, min_y), coordinates(max_x, max_y), NodeType::Record(Box::new(los_block.build()?))]);
242 }
243
244 let mut record = layout.builder(&path)?;
245 record.group()?.set_record("FILLERS_QUADTREE_DATA_BLOCK", Some(block.build()?))?;
246 record.build()
247}
248
249fn filler_bucket(cell: HexCell) -> u32 {
252 (u32::from(cell.y()) * 0x3f8 + u32::from(cell.x())) % FILLER_BUCKETS
253}
254
255fn filler_vision(grid: &VisionGrid, scale: &MapScale, settlement: &SettlementLayout) -> (Vision, WorldBounds) {
258 let zones = settlement.sprawl().iter().filter(|cell| cell.zone_slots() & FILLER_SLOTS != 0).map(|cell| cell.cell()).collect::<HashSet<_>>();
259 if zones.is_empty() {
260 return (Vision::default(), ((0.0, 0.0), (0.0, 0.0)));
261 }
262 let corners = zones.iter().map(|cell| scale.world_position(*cell)).collect::<Vec<_>>();
263 let min = corners.iter().fold((f32::MAX, f32::MAX), |min, corner| (min.0.min(corner.0), min.1.min(corner.1)));
264 let max = corners.iter().fold((f32::MIN, f32::MIN), |max, corner| (max.0.max(corner.0), max.1.max(corner.1)));
265
266 let mut revealed = zones;
267 for _ in 0..FILLER_STEPS {
268 let ring = revealed.iter().flat_map(|cell| cell.neighbours()).collect::<Vec<_>>();
269 revealed.extend(ring);
270 }
271 let margin = FILLER_MARGIN * scale.x();
272 let min = (min.0 - margin, min.1 - margin);
273 let max = (max.0 + margin, max.1 + margin);
274 grid.cells_where(min, max, |(x, y)| revealed.contains(&scale.cell_at(x, y)))
275}
276
277fn write_quad_tree(layout: &StartposLayout, parent: &str, grid: &VisionGrid, vision: &Vision) -> Result<RecordNode> {
279 let (width, height, tree_size) = grid.dimensions();
280 let mut record = child(layout, parent, "QUAD_TREE_BIT_ARRAY")?;
281 record.group()?
282 .set_u32("^+1", width)?
283 .set_u32("^+2", height)?
284 .set_u32("^+3", tree_size)?
285 .set_bytes("^+4", vision.encode(grid))?;
286 record.build()
287}
288
289fn coordinates(x: f32, y: f32) -> NodeType {
291 let mut node = Coordinates2DNode::default();
292 node.set_x(x);
293 node.set_y(y);
294 NodeType::Coord2d(node)
295}