rpfm_extensions/startpos/cai/
regions.rs1use getset::Setters;
16
17use std::collections::HashMap;
18
19use rpfm_lib::error::Result;
20use rpfm_lib::files::esf::RecordNode;
21
22use super::map::{MapRegion, PathfinderNode};
23use super::plan::CaiPlan;
24use super::world_view::WorldView;
25use crate::startpos::coords::HexCell;
26use crate::startpos::region_map::RegionMap;
27use super::{component, CAI_WORLD_PATH};
28use crate::startpos::layout::{child_path, StartposLayout};
29
30#[cfg(test)] #[path = "regions_test.rs"] mod regions_test;
31
32pub const THEATRE_VALUE: u32 = 3;
34
35#[derive(Debug, Clone, Default, Setters)]
41#[getset(set = "pub")]
42pub struct RegionLinks {
43
44 settlement: u32,
46
47 boundaries: Vec<u32>,
49
50 mobiles: Vec<u32>,
52
53 world_id: u32,
55
56 province: u32,
58
59 sea_index: u32,
61
62 port_regions: Vec<u32>,
64
65 port_sea: u32,
67
68 faction_seas: Vec<u32>,
70
71 victory_factions: Vec<u32>,
73}
74
75#[derive(Debug, Clone, Copy)]
77pub struct LinkInputs<'a> {
78
79 pub plan: &'a CaiPlan,
81
82 pub world: &'a WorldView,
84
85 pub map_regions: &'a [MapRegion],
87
88 pub region_map: &'a RegionMap,
90
91 pub mobiles: &'a [(u32, HexCell)],
93
94 pub ports: &'a HashMap<u32, HexCell>,
96
97 pub victory_factions: &'a HashMap<String, Vec<u32>>,
99}
100
101pub fn region_links(inputs: &LinkInputs) -> Vec<RegionLinks> {
107 let plan = inputs.plan;
108 let world = inputs.world;
109 let mut links = vec![RegionLinks::default(); inputs.map_regions.len()];
110
111 for boundary in plan.boundaries() {
112 for region in [boundary.region(), boundary.other()] {
113 links[region].boundaries.push(boundary.id());
114 }
115 }
116 for (mobile, cell) in inputs.mobiles {
117 if let Some(region) = inputs.region_map.region_index_at(*cell).filter(|region| *region < links.len()) {
118 links[region].mobiles.push(*mobile);
119 }
120 }
121
122 let index_of = inputs.map_regions.iter().enumerate().map(|(index, region)| (region.key().as_str(), index)).collect::<HashMap<_, _>>();
123 for (index, region) in inputs.map_regions.iter().enumerate() {
124 let link = &mut links[index];
125 link.victory_factions = inputs.victory_factions.get(region.key()).cloned().unwrap_or_default();
126 if let Some(settlement) = plan.settlements().get(&index) {
127 link.settlement = settlement.settlement();
128 }
129 if let Some(world_region) = world.regions().iter().find(|world_region| world_region.key() == region.key()) {
130 link.world_id = world_region.id();
131 }
132 if let Some(province) = world.provinces().iter().zip(plan.provinces()).find_map(|(province, ids)| province.regions().contains(region.key()).then(|| ids.province())) {
133 link.province = province;
134 }
135 if let Some((sea, ids)) = world.seas().iter().zip(plan.faction_seas()).find(|(sea, _)| sea.key() == region.key()) {
136 link.sea_index = sea.id();
137 link.faction_seas = ids.to_vec();
138 }
139 }
140
141 let mut world_regions = world.regions().iter().collect::<Vec<_>>();
143 world_regions.sort_by_key(|world_region| world_region.id());
144 for world_region in world_regions {
145 let (Some(cell), Some(land)) = (inputs.ports.get(&world_region.residence()), index_of.get(world_region.key().as_str())) else { continue };
146 let Some(sea) = inputs.region_map.region_index_at(*cell).filter(|sea| *sea < links.len()) else { continue };
147 links[*land].port_sea = plan.regions()[sea].id();
148 links[sea].port_regions.push(plan.regions()[*land].id());
149 }
150 links
151}
152
153pub fn write_theatres(layout: &StartposLayout, plan: &CaiPlan, key: &str, mobiles: Vec<u32>) -> Result<RecordNode> {
166 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_THEATRES");
167 let mut theatre = layout.builder(&child_path(&path, "CAI_THEATRE"))?;
168 theatre.group()?
169 .set_str("^+1", key)?
170 .set_u32s("^+2", mobiles)?
171 .set_u32s("^+4", plan.regions().iter().map(|region| region.id()).collect())?
172 .set_u32("^+6", THEATRE_VALUE)?;
173
174 let mut theatres = layout.builder(&path)?;
175 theatres.push_group()?
176 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, plan.theatre(), vec![])?))?
177 .set_record("CAI_THEATRE", Some(theatre.build()?))?;
178 theatres.build()
179}
180
181pub fn write_regions(layout: &StartposLayout, plan: &CaiPlan, map_regions: &[MapRegion], nodes: &HashMap<usize, Vec<PathfinderNode>>, links: &[RegionLinks]) -> Result<RecordNode> {
195 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_REGIONS");
196 let region_path = child_path(&path, "CAI_REGION");
197 let owned_path = child_path(&path, "OWNED_INDIRECT");
198 let empty = RegionLinks::default();
199
200 let mut regions = layout.builder(&path)?;
201 for (index, (region, ids)) in map_regions.iter().zip(plan.regions()).enumerate() {
202 let links = links.get(index).unwrap_or(&empty);
203 let (x, y) = region_node(region, nodes.get(&index)).map(|node| (node.x(), node.y())).unwrap_or((0, 0));
204
205 let mut owned = layout.builder(&owned_path)?;
206 owned.group()?.set_u32("^+1", links.settlement)?;
207
208 let mut record = layout.builder(®ion_path)?;
209 record.group()?
210 .set_u32s("^+1", vec![plan.theatre()])?
211 .set_u32s("^+2", ids.hlcis().to_vec())?
212 .set_u32("^+3", links.settlement)?
213 .set_u32("^+5", u32::from(x))?
214 .set_u32("^+6", u32::from(y))?
215 .set_u32s("^+8", links.boundaries.to_vec())?
216 .set_bool("^+9", region.connects_to_other_kind())?
217 .set_u32s("^+10", links.mobiles.to_vec())?
218 .set_str("^+11", region.key())?
219 .set_u32("^+12", links.world_id)?
220 .set_u32s("^+13", links.victory_factions.to_vec())?
221 .set_u32("^+14", links.province)?
222 .set_u32("^+15", links.sea_index)?
223 .set_u32s("^+16", links.port_regions.to_vec())?
224 .set_u32("^+17", links.port_sea)?
225 .set_u32s("^+18", links.faction_seas.to_vec())?;
226
227 regions.push_group()?
228 .set_record("OWNED_INDIRECT", Some(owned.build()?))?
229 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, ids.id(), vec![])?))?
230 .set_record("CAI_REGION", Some(record.build()?))?;
231 }
232 regions.build()
233}
234
235pub fn write_hlcis(layout: &StartposLayout, plan: &CaiPlan, map_regions: &[MapRegion], nodes: &HashMap<usize, Vec<PathfinderNode>>) -> Result<RecordNode> {
248 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_REGION_HLCIS");
249 let hlci_path = child_path(&path, "CAI_REGION_HLCI");
250
251 let mut hlcis = layout.builder(&path)?;
252 for (index, (region, ids)) in map_regions.iter().zip(plan.regions()).enumerate() {
253 let node = region_node(region, nodes.get(&index));
254 let main = node.and_then(|node| region.hlci_of_area(node.area() as usize));
255 for (hlci_index, ((group, areas), id)) in region.hlcis().into_iter().zip(ids.hlcis()).enumerate() {
256 let (x, y) = node.filter(|_| main == Some(hlci_index)).map(|node| (node.x(), node.y())).unwrap_or((0, 0));
257 let mut record = layout.builder(&hlci_path)?;
258 record.group()?
259 .set_u32("^+1", ids.id())?
260 .set_u32("^+2", group)?
261 .set_u32s("^+3", areas)?
262 .set_u32("^+4", u32::from(x))?
263 .set_u32("^+5", u32::from(y))?;
264
265 hlcis.push_group()?
266 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, *id, vec![])?))?
267 .set_record("CAI_REGION_HLCI", Some(record.build()?))?;
268 }
269 }
270 hlcis.build()
271}
272
273pub fn region_positions(map_regions: &[MapRegion], nodes: &HashMap<usize, Vec<PathfinderNode>>) -> Vec<(u16, u16)> {
280 map_regions.iter().enumerate()
281 .map(|(index, region)| region_node(region, nodes.get(&index)).map(|node| (node.x(), node.y())).unwrap_or((0, 0)))
282 .collect()
283}
284
285fn region_node(region: &MapRegion, nodes: Option<&Vec<PathfinderNode>>) -> Option<PathfinderNode> {
287 let nodes = nodes?;
288 let largest = region.largest_area();
289 nodes.iter().find(|node| Some(node.area() as usize) == largest).or_else(|| nodes.first()).copied()
290}