1use std::collections::{HashMap, HashSet};
14
15use rpfm_lib::error::{RLibError, Result};
16use rpfm_lib::files::esf::RecordNode;
17
18use super::factions::write_flag;
19use super::forces::{ForcePlan, PlannedForce};
20use super::sight::{write_line_of_sight, SightPlan};
21use super::{child, horde_details, next_id, HORDE_BUILDING_COLUMNS, WorldInputs, WorldPlan, REBEL_FACTION_ID, WORLD_PATH};
22use crate::startpos::coords::{EdgePiece, HexCell};
23use crate::startpos::settlements::{SettlementLayout, SlotLayout};
24use crate::startpos::tables::Row;
25use crate::startpos::world::RegionEntry;
26use crate::startpos::world::legacy::ForceKind;
27use crate::startpos::world::rebels::rebel_setup;
28
29const REGION_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/REGION_MANAGER/REGIONS_ARRAY/REGION";
31
32const SLOT_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/REGION_MANAGER/REGIONS_ARRAY/REGION/REGION_SLOT_MANAGER/REGION_SLOT_ARRAY/REGION_SLOT";
34
35const SETTLEMENT_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/REGION_MANAGER/REGIONS_ARRAY/REGION/SETTLEMENT";
37
38const EXPANSION_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/REGION_MANAGER/REGIONS_ARRAY/REGION/SETTLEMENT/SETTLEMENT_EXPANSION_MANAGER/SETTLEMENT_EXPANSION_MAP_DATA";
40
41const REGION_NAME_PREFIX: &str = "regions_onscreen_";
43
44const SETTLEMENT_NAME_PREFIX: &str = "start_pos_settlements_onscreen_name_";
46
47const ABANDONED_REBELS_NAME: &str = "campaign_localised_strings_string_abandoned_region_rebel_faction_name";
49
50const ABANDONED_REBELS_VALUE: u32 = 12;
52
53const POPULATION_CLASSES: [&str; 3] = ["upper", "lower", "middle"];
55
56const POPULATION_CLASS_VALUE: u32 = 7;
58
59const NO_POSITION: u32 = 65535;
61
62const BUILDING_BASE_VALUE_OFFSET: i64 = 3;
64
65struct RegionWriter<'a, 'b> {
71
72 inputs: &'b WorldInputs<'a>,
74
75 plan: &'b WorldPlan<'a>,
77
78 forces: &'b ForcePlan<'a>,
80
81 sight: &'b SightPlan,
83
84 rows: RegionRows<'a>,
86
87 next_slot: u32,
89
90 next_residence: u32,
92
93 next_building: u32,
95}
96
97struct RegionRows<'a> {
99
100 regions: HashMap<String, Row<'a>>,
102
103 settlement_keys: HashMap<String, String>,
105
106 governments: HashMap<String, String>,
108
109 templates: HashMap<(String, String), String>,
111
112 building_levels: HashMap<String, (i64, i64)>,
114
115 religions: Vec<String>,
117
118 religion_shares: HashMap<(String, String), f32>,
120}
121
122#[derive(Debug, Clone, Copy)]
124struct SlotContent<'s> {
125
126 slot_type: &'s str,
128
129 building: Option<&'s str>,
131}
132
133pub fn write_region_manager<'a>(inputs: &WorldInputs<'a>, plan: &WorldPlan<'a>, forces: &ForcePlan<'a>, sight: &SightPlan) -> Result<RecordNode> {
143 let mut writer = RegionWriter {
144 inputs,
145 plan,
146 forces,
147 sight,
148 rows: RegionRows::new(inputs)?,
149 next_slot: 1,
150 next_residence: 1,
151 next_building: horde_building_count(inputs)? + 1,
152 };
153
154 let mut regions = child(inputs.layout, &format!("{WORLD_PATH}/REGION_MANAGER"), "REGIONS_ARRAY")?;
155 for region in plan.regions() {
156 regions.push_group()?.set_record("REGION", Some(writer.write_region(region)?))?;
157 }
158
159 let mut manager = child(inputs.layout, WORLD_PATH, "REGION_MANAGER")?;
160 manager.group()?.set_record("REGIONS_ARRAY", Some(regions.build()?))?;
161 manager.build()
162}
163
164impl<'a> RegionRows<'a> {
165
166 fn new(inputs: &WorldInputs<'a>) -> Result<Self> {
168 let tables = inputs.tables;
169 let campaign = inputs.index.campaign();
170
171 let mut regions = HashMap::new();
172 for row in tables.table("start_pos_regions")?.rows() {
173 regions.insert(row.str("id")?.into_owned(), row);
174 }
175
176 let settlement_keys = settlement_keys(inputs)?;
177
178 let mut governments = HashMap::new();
179 for row in tables.table("start_pos_factions")?.rows() {
180 if row.str("campaign")? == *campaign {
181 governments.insert(row.str("faction")?.into_owned(), row.str("government_type")?.into_owned());
182 }
183 }
184
185 let mut templates = HashMap::new();
186 for row in tables.table("start_pos_region_slot_templates")?.rows() {
187 if row.str("campaign")? == *campaign {
188 templates.insert((row.str("region")?.into_owned(), row.str("slot_type")?.into_owned()), row.str("slot_template")?.into_owned());
189 }
190 }
191
192 let mut building_levels = HashMap::new();
193 for row in tables.table("building_levels")?.rows() {
194 building_levels.insert(row.str("level_name")?.into_owned(), (row.int("create_time")?, row.int("level")?));
195 }
196
197 let religions = tables.table("religions")?.rows()
198 .map(|row| Ok(row.str("religion_key")?.into_owned()))
199 .collect::<Result<Vec<_>>>()?;
200
201 let mut religion_shares = HashMap::new();
202 for row in tables.table("start_pos_region_religions")?.rows() {
203 religion_shares.insert((row.str("region")?.into_owned(), row.str("religion")?.into_owned()), row.f32("percentage")?);
204 }
205
206 Ok(Self { regions, settlement_keys, governments, templates, building_levels, religions, religion_shares })
207 }
208}
209
210impl RegionWriter<'_, '_> {
211
212 fn write_region(&mut self, region: &RegionEntry) -> Result<RecordNode> {
214 let layout = self.inputs.layout;
215 let key = region.key();
216 let row = *self.rows.regions.get(region.id())
217 .ok_or_else(|| RLibError::StartposInvalidValue("start_pos_regions".to_owned(), "id".to_owned(), region.id().to_owned()))?;
218 let wealth = u32::try_from(row.int("town_wealth")?).unwrap_or_default();
219 let owner = region.owner().as_deref().and_then(|owner| self.plan.faction_id(owner)).unwrap_or(REBEL_FACTION_ID);
220 let map_layout = settlement_layout(self.inputs, region)?;
221
222 let mut localisation = child(layout, REGION_PATH, "CAMPAIGN_LOCALISATION")?;
223 localisation.group()?.set_str("^+1", &format!("{REGION_NAME_PREFIX}{key}"))?;
224
225 let slots = self.write_slot_manager(region, map_layout)?;
226 let settlement = self.write_settlement(region, map_layout)?;
227
228 let mut record = layout.builder(REGION_PATH)?;
229 record.group()?
230 .set_u32("^+1", self.plan.region_id(key).unwrap_or_default())?
231 .set_str("^+2", key)?
232 .set_record("POPULATION", Some(self.write_population(region)?))?
233 .set_record("REGION_SLOT_MANAGER", Some(slots))?
234 .set_record("SETTLEMENT", Some(settlement))?
235 .set_u32("SETTLEMENT+3", wealth)?
236 .set_u32("SETTLEMENT+4", wealth)?
237 .set_u32("SETTLEMENT+5", wealth)?
238 .set_u32("SETTLEMENT+9", owner)?
239 .set_str("LINE_OF_SIGHT+1", &row.str("rebel_faction")?)?
240 .set_str("LINE_OF_SIGHT+3", &row.str("alternative_rebel_faction")?)?
241 .set_str("LINE_OF_SIGHT+4", &row.str("cultural_originator")?)?
242 .set_record("CAMPAIGN_LOCALISATION", Some(localisation.build()?))?
243 .set_record("REGION_REBEL_MANAGER", Some(self.write_rebel_manager(region)?))?
244 .set_bool("REGION_REBEL_MANAGER+1", region.owner().is_none())?
245 .set_record("LINE_OF_SIGHT", Some(write_line_of_sight(layout, REGION_PATH, self.sight.grid(), self.sight.region(key))?))?;
246 record.build()
247 }
248
249 fn write_population(&self, region: &RegionEntry) -> Result<RecordNode> {
255 let percentages = self.rows.religions.iter()
256 .map(|religion| self.rows.religion_shares.get(&(region.id().to_owned(), religion.to_owned())).copied().unwrap_or_default())
257 .collect::<Vec<_>>();
258 let total = percentages.iter().sum::<f32>() * 0.01;
259
260 let path = format!("{REGION_PATH}/POPULATION/REGION_FACTORS");
261 let mut breakdown = child(self.inputs.layout, &path, "RELIGION_BREAKDOWN")?;
262 for (religion, percentage) in self.rows.religions.iter().zip(percentages) {
263 let share = if total > 0.0 { percentage * 0.01 / total } else { 0.0 };
264 breakdown.push_group()?
265 .set_str("^+1", religion)?
266 .set_f32("^+2", share)?;
267 }
268
269 let classes_path = format!("{path}/POPULATION CLASSES");
270 let mut classes = self.inputs.layout.builder(&classes_path)?;
271 for class in POPULATION_CLASSES {
272 let mut record = child(self.inputs.layout, &classes_path, "POPULATION_CLASS")?;
273 record.group()?
274 .set_str("^+1", class)?
275 .set_u32("^+5", POPULATION_CLASS_VALUE)?;
276 classes.push_group()?.set_record("POPULATION_CLASS", Some(record.build()?))?;
277 }
278
279 let mut factors = self.inputs.layout.builder(&path)?;
280 factors.group()?
281 .set_record("POPULATION CLASSES", Some(classes.build()?))?
282 .set_record("RELIGION_BREAKDOWN", Some(breakdown.build()?))?;
283
284 let mut population = child(self.inputs.layout, REGION_PATH, "POPULATION")?;
285 population.group()?.set_record("REGION_FACTORS", Some(factors.build()?))?;
286 population.build()
287 }
288
289 fn write_rebel_manager(&self, region: &RegionEntry) -> Result<RecordNode> {
291 let layout = self.inputs.layout;
292 let path = format!("{REGION_PATH}/REGION_REBEL_MANAGER");
293 let mut manager = layout.builder(&path)?;
294 if let Some(setup) = rebel_setup(self.inputs.tables, region)? {
295 let setup_path = format!("{path}/REBEL_SETUP");
296 let mut name = child(layout, &setup_path, "CAMPAIGN_LOCALISATION")?;
297 name.group()?.set_str("^+1", ABANDONED_REBELS_NAME)?.unset("^+2")?;
298 let name = name.build()?;
299
300 let mut record = layout.builder(&setup_path)?;
301 record.group()?
302 .set_str("^+2", setup.faction())?
303 .set_str("^+3", setup.religion())?
304 .set_u32("^+5", ABANDONED_REBELS_VALUE)?
305 .set_record("CAMPAIGN_LOCALISATION", Some(name.clone()))?
306 .set_record("CAMPAIGN_LOCALISATION@2", Some(name))?
307 .set_record("FACTION_FLAG_AND_COLOURS", Some(write_flag(self.inputs, &setup_path, setup.flag())?))?;
308 manager.group()?
309 .set_record("REBEL_SETUP", Some(record.build()?))?
310 .set_bool("REBEL_SETUP+1", true)?;
311 }
312 manager.build()
313 }
314
315 fn write_slot_manager(&mut self, region: &RegionEntry, map_layout: &SettlementLayout) -> Result<RecordNode> {
318 let inputs = self.inputs;
319 let settlement = inputs.index.settlement_of(region.key());
320 let primary = settlement.map(|settlement| settlement.primary_building().as_str()).filter(|building| !building.is_empty());
321 let port = settlement.map(|settlement| settlement.port_building().as_str()).filter(|building| !building.is_empty());
322 let others = settlement.map(|settlement| settlement.buildings().as_slice()).unwrap_or_default();
323
324 let mut contents = vec![SlotContent { slot_type: "primary", building: primary }];
325 if has_port(map_layout) {
326 contents.push(SlotContent { slot_type: "port", building: port });
327 }
328 for index in 0..map_layout.slots().len().saturating_sub(2) {
329 contents.push(SlotContent { slot_type: "secondary", building: others.get(index).map(String::as_str) });
330 }
331
332 let mut array = child(inputs.layout, &format!("{REGION_PATH}/REGION_SLOT_MANAGER"), "REGION_SLOT_ARRAY")?;
333 for (index, content) in contents.iter().enumerate() {
334 let slot = self.write_slot(region, index, content)?;
335 array.push_group()?.set_record("REGION_SLOT", Some(slot))?;
336 }
337
338 let mut manager = child(inputs.layout, REGION_PATH, "REGION_SLOT_MANAGER")?;
339 manager.group()?.set_record("REGION_SLOT_ARRAY", Some(array.build()?))?;
340 manager.build()
341 }
342
343 fn write_slot(&mut self, region: &RegionEntry, index: usize, content: &SlotContent) -> Result<RecordNode> {
345 let layout = self.inputs.layout;
346 let template = self.rows.templates.get(&(region.key().to_owned(), content.slot_type.to_owned())).cloned().unwrap_or_default();
347
348 let mut residence = child(layout, SLOT_PATH, "GARRISON_RESIDENCE")?;
349 residence.group()?.set_u32("^+1", next_id(&mut self.next_residence))?;
350
351 let building = content.building.map(|building| self.write_building(region, building, content.slot_type == "primary")).transpose()?;
352 let mut manager = child(layout, SLOT_PATH, "REGION_BUILDING_MANAGER")?;
353 manager.group()?
354 .set_bool("^+2", building.is_some())?
355 .set_bool("^+5", building.is_some())?
356 .set_bool("^+6", region.owner().is_none())?
357 .set_record("BUILDING_BASE", building)?;
358
359 let mut record = layout.builder(SLOT_PATH)?;
360 record.group()?
361 .set_u32("^+1", next_id(&mut self.next_slot))?
362 .set_record("GARRISON_RESIDENCE", Some(residence.build()?))?
363 .set_record("REGION_BUILDING_MANAGER", Some(manager.build()?))?
364 .set_str("REGION_BUILDING_MANAGER+1", &format!("{}:{index}", region.key()))?
365 .set_str("REGION_BUILDING_MANAGER+2", &template)?
366 .set_str("REGION_BUILDING_MANAGER+3", content.slot_type)?;
367 record.build()
368 }
369
370 fn write_building(&mut self, region: &RegionEntry, building: &str, in_primary_slot: bool) -> Result<RecordNode> {
375 let owner = region.owner().as_deref()
376 .or_else(|| self.plan.factions().first().map(|faction| faction.key().as_str()))
377 .unwrap_or_default();
378 let government = self.rows.governments.get(owner).map(String::as_str).unwrap_or_default();
379 let (create_time, level) = self.rows.building_levels.get(building).copied()
380 .ok_or_else(|| RLibError::StartposInvalidValue("building_levels".to_owned(), "level_name".to_owned(), building.to_owned()))?;
381 let value = create_time.max(level + BUILDING_BASE_VALUE_OFFSET + i64::from(in_primary_slot)) as f32;
382
383 let mut record = child(self.inputs.layout, &format!("{SLOT_PATH}/REGION_BUILDING_MANAGER"), "BUILDING_BASE")?;
384 record.group()?
385 .set_u32("^+1", next_id(&mut self.next_building))?
386 .set_str("^+2", building)?
387 .set_f32("^+3", value)?
388 .set_f32("^+4", value)?
389 .set_str("^+5", owner)?
390 .set_str("^+6", government)?;
391 record.build()
392 }
393
394 fn write_settlement(&mut self, region: &RegionEntry, map_layout: &SettlementLayout) -> Result<RecordNode> {
397 let layout = self.inputs.layout;
398 let key = region.key();
399 let settlement_key = self.inputs.index.settlement_of(key)
400 .and_then(|settlement| self.rows.settlement_keys.get(settlement.id()))
401 .cloned()
402 .unwrap_or_default();
403
404 let mut localisation = child(layout, SETTLEMENT_PATH, "CAMPAIGN_LOCALISATION")?;
405 localisation.group()?.set_str("^+1", &settlement_name_key(&settlement_key, region))?;
406
407 let zone = write_cells(self.inputs, &format!("{SETTLEMENT_PATH}/GARRISON_RESIDENCE/ZOE_BLOCK"), &garrison_zone(map_layout))?;
408 let mut residence = child(layout, SETTLEMENT_PATH, "GARRISON_RESIDENCE")?;
409 let force_id = |force: Option<&PlannedForce>| force.map(|force| force.id()).unwrap_or_default();
410 residence.group()?
411 .set_u32("^+1", next_id(&mut self.next_residence))?
412 .set_u32("^+2", force_id(self.forces.stationed_in(key)))?
413 .set_record("ZOE_BLOCK", Some(zone))?
414 .set_record("LINE_OF_SIGHT", Some(write_line_of_sight(layout, &format!("{SETTLEMENT_PATH}/GARRISON_RESIDENCE"), self.sight.grid(), self.sight.settlement(key))?))?
415 .set_u32("ZOE_BLOCK+3", force_id(self.forces.garrison_of(key, ForceKind::GarrisonArmy)))?
416 .set_u32("ZOE_BLOCK+4", force_id(self.forces.garrison_of(key, ForceKind::GarrisonNavy)))?;
417
418 let mut expansion = child(layout, SETTLEMENT_PATH, "SETTLEMENT_EXPANSION_MANAGER")?;
419 expansion.group()?.set_record("SETTLEMENT_EXPANSION_MAP_DATA", Some(write_expansion_map_data(self.inputs, map_layout)?))?;
420
421 let mut record = layout.builder(SETTLEMENT_PATH)?;
422 record.group()?
423 .set_record("GARRISON_RESIDENCE", Some(residence.build()?))?
424 .set_u32("GARRISON_RESIDENCE+1", self.plan.region_id(key).unwrap_or_default())?
425 .set_record("CAMPAIGN_LOCALISATION", Some(localisation.build()?))?
426 .set_str("CAMPAIGN_LOCALISATION+2", &settlement_key)?
427 .set_record("SETTLEMENT_EXPANSION_MANAGER", Some(expansion.build()?))?;
428 record.build()
429 }
430}
431
432fn write_expansion_map_data(inputs: &WorldInputs, map_layout: &SettlementLayout) -> Result<RecordNode> {
437 let mut secondary = child(inputs.layout, EXPANSION_PATH, "SECONDARY_SLOT_ARRAY")?;
438 for slot in map_layout.slots().iter().skip(2) {
439 secondary.push_group()?.set_record("SLOT_MAP_DATA", Some(write_slot_map_data(inputs, &format!("{EXPANSION_PATH}/SECONDARY_SLOT_ARRAY/SLOT_MAP_DATA"), slot)?))?;
440 }
441
442 let sprawl_path = format!("{EXPANSION_PATH}/SPRAWL_BLOCK");
443 let mut sprawl = inputs.layout.builder(&sprawl_path)?;
444 let sprawl_cells = map_layout.sprawl().iter().map(|cell| cell.cell()).collect::<HashSet<_>>();
445 let is_water = |cell: HexCell| inputs.region_map.region_at(cell).is_some_and(|region| inputs.index.region(region).is_none());
446 for cell in map_layout.sprawl() {
447 let hex_cell = cell.cell();
448 let [first_edges, second_edges] = inputs.region_map.edge_masks(hex_cell);
449 let roads = (first_edges | second_edges) & hex_cell.neighbour_mask(|neighbour| sprawl_cells.contains(&neighbour));
450 let coast = hex_cell.neighbour_mask(is_water);
451 let road_piece = EdgePiece::from_mask(roads);
452 let coast_piece = EdgePiece::from_mask(coast);
453
454 let mut hex = child(inputs.layout, &sprawl_path, "SPRAWL_HEX_MAP_DATA")?;
455 hex.group()?
456 .set_u32("^+1", u32::from(hex_cell.x()))?
457 .set_u32("^+2", u32::from(hex_cell.y()))?
458 .set_bool("^+3", cell.in_footprint())?
459 .set_bool("^+4", cell.in_zone())?
460 .set_u32("^+5", u32::from(first_edges))?
461 .set_u32("^+6", u32::from(second_edges))?
462 .set_bool("^+7", is_water(hex_cell))?
463 .set_u32("^+8", u32::from(roads))?
464 .set_i32("^+9", i32::from(road_piece.shape()))?
465 .set_u32("^+10", u32::from(road_piece.rotation()))?
466 .set_u32("^+11", cell.zone_slots())?
467 .set_u32("^+12", u32::from(coast))?
468 .set_i32("^+13", i32::from(coast_piece.shape()))?
469 .set_u32("^+14", u32::from(coast_piece.rotation()))?;
470 sprawl.push_group()?
471 .set_u32("^+1", u32::from(hex_cell.x()))?
472 .set_u32("^+2", u32::from(hex_cell.y()))?
473 .set_record("SPRAWL_HEX_MAP_DATA", Some(hex.build()?))?;
474 }
475
476 let slot_path = format!("{EXPANSION_PATH}/SLOT_MAP_DATA");
477 let mut record = inputs.layout.builder(EXPANSION_PATH)?;
478 record.group()?
479 .set_str("^+1", map_layout.region())?
480 .set_record("SLOT_MAP_DATA", Some(write_slot_map_data(inputs, &slot_path, &map_layout.slots()[0])?))?
481 .set_record("SLOT_MAP_DATA@2", Some(write_slot_map_data(inputs, &slot_path, &map_layout.slots()[1])?))?
482 .set_record("SECONDARY_SLOT_ARRAY", Some(secondary.build()?))?
483 .set_record("SPRAWL_BLOCK", Some(sprawl.build()?))?;
484 record.build()
485}
486
487fn write_slot_map_data(inputs: &WorldInputs, path: &str, slot: &SlotLayout) -> Result<RecordNode> {
489 let (x, y) = slot.position().map(|cell| (u32::from(cell.x()), u32::from(cell.y()))).unwrap_or((NO_POSITION, NO_POSITION));
490 let mut record = inputs.layout.builder(path)?;
491 record.group()?
492 .set_u32("^+1", slot.id())?
493 .set_u32("^+2", x)?
494 .set_u32("^+3", y)?
495 .set_record("SLOT_FOOTPRINT_ARRAY", Some(write_cells(inputs, &format!("{path}/SLOT_FOOTPRINT_ARRAY"), slot.footprint())?))?
496 .set_record("SLOT_ZOE_ARRAY", Some(write_cells(inputs, &format!("{path}/SLOT_ZOE_ARRAY"), slot.zone())?))?
497 .set_record("SLOT_WALL_ARRAY", Some(write_cells(inputs, &format!("{path}/SLOT_WALL_ARRAY"), slot.walls())?))?
498 .set_u32("SLOT_WALL_ARRAY+1", slot.touching_slots())?;
499 record.build()
500}
501
502fn write_cells(inputs: &WorldInputs, path: &str, cells: &[HexCell]) -> Result<RecordNode> {
504 let mut record = inputs.layout.builder(path)?;
505 for cell in cells {
506 record.push_group()?
507 .set_u32("^+1", u32::from(cell.x()))?
508 .set_u32("^+2", u32::from(cell.y()))?;
509 }
510 record.build()
511}
512
513fn garrison_zone(map_layout: &SettlementLayout) -> Vec<HexCell> {
515 let mut seen = HashSet::new();
516 let main = &map_layout.slots()[0];
517 let port = &map_layout.slots()[1];
518 main.footprint().iter().chain(main.zone()).chain(port.zone())
519 .filter(|cell| seen.insert(**cell))
520 .copied()
521 .collect()
522}
523
524pub(crate) fn settlement_residences(inputs: &WorldInputs, plan: &WorldPlan) -> Result<HashMap<String, u32>> {
531 let mut residences = HashMap::new();
532 let mut next_residence = 1;
533 for region in plan.regions() {
534 let map_layout = settlement_layout(inputs, region)?;
535 let slots = 1 + usize::from(has_port(map_layout)) + map_layout.slots().len().saturating_sub(2);
536 next_residence += u32::try_from(slots).unwrap_or(u32::MAX);
537 residences.insert(region.key().to_owned(), next_id(&mut next_residence));
538 }
539 Ok(residences)
540}
541
542pub(crate) fn settlement_keys(inputs: &WorldInputs) -> Result<HashMap<String, String>> {
548 let mut keys = HashMap::new();
549 for row in inputs.tables.table("start_pos_settlements")?.rows() {
550 keys.insert(row.str("id")?.into_owned(), row.str("settlement_id")?.into_owned());
551 }
552 Ok(keys)
553}
554
555pub(crate) fn settlement_name_key(settlement_key: &str, region: &RegionEntry) -> String {
557 format!("{SETTLEMENT_NAME_PREFIX}{settlement_key}{}", region.id())
558}
559
560fn settlement_layout<'a>(inputs: &WorldInputs<'a>, region: &RegionEntry) -> Result<&'a SettlementLayout> {
562 inputs.settlements.iter().find(|settlement| settlement.region() == region.key())
563 .ok_or_else(|| RLibError::StartposInvalidValue("map_data".to_owned(), "SETTLEMENT_INFO".to_owned(), region.key().to_owned()))
564}
565
566fn has_port(map_layout: &SettlementLayout) -> bool {
568 map_layout.slots().get(1).is_some_and(|slot| slot.position().is_some())
569}
570
571fn horde_building_count(inputs: &WorldInputs) -> Result<u32> {
574 let mut count = 0;
575 for row in horde_details(inputs)? {
576 for column in HORDE_BUILDING_COLUMNS {
577 if !row.str(column)?.is_empty() {
578 count += 1;
579 }
580 }
581 }
582 Ok(count)
583}