1use 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::MapRegion;
21use super::plan::CaiPlan;
22use super::world_view::WorldView;
23use super::{component, CAI_WORLD_PATH};
24use crate::startpos::layout::{child_path, StartposLayout};
25use crate::startpos::world::CampaignIndex;
26
27#[cfg(test)] #[path = "lists_test.rs"] mod lists_test;
28
29pub fn write_trade_segments(layout: &StartposLayout, plan: &CaiPlan, splines: usize) -> Result<RecordNode> {
45 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_TRADE_ROUTE_SEGMENTS");
46 let mut list = layout.builder(&path)?;
47 for index in 0..splines as u32 {
48 let mut record = layout.builder(&child_path(&path, "CAI_TRADE_ROUTE_SEGMENT"))?;
49 record.group()?.set_u32("^+1", index)?;
50 list.push_group()?
51 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, plan.first_trade_segment() + index, vec![])?))?
52 .set_record("CAI_TRADE_ROUTE_SEGMENT", Some(record.build()?))?;
53 }
54 list.build()
55}
56
57pub fn write_trade_routes(layout: &StartposLayout, plan: &CaiPlan, world: &WorldView, routes: &[(u32, u32)]) -> Result<RecordNode> {
71 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_TRADE_ROUTES");
72 let mut components = world.factions().iter().zip(plan.factions()).map(|(faction, ids)| (faction.id(), ids.faction())).collect::<HashMap<_, _>>();
73 if let Some(rebels) = plan.factions().last() {
74 components.insert(REBELS_WORLD_ID, rebels.faction());
75 }
76 let owners = routes.iter().map(|(owner, route)| (*route, components.get(owner).copied().unwrap_or_default())).collect::<HashMap<_, _>>();
77 let mut ids = owners.keys().copied().collect::<Vec<_>>();
78 ids.sort_unstable();
79
80 let mut list = layout.builder(&path)?;
81 for (index, route) in ids.iter().enumerate() {
82 let partner = if route % 2 == 1 { route + 1 } else { route - 1 };
83 let mut record = layout.builder(&child_path(&path, "CAI_TRADE_ROUTE"))?;
84 record.group()?
85 .set_u32("^+2", *route)?
86 .set_u32("^+3", owners.get(&partner).copied().unwrap_or_default())?;
87 list.push_group()?
88 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, plan.first_trade_route() + index as u32, vec![])?))?
89 .set_record("OWNED_DIRECT", Some(owned_direct(layout, &path, owners[route])?))?
90 .set_record("CAI_TRADE_ROUTE", Some(record.build()?))?;
91 }
92 list.build()
93}
94
95pub fn write_boundaries(layout: &StartposLayout, plan: &CaiPlan) -> Result<RecordNode> {
101 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_REGION_BOUNDARIES");
102 let mut list = layout.builder(&path)?;
103 for boundary in plan.boundaries() {
104 let mut record = layout.builder(&child_path(&path, "CAI_REGION_BOUNDARY"))?;
105 record.group()?
106 .set_u32("^+1", plan.regions()[boundary.region()].id())?
107 .set_u32("^+2", plan.regions()[boundary.other()].id())?
108 .set_f32("^+3", boundary.value() as f32)?;
109 list.push_group()?
110 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, boundary.id(), vec![])?))?
111 .set_record("CAI_REGION_BOUNDARY", Some(record.build()?))?;
112 }
113 list.build()
114}
115
116pub fn write_provinces(layout: &StartposLayout, plan: &CaiPlan, world: &WorldView, map_regions: &[MapRegion], index: &CampaignIndex) -> Result<(RecordNode, RecordNode)> {
134 let lookups = Lookups::new(plan, world, map_regions);
135 let provinces_path = child_path(CAI_WORLD_PATH, "CAI_PROVINCES");
136 let parts_path = child_path(CAI_WORLD_PATH, "CAI_FACTION_PROVINCES");
137
138 let mut provinces = layout.builder(&provinces_path)?;
139 let mut parts = layout.builder(&parts_path)?;
140 for (province, ids) in world.provinces().iter().zip(plan.provinces()) {
141 for (part, id) in province.managers().iter().zip(ids.faction_provinces()) {
142
143 let mut regions = part.regions().iter().map(|region| lookups.region_by_id(*region)).collect::<Vec<_>>();
145 let main = lookups.region_by_id(part.main_region());
146 if let Some(position) = regions.iter().position(|region| *region == main) {
147 regions.swap(0, position);
148 }
149
150 let mut record = layout.builder(&child_path(&parts_path, "CAI_FACTION_PROVINCE"))?;
151 record.group()?
152 .set_u32("^+1", part.id())?
153 .set_u32("^+2", ids.province())?
154 .set_u32s("^+3", regions)?
155 .set_u32("^+4", lookups.region_by_id(part.main_region()))?;
156 parts.push_group()?
157 .set_record("OWNED_DIRECT", Some(owned_direct(layout, &parts_path, lookups.faction(part.faction()))?))?
158 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &parts_path, *id, vec![])?))?
159 .set_record("CAI_FACTION_PROVINCE", Some(record.build()?))?;
160 }
161
162 let major = province.regions().iter().find(|key| index.settlement_of(key).is_some_and(|settlement| settlement.is_major()));
164 let main_region = match major {
165 Some(key) => lookups.region_by_key(key),
166 None => province.managers().first().map(|part| lookups.region_by_id(part.main_region())).unwrap_or_default(),
167 };
168 let mut record = layout.builder(&child_path(&provinces_path, "CAI_PROVINCE"))?;
169 record.group()?
170 .set_u32("^+1", province.id())?
171 .set_u32("^+2", main_region)?
172 .set_u32s("^+3", province.regions().iter().map(|key| lookups.region_by_key(key)).collect())?
173 .set_u32s("^+4", ids.faction_provinces().to_vec())?;
174 provinces.push_group()?
175 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &provinces_path, ids.province(), vec![])?))?
176 .set_record("CAI_PROVINCE", Some(record.build()?))?;
177 }
178 Ok((provinces.build()?, parts.build()?))
179}
180
181pub fn write_faction_seas(layout: &StartposLayout, plan: &CaiPlan, world: &WorldView, map_regions: &[MapRegion]) -> Result<RecordNode> {
194 let lookups = Lookups::new(plan, world, map_regions);
195 let path = child_path(CAI_WORLD_PATH, "CAI_FACTION_SEA_REGIONS");
196 let mut list = layout.builder(&path)?;
197 for (sea, ids) in world.seas().iter().zip(plan.faction_seas()) {
198 for (part, id) in sea.managers().iter().zip(ids) {
199 let mut record = layout.builder(&child_path(&path, "CAI_FACTION_SEA_REGION"))?;
200 record.group()?
201 .set_u32("^+1", lookups.region_by_key(sea.key()))?
202 .set_u32("^+2", part.id())?
203 .set_u32s("^+3", part.regions().iter().map(|region| lookups.region_by_id(*region)).collect())?;
204 list.push_group()?
205 .set_record("OWNED_DIRECT", Some(owned_direct(layout, &path, lookups.faction(part.faction()))?))?
206 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, *id, vec![])?))?
207 .set_record("CAI_FACTION_SEA_REGION", Some(record.build()?))?;
208 }
209 }
210 list.build()
211}
212
213pub fn write_governorships(layout: &StartposLayout, plan: &CaiPlan) -> Result<RecordNode> {
219 let path = child_path(CAI_WORLD_PATH, "CAI_WORLD_GOVERNORSHIPS");
220 let mut list = layout.builder(&path)?;
221 for (id, faction) in plan.governorships().iter().zip(plan.factions()) {
222 let mut record = layout.builder(&child_path(&path, "CAI_GOVERNORSHIP"))?;
223 record.group()?.set_u32("^+1", plan.theatre())?;
224 list.push_group()?
225 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, *id, vec![])?))?
226 .set_record("OWNED_DIRECT", Some(owned_direct(layout, &path, faction.faction())?))?
227 .set_record("CAI_GOVERNORSHIP", Some(record.build()?))?;
228 }
229 list.build()
230}
231
232pub fn write_posts(layout: &StartposLayout, plan: &CaiPlan, world: &WorldView) -> Result<RecordNode> {
238 let path = child_path(CAI_WORLD_PATH, "CAI_CHARACTER_POSTS");
239 let mut list = layout.builder(&path)?;
240 for (position, faction) in world.factions().iter().enumerate() {
241 let characters = &plan.characters()[position];
242 for ((post, holder), id) in faction.posts().iter().zip(&plan.posts()[position]) {
243 let holder = faction.characters().iter().position(|character| *holder != 0 && character.id() == *holder).map(|index| characters[index].character()).unwrap_or_default();
244 let mut record = layout.builder(&child_path(&path, "CAI_CHARACTER_POST"))?;
245 record.group()?
246 .set_u32("^+1", *post)?
247 .set_u32("^+2", holder)?;
248 list.push_group()?
249 .set_record("OWNED_DIRECT", Some(owned_direct(layout, &path, plan.factions()[position].faction())?))?
250 .set_record("CAI_BDI_COMPONENT", Some(component(layout, &path, *id, vec![])?))?
251 .set_record("CAI_CHARACTER_POST", Some(record.build()?))?;
252 }
253 }
254 list.build()
255}
256
257struct Lookups {
259
260 regions: HashMap<String, u32>,
262
263 region_keys: HashMap<u32, String>,
265
266 factions: HashMap<u32, u32>,
268}
269
270impl Lookups {
271
272 fn new(plan: &CaiPlan, world: &WorldView, map_regions: &[MapRegion]) -> Self {
274 let regions = map_regions.iter().zip(plan.regions()).map(|(region, ids)| (region.key().to_owned(), ids.id())).collect();
275 let region_keys = world.regions().iter().map(|region| (region.id(), region.key().to_owned())).collect();
276 let mut factions = world.factions().iter().zip(plan.factions()).map(|(faction, ids)| (faction.id(), ids.faction())).collect::<HashMap<_, _>>();
277 if let Some(rebels) = plan.factions().last() {
278 factions.insert(REBELS_WORLD_ID, rebels.faction());
279 }
280 Self { regions, region_keys, factions }
281 }
282
283 fn region_by_key(&self, key: &str) -> u32 {
285 self.regions.get(key).copied().unwrap_or_default()
286 }
287
288 fn region_by_id(&self, id: u32) -> u32 {
290 self.region_keys.get(&id).map(|key| self.region_by_key(key)).unwrap_or_default()
291 }
292
293 fn faction(&self, id: u32) -> u32 {
295 self.factions.get(&id).copied().unwrap_or_default()
296 }
297}