rpfm_extensions/startpos/cai/
settlements.rs1use std::collections::HashMap;
15
16use rpfm_lib::error::Result;
17use rpfm_lib::files::esf::RecordNode;
18
19use super::factions::{owned_direct, REBELS_WORLD_ID};
20use super::map::{AreaGroups, MapRegion};
21use super::mobiles::{situated, MobileInputs};
22use super::plan::CaiPlan;
23use super::world_view::{ForceView, WorldView};
24use super::{component, CAI_WORLD_PATH};
25use crate::startpos::coords::HexCell;
26use crate::startpos::layout::{child_path, StartposLayout};
27use crate::startpos::settlements::SettlementLayout;
28
29#[cfg(test)] #[path = "settlements_test.rs"] mod settlements_test;
30
31const PORT_SLOT_TYPE: u32 = 3;
33
34const HORDE_SLOT_TYPE: u32 = 9;
36
37const PORT_SLOT: usize = 1;
39
40pub fn port_cells(world: &WorldView, map_regions: &[MapRegion], groups: &AreaGroups) -> HashMap<u32, HexCell> {
52 world.regions().iter()
53 .filter_map(|region| {
54 let port = map_regions.iter().find(|map_region| map_region.key() == region.key())?.port()?;
55 Some((region.residence(), groups.port_cell(port)?))
56 })
57 .collect()
58}
59
60pub fn settlement_components(world: &WorldView, plan: &CaiPlan, map_regions: &[MapRegion]) -> HashMap<u32, u32> {
68 map_regions.iter().enumerate()
69 .filter_map(|(index, map_region)| {
70 let region = world.regions().iter().find(|region| region.key() == map_region.key())?;
71 Some((region.residence(), plan.settlements().get(&index)?.settlement()))
72 })
73 .collect()
74}
75
76pub fn write_settlements(inputs: &MobileInputs, map_regions: &[MapRegion], positions: &[(u16, u16)], capitals: &HashMap<usize, u32>) -> Result<RecordNode> {
90 let layout = inputs.layout;
91 let plan = inputs.plan;
92 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_SETTLEMENTS");
93 let garrisonable_path = child_path(&path, "CAI_GARRISONABLE");
94 let threatened_path = child_path(&path, "CAI_THREATENED_GARRISONABLE");
95
96 let mut mobiles = HashMap::new();
98 for (position, faction) in inputs.world.factions().iter().enumerate() {
99 for (character, ids) in faction.characters().iter().zip(&plan.characters()[position]) {
100 if let Some(mobile) = ids.mobile().filter(|_| character.force() != 0) {
101 mobiles.insert(character.force(), mobile);
102 }
103 }
104 }
105 let mut faction_components = inputs.world.factions().iter().zip(plan.factions()).map(|(faction, ids)| (faction.id(), ids.faction())).collect::<HashMap<_, _>>();
106 if let Some(rebels) = plan.factions().last() {
107 faction_components.insert(REBELS_WORLD_ID, rebels.faction());
108 }
109
110 let forces = inputs.world.factions().iter().flat_map(|faction| faction.forces()).collect::<Vec<_>>();
111 let mut list = layout.builder(&path)?;
112 for (index, map_region) in map_regions.iter().enumerate() {
113 let (Some(ids), Some(region)) = (plan.settlements().get(&index), inputs.world.regions().iter().find(|region| region.key() == map_region.key())) else { continue };
114 let mobile_of = |matches: fn(&ForceView, u32) -> bool| forces.iter()
115 .find(|force| matches(force, region.residence()))
116 .and_then(|force| mobiles.get(&force.id()))
117 .copied()
118 .unwrap_or_default();
119
120 let mut garrisonable = layout.builder(&garrisonable_path)?;
121 garrisonable.group()?
122 .set_u32("^+1", mobile_of(|force, residence| force.residence() == residence && force.garrison_army_of() == 0 && force.garrison_navy_of() == 0))?
123 .set_u32("^+3", mobile_of(|force, residence| force.garrison_army_of() == residence))?
124 .set_u32("^+4", mobile_of(|force, residence| force.garrison_navy_of() == residence))?
125 .set_u32s("^+10", vec![])?;
126
127 let port = inputs.ports.get(®ion.residence()).copied();
128 let mut threatened = layout.builder(&threatened_path)?;
129 threatened.group()?
130 .set_bool("^+1", port.is_some())?
131 .set_record("CAI_SITUATED", port.map(|cell| situated(inputs, &threatened_path, cell)).transpose()?)?;
132
133 let mut settlement = layout.builder(&child_path(&path, "CAI_SETTLEMENT"))?;
134 settlement.group()?
135 .set_u32s("^+1", ids.slots().to_vec())?
136 .set_u32("^+2", capitals.get(&index).copied().unwrap_or_default())?
137 .set_u32("^+3", region.id())?;
138
139 let (x, y) = positions.get(index).copied().unwrap_or_default();
140 list.push_group()?
141 .set_record("OWNED_DIRECT", Some(owned_direct(layout, &path, faction_components.get(®ion.owner()).copied().unwrap_or_default())?))?
142 .set_record("CAI_SITUATED", Some(situated(inputs, &path, HexCell::new(x, y))?))?
143 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, ids.settlement(), vec![])?))?
144 .set_record("CAI_GARRISONABLE", Some(garrisonable.build()?))?
145 .set_record("CAI_THREATENED_GARRISONABLE", Some(threatened.build()?))?
146 .set_record("CAI_SETTLEMENT", Some(settlement.build()?))?;
147 }
148 list.build()
149}
150
151pub fn write_building_slots(layout: &StartposLayout, plan: &CaiPlan, world: &WorldView, map_regions: &[MapRegion], layouts: &[SettlementLayout]) -> Result<RecordNode> {
165 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_BUILDING_SLOTS");
166 let slot_path = child_path(&path, "CAI_BUILDING_SLOT");
167 let mut list = layout.builder(&path)?;
168 let mut push = |id: u32, settlement_slot: u32, horde_slot: u32, kind: u32, settlement: u32, horde: u32| -> Result<()> {
170 let mut record = layout.builder(&slot_path)?;
171 record.group()?
172 .set_u32("^+1", settlement_slot)?
173 .set_u32("^+2", horde_slot)?
174 .set_u32("^+3", kind)?
175 .set_u32("^+4", settlement)?
176 .set_u32("^+5", horde)?;
177 list.push_group()?
178 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, id, vec![])?))?
179 .set_record("CAI_BUILDING_SLOT", Some(record.build()?))?;
180 Ok(())
181 };
182
183 for (index, map_region) in map_regions.iter().enumerate() {
184 let (Some(ids), Some(region)) = (plan.settlements().get(&index), world.regions().iter().find(|region| region.key() == map_region.key())) else { continue };
185 let has_port = layouts.iter().find(|settlement| settlement.region() == map_region.key()).and_then(|settlement| settlement.slots().get(PORT_SLOT)).is_some_and(|slot| slot.position().is_some());
186 for (slot, (id, world_id)) in ids.slots().iter().zip(region.slots()).enumerate() {
187 let kind = if has_port && slot == PORT_SLOT { PORT_SLOT_TYPE } else { 0 };
188 push(*id, *world_id, 0, kind, ids.settlement(), 0)?;
189 }
190 }
191
192 for (position, faction) in world.factions().iter().enumerate() {
193 for (character, ids) in faction.characters().iter().zip(&plan.characters()[position]) {
194 let Some(force) = faction.forces().iter().find(|force| character.force() != 0 && force.id() == character.force()) else { continue };
195 for (id, world_id) in ids.horde_slots().iter().zip(force.horde_slots()) {
196 push(*id, 0, *world_id, HORDE_SLOT_TYPE, 0, ids.mobile().unwrap_or_default())?;
197 }
198 }
199 }
200 list.build()
201}