1use std::collections::HashMap;
17
18use rpfm_lib::error::Result;
19use rpfm_lib::files::esf::RecordNode;
20
21use super::sight::{write_line_of_sight, SightPlan};
22use super::{child, horde_details, next_id, WorldInputs, WorldPlan, REBEL_FACTION_ID, WORLD_PATH};
23use crate::startpos::tables::{Row, StartposTables};
24use crate::startpos::vision::Vision;
25use crate::startpos::world::pools::{province_mercenary_pool, MercenaryUnit};
26use crate::startpos::world::provinces::{province_managers, sea_regions, ProvinceManager, SeaRegion};
27
28const PROVINCE_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/PROVINCE_MANAGER/PROVINCE_ARRAY/PROVINCE";
30
31const PROVINCE_MANAGER_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/PROVINCE_MANAGER/PROVINCE_ARRAY/PROVINCE/FACTION_PROVINCE_MANAGER_ARRAY/FACTION_PROVINCE_MANAGER";
33
34const SEA_REGION_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/SEA_REGION_MANAGER/SEA_REGIONS_BLOCK/SEA_REGION";
36
37const SEA_MANAGER_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/SEA_REGION_MANAGER/SEA_REGIONS_BLOCK/SEA_REGION/FACTION_SEA_REGION_MANAGER_BLOCK/FACTION_SEA_REGION_MANAGER";
39
40const MAX_PUBLIC_ORDER: i64 = 100;
42
43const HAPPINESS_THRESHOLDS: [i64; 6] = [80, 50, 20, -20, -50, -80];
46
47struct ProvinceWriter<'a, 'b> {
53
54 inputs: &'b WorldInputs<'a>,
56
57 plan: &'b WorldPlan<'a>,
59
60 sight: &'b SightPlan,
62
63 regions: HashMap<String, Row<'a>>,
65
66 next_recruitment: u32,
68
69 next_pool: u32,
71
72 province_ids: HashMap<String, u32>,
74}
75
76#[derive(Debug, Clone)]
78pub struct ProvinceRecords {
79
80 pub provinces: RecordNode,
82
83 pub seas: RecordNode,
85
86 pub next_recruitment: u32,
88
89 pub next_pool: u32,
91
92 pub province_ids: HashMap<String, u32>,
94}
95
96pub fn write_province_and_sea_managers(inputs: &WorldInputs, plan: &WorldPlan, sight: &SightPlan) -> Result<ProvinceRecords> {
110 let mut regions = HashMap::new();
111 for row in inputs.tables.table("start_pos_regions")?.rows() {
112 if row.str("campaign")? == *inputs.index.campaign() {
113 regions.insert(row.str("region")?.into_owned(), row);
114 }
115 }
116
117 let first_recruitment = u32::try_from(horde_details(inputs)?.len()).unwrap_or(u32::MAX).saturating_add(1);
118 let first_pool = u32::try_from(plan.factions().len()).unwrap_or(u32::MAX).saturating_add(REBEL_FACTION_ID + 1);
120 let mut writer = ProvinceWriter { inputs, plan, sight, regions, next_recruitment: first_recruitment, next_pool: first_pool, province_ids: HashMap::new() };
121 let provinces = writer.write_province_manager()?;
122 let seas = writer.write_sea_region_manager()?;
123 Ok(ProvinceRecords {
124 provinces,
125 seas,
126 next_recruitment: writer.next_recruitment,
127 next_pool: writer.next_pool,
128 province_ids: writer.province_ids,
129 })
130}
131
132impl ProvinceWriter<'_, '_> {
133
134 fn write_province_manager(&mut self) -> Result<RecordNode> {
136 let inputs = self.inputs;
137 let managers = province_managers(inputs.tables, inputs.index)?;
138
139 let provinces = campaign_provinces(inputs.tables, self.plan)?;
140
141 let mut next_manager = 1;
142 let mut array = child(inputs.layout, &format!("{WORLD_PATH}/PROVINCE_MANAGER"), "PROVINCE_ARRAY")?;
143 for (index, (province, regions)) in provinces.iter().enumerate() {
144 let province_id = u32::try_from(index + 1).unwrap_or(u32::MAX);
145 self.province_ids.insert(province.to_owned(), province_id);
146 let mut manager_array = child(inputs.layout, PROVINCE_PATH, "FACTION_PROVINCE_MANAGER_ARRAY")?;
147 for manager in managers.iter().filter(|manager| manager.province() == province) {
148 let record = self.write_province_faction_manager(manager, next_id(&mut next_manager))?;
149 manager_array.push_group()?.set_record("FACTION_PROVINCE_MANAGER", Some(record))?;
150 }
151
152 let mut record = self.inputs.layout.builder(PROVINCE_PATH)?;
153 record.group()?
154 .set_u32("^+1", province_id)?
155 .set_record("FACTION_PROVINCE_MANAGER_ARRAY", Some(manager_array.build()?))?
156 .set_record("PROVINCE_MERCENARY_POOL", Some(self.write_province_mercenary_pool(province)?))?
157 .set_record("PROVINCE_FERTILITY", Some(self.write_fertility(regions)?))?;
158 array.push_group()?
159 .set_str("^+1", province)?
160 .set_record("PROVINCE", Some(record.build()?))?;
161 }
162
163 let mut manager = child(inputs.layout, WORLD_PATH, "PROVINCE_MANAGER")?;
164 manager.group()?.set_record("PROVINCE_ARRAY", Some(array.build()?))?;
165 manager.build()
166 }
167
168 fn write_province_faction_manager(&mut self, manager: &ProvinceManager, id: u32) -> Result<RecordNode> {
170 let layout = self.inputs.layout;
171 let public_order = self.sum(manager.regions(), "initial_public_order")?.clamp(-MAX_PUBLIC_ORDER, MAX_PUBLIC_ORDER);
172 let happiness = HAPPINESS_THRESHOLDS.iter().position(|threshold| public_order >= *threshold).unwrap_or(HAPPINESS_THRESHOLDS.len());
173
174 let mut info = child(layout, &format!("{PROVINCE_MANAGER_PATH}/FACTION_PROVINCE_PUBLIC_ORDER"), "FACTION_PROVINCE_PUBLIC_ORDER_INFO")?;
175 info.group()?
176 .set_i32("^+4", i32::try_from(public_order).unwrap_or_default())?
177 .set_i32("^+7", i32::try_from(happiness).unwrap_or_default())?;
178 let mut public_order_record = child(layout, PROVINCE_MANAGER_PATH, "FACTION_PROVINCE_PUBLIC_ORDER")?;
179 public_order_record.group()?.set_record("FACTION_PROVINCE_PUBLIC_ORDER_INFO", Some(info.build()?))?;
180
181 let mut development = child(layout, PROVINCE_MANAGER_PATH, "PROVINCE_DEVELOPMENT")?;
182 development.group()?.set_u32("^+1", u32::try_from(self.sum(manager.regions(), "development_points")?).unwrap_or_default())?;
183
184 let region_ids = manager.regions().iter().map(|region| self.plan.region_id(region).unwrap_or_default()).collect();
185 let mut record = layout.builder(PROVINCE_MANAGER_PATH)?;
186 record.group()?
187 .set_u32("^+1", id)?
188 .set_u32("^+2", self.faction_id(manager.faction().as_deref()))?
189 .set_u32s("^+3", region_ids)?
190 .set_record("RECRUITMENT_MANAGER", Some(self.write_recruitment_manager(PROVINCE_MANAGER_PATH)?))?
191 .set_record("FACTION_PROVINCE_PUBLIC_ORDER", Some(public_order_record.build()?))?
192 .set_record("PROVINCE_DEVELOPMENT", Some(development.build()?))?
193 .set_u32("CAMPAIGN_EFFECT_BUNDLES+4", self.plan.region_id(manager.main_region()).unwrap_or_default())?;
194 record.build()
195 }
196
197 fn write_province_mercenary_pool(&mut self, province: &str) -> Result<RecordNode> {
199 let id = next_id(&mut self.next_pool);
200 let path = format!("{PROVINCE_PATH}/PROVINCE_MERCENARY_POOL");
201 let units = province_mercenary_pool(self.inputs.tables, province)?;
202 let mut record = self.inputs.layout.builder(&path)?;
203 record.group()?
204 .set_u32("^+1", id)?
205 .set_record("MERCENARY_POOL", Some(write_mercenary_pool(self.inputs, &path, &units)?))?;
206 record.build()
207 }
208
209 fn write_fertility(&self, regions: &[String]) -> Result<RecordNode> {
211 let path = format!("{PROVINCE_PATH}/PROVINCE_FERTILITY");
212 let mut block = child(self.inputs.layout, &path, "REGION_BLOCK")?;
213 for region in regions {
214 block.push_group()?.set_str("^+1", region)?;
215 }
216
217 let mut record = self.inputs.layout.builder(&path)?;
218 record.group()?
219 .set_i32("^+1", i32::try_from(self.sum(regions, "base_fertility")?).unwrap_or_default())?
220 .set_record("REGION_BLOCK", Some(block.build()?))?;
221 record.build()
222 }
223
224 fn write_sea_region_manager(&mut self) -> Result<RecordNode> {
226 let inputs = self.inputs;
227 let seas = sea_regions(inputs.region_map, inputs.settlements, inputs.index);
228
229 let mut next_manager = 1;
230 let mut block = child(inputs.layout, &format!("{WORLD_PATH}/SEA_REGION_MANAGER"), "SEA_REGIONS_BLOCK")?;
231 for (index, sea) in seas.iter().enumerate() {
232 let record = self.write_sea_region(sea, u32::try_from(index + 1).unwrap_or(u32::MAX), &mut next_manager)?;
233 block.push_group()?
234 .set_str("^+1", sea.key())?
235 .set_record("SEA_REGION", Some(record))?;
236 }
237
238 let mut manager = child(inputs.layout, WORLD_PATH, "SEA_REGION_MANAGER")?;
239 manager.group()?.set_record("SEA_REGIONS_BLOCK", Some(block.build()?))?;
240 manager.build()
241 }
242
243 fn write_sea_region(&mut self, sea: &SeaRegion, id: u32, next_manager: &mut u32) -> Result<RecordNode> {
245 let layout = self.inputs.layout;
246 let mut managers = child(layout, SEA_REGION_PATH, "FACTION_SEA_REGION_MANAGER_BLOCK")?;
247 for (faction, regions) in sea.ports() {
248 let mut region_ids = regions.iter().map(|region| self.plan.region_id(region).unwrap_or_default()).collect::<Vec<_>>();
249 region_ids.sort_unstable();
250
251 let mut vision = Vision::default();
253 for region in regions {
254 if let Some(settlement) = self.sight.settlement(region) {
255 vision.extend(settlement);
256 }
257 }
258
259 let mut manager = layout.builder(SEA_MANAGER_PATH)?;
260 manager.group()?
261 .set_u32("^+1", next_id(next_manager))?
262 .set_u32("^+2", self.faction_id(faction.as_deref()))?
263 .set_u32s("^+3", region_ids)?
264 .set_record("RECRUITMENT_MANAGER", Some(self.write_recruitment_manager(SEA_MANAGER_PATH)?))?
265 .set_record("LINE_OF_SIGHT", Some(write_line_of_sight(layout, SEA_MANAGER_PATH, self.sight.grid(), Some(&vision))?))?;
266 managers.push_group()?.set_record("FACTION_SEA_REGION_MANAGER", Some(manager.build()?))?;
267 }
268
269 let mut record = layout.builder(SEA_REGION_PATH)?;
270 record.group()?
271 .set_u32("^+1", id)?
272 .set_record("FACTION_SEA_REGION_MANAGER_BLOCK", Some(managers.build()?))?;
273 record.build()
274 }
275
276 fn write_recruitment_manager(&mut self, parent: &str) -> Result<RecordNode> {
278 let mut record = child(self.inputs.layout, parent, "RECRUITMENT_MANAGER")?;
279 record.group()?.set_u32("^+1", next_id(&mut self.next_recruitment))?;
280 record.build()
281 }
282
283 fn faction_id(&self, faction: Option<&str>) -> u32 {
285 faction.and_then(|faction| self.plan.faction_id(faction)).unwrap_or(REBEL_FACTION_ID)
286 }
287
288 fn sum(&self, regions: &[String], column: &str) -> Result<i64> {
290 let mut total = 0;
291 for region in regions {
292 if let Some(row) = self.regions.get(region) {
293 total += row.int(column)?;
294 }
295 }
296 Ok(total)
297 }
298}
299
300pub(crate) fn campaign_provinces(tables: &StartposTables, plan: &WorldPlan) -> Result<Vec<(String, Vec<String>)>> {
311 let mut provinces = Vec::<(String, Vec<String>)>::new();
312 for row in tables.table("region_to_province_junctions")?.rows() {
313 let region = row.str("region")?;
314 if plan.region_id(®ion).is_none() {
315 continue;
316 }
317 let province = row.str("province")?;
318 match provinces.iter_mut().find(|(key, _)| *key == province) {
319 Some((_, province_regions)) => province_regions.push(region.into_owned()),
320 None => provinces.push((province.into_owned(), vec![region.into_owned()])),
321 }
322 }
323 Ok(provinces)
324}
325
326pub(super) fn write_mercenary_pool(inputs: &WorldInputs, parent: &str, units: &[MercenaryUnit]) -> Result<RecordNode> {
328 let path = format!("{parent}/MERCENARY_POOL");
329 let mut array = child(inputs.layout, &path, "MERCENARY_POOL_UNIT_ARRAY")?;
330 for (index, unit) in units.iter().enumerate() {
331 array.push_group()?
332 .set_str("^+1", unit.unit())?
333 .set_u32("^+2", unit.initial_count())?
334 .set_f32("^+3", unit.replenish_chance())?
335 .set_u32("^+4", unit.max_count())?
336 .set_u32("^+5", unit.max_replenish_per_turn())?
337 .set_u32("^+6", unit.max_count())?
338 .set_u32("^+8", u32::try_from(index).unwrap_or(u32::MAX))?;
339 }
340
341 let mut record = inputs.layout.builder(&path)?;
342 record.group()?.set_record("MERCENARY_POOL_UNIT_ARRAY", Some(array.build()?))?;
343 record.build()
344}