1use getset::{CopyGetters, Getters};
15
16use rpfm_lib::error::{RLibError, Result};
17use rpfm_lib::files::esf::RecordNode;
18
19use crate::startpos::layout::{child_path, GroupView, StartposLayout};
20
21#[cfg(test)] #[path = "world_view_test.rs"] mod world_view_test;
22
23const WORLD_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD";
25
26const TRADE_MANAGER_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/CAMPAIGN_TRADE_MANAGER";
28
29#[derive(Debug, Clone, Default, Getters)]
35#[getset(get = "pub")]
36pub struct WorldView {
37
38 factions: Vec<FactionView>,
40
41 regions: Vec<RegionView>,
43
44 provinces: Vec<ProvinceView>,
46
47 seas: Vec<SeaView>,
49}
50
51#[derive(Debug, Clone, Default, Getters, CopyGetters)]
53pub struct FactionView {
54
55 #[getset(get_copy = "pub")]
57 id: u32,
58
59 #[getset(get = "pub")]
61 key: String,
62
63 #[getset(get = "pub")]
65 characters: Vec<CharacterView>,
66
67 #[getset(get = "pub")]
69 forces: Vec<ForceView>,
70
71 #[getset(get = "pub")]
73 posts: Vec<(u32, u32)>,
74}
75
76#[derive(Debug, Clone, Default, Getters, CopyGetters)]
78pub struct CharacterView {
79
80 #[getset(get_copy = "pub")]
82 id: u32,
83
84 #[getset(get = "pub")]
86 agent: String,
87
88 #[getset(get_copy = "pub")]
90 cell: (u16, u16),
91
92 #[getset(get_copy = "pub")]
94 force: u32,
95
96 #[getset(get_copy = "pub")]
98 unit: u32,
99}
100
101#[derive(Debug, Clone, Default, Getters, CopyGetters)]
103pub struct ForceView {
104
105 #[getset(get_copy = "pub")]
107 id: u32,
108
109 #[getset(get_copy = "pub")]
111 commander: u32,
112
113 #[getset(get = "pub")]
115 units: Vec<u32>,
116
117 #[getset(get_copy = "pub")]
119 residence: u32,
120
121 #[getset(get_copy = "pub")]
123 garrison_army_of: u32,
124
125 #[getset(get_copy = "pub")]
127 garrison_navy_of: u32,
128
129 #[getset(get_copy = "pub")]
131 navy: bool,
132
133 #[getset(get = "pub")]
135 horde_slots: Vec<u32>,
136}
137
138#[derive(Debug, Clone, Default, Getters, CopyGetters)]
140pub struct RegionView {
141
142 #[getset(get_copy = "pub")]
144 id: u32,
145
146 #[getset(get = "pub")]
148 key: String,
149
150 #[getset(get_copy = "pub")]
152 owner: u32,
153
154 #[getset(get_copy = "pub")]
156 residence: u32,
157
158 #[getset(get = "pub")]
160 slots: Vec<u32>,
161}
162
163#[derive(Debug, Clone, Default, Getters, CopyGetters)]
165pub struct ProvinceView {
166
167 #[getset(get_copy = "pub")]
169 id: u32,
170
171 #[getset(get = "pub")]
173 key: String,
174
175 #[getset(get = "pub")]
177 regions: Vec<String>,
178
179 #[getset(get = "pub")]
181 managers: Vec<FactionPartView>,
182}
183
184#[derive(Debug, Clone, Default, Getters, CopyGetters)]
186pub struct SeaView {
187
188 #[getset(get_copy = "pub")]
190 id: u32,
191
192 #[getset(get = "pub")]
194 key: String,
195
196 #[getset(get = "pub")]
198 managers: Vec<FactionPartView>,
199}
200
201#[derive(Debug, Clone, Default, Getters, CopyGetters)]
203pub struct FactionPartView {
204
205 #[getset(get_copy = "pub")]
207 id: u32,
208
209 #[getset(get_copy = "pub")]
211 faction: u32,
212
213 #[getset(get = "pub")]
215 regions: Vec<u32>,
216
217 #[getset(get_copy = "pub")]
219 main_region: u32,
220}
221
222impl WorldView {
227
228 pub fn new(layout: &StartposLayout, world: &RecordNode) -> Result<Self> {
239 let reader = Reader { layout };
240 let world_group = layout.read_single(WORLD_PATH, world)?;
241
242 let faction_array = child_path(WORLD_PATH, "FACTION_ARRAY");
243 let factions = layout.read_items(&faction_array, &world_group, "FACTION_ARRAY", "FACTION")?.into_iter()
244 .map(|(_, faction)| reader.faction(&child_path(&faction_array, "FACTION"), &faction))
245 .collect::<Result<Vec<_>>>()?;
246
247 let manager_path = child_path(WORLD_PATH, "REGION_MANAGER");
248 let manager = layout.read_single(&manager_path, reader.record(&world_group, "REGION_MANAGER")?)?;
249 let region_array = child_path(&manager_path, "REGIONS_ARRAY");
250 let regions = layout.read_items(®ion_array, &manager, "REGIONS_ARRAY", "REGION")?.into_iter()
251 .map(|(_, region)| reader.region(&child_path(®ion_array, "REGION"), ®ion))
252 .collect::<Result<Vec<_>>>()?;
253
254 let manager_path = child_path(WORLD_PATH, "PROVINCE_MANAGER");
255 let manager = layout.read_single(&manager_path, reader.record(&world_group, "PROVINCE_MANAGER")?)?;
256 let province_array = child_path(&manager_path, "PROVINCE_ARRAY");
257 let province_path = child_path(&province_array, "PROVINCE");
258 let provinces = layout.read_items(&province_array, &manager, "PROVINCE_ARRAY", "PROVINCE")?.into_iter()
259 .map(|(entry, province)| Ok(ProvinceView {
260 id: province.u32("^+1").unwrap_or_default(),
261 key: entry.str("^+1").unwrap_or_default().to_owned(),
262 regions: reader.province_regions(&province_path, &province)?,
263 managers: reader.parts(&province_path, &province, "FACTION_PROVINCE_MANAGER_ARRAY", "FACTION_PROVINCE_MANAGER")?,
264 }))
265 .collect::<Result<Vec<_>>>()?;
266
267 let manager_path = child_path(WORLD_PATH, "SEA_REGION_MANAGER");
268 let manager = layout.read_single(&manager_path, reader.record(&world_group, "SEA_REGION_MANAGER")?)?;
269 let sea_block = child_path(&manager_path, "SEA_REGIONS_BLOCK");
270 let sea_path = child_path(&sea_block, "SEA_REGION");
271 let seas = layout.read_items(&sea_block, &manager, "SEA_REGIONS_BLOCK", "SEA_REGION")?.into_iter()
272 .map(|(entry, sea)| Ok(SeaView {
273 id: sea.u32("^+1").unwrap_or_default(),
274 key: entry.str("^+1").unwrap_or_default().to_owned(),
275 managers: reader.parts(&sea_path, &sea, "FACTION_SEA_REGION_MANAGER_BLOCK", "FACTION_SEA_REGION_MANAGER")?,
276 }))
277 .collect::<Result<Vec<_>>>()?;
278
279 Ok(Self { factions, regions, provinces, seas })
280 }
281}
282
283pub fn international_routes(layout: &StartposLayout, trade_manager: &RecordNode) -> Result<Vec<(u32, u32)>> {
298 let manager = layout.read_single(TRADE_MANAGER_PATH, trade_manager)?;
299 let Some(routes) = manager.record("INTERNATIONAL_TRADE_ROUTES") else { return Ok(vec![]) };
300
301 let routes_path = child_path(TRADE_MANAGER_PATH, "INTERNATIONAL_TRADE_ROUTES");
302 let faction_routes_path = child_path(&routes_path, "FACTION_INTERNATIONAL_TRADE_ROUTES_ARRAY");
303 let mut list = vec![];
304 for faction in layout.read(&routes_path, routes)? {
305 let owner = faction.u32("^+1").unwrap_or_default();
306 for (_, route) in layout.read_items(&faction_routes_path, &faction, "FACTION_INTERNATIONAL_TRADE_ROUTES_ARRAY", "INTERNATIONAL_TRADE_ROUTE")? {
307 list.push((owner, route.u32("^+1").unwrap_or_default()));
308 }
309 }
310 Ok(list)
311}
312
313struct Reader<'l> {
315 layout: &'l StartposLayout,
316}
317
318impl<'l> Reader<'l> {
319
320 fn record<'a>(&self, group: &GroupView<'a>, key: &str) -> Result<&'a RecordNode> {
322 group.record(key).ok_or_else(|| world_error(&format!("missing {key}")))
323 }
324
325 fn faction(&self, path: &str, faction: &GroupView) -> Result<FactionView> {
327 let character_array = child_path(path, "CHARACTER_ARRAY");
328 let character_path = child_path(&character_array, "CHARACTER");
329 let characters = self.layout.read_items(&character_array, faction, "CHARACTER_ARRAY", "CHARACTER")?.into_iter()
330 .map(|(_, character)| {
331 let cell = match character.record("LOCOMOTABLE") {
332 Some(record) => {
333 let locomotable = self.layout.read_single(&child_path(&character_path, "LOCOMOTABLE"), record)?;
334 (locomotable.u32("^+2").unwrap_or_default() as u16, locomotable.u32("^+3").unwrap_or_default() as u16)
335 },
336 None => (0, 0),
337 };
338 Ok(CharacterView {
339 id: character.u32("LOCOMOTABLE+1").unwrap_or_default(),
340 agent: character.str("CHARACTER_DETAILS+1").unwrap_or_default().to_owned(),
341 cell,
342 force: character.u32("CHARACTER_DETAILS+2").unwrap_or_default(),
343 unit: character.u32("CHARACTER_DETAILS+3").unwrap_or_default(),
344 })
345 })
346 .collect::<Result<Vec<_>>>()?;
347
348 let army_array = child_path(path, "ARMY_ARRAY");
349 let force_path = child_path(&army_array, "MILITARY_FORCE");
350 let forces = self.layout.read_items(&army_array, faction, "ARMY_ARRAY", "MILITARY_FORCE")?.into_iter()
351 .map(|(_, force)| self.force(&force_path, &force))
352 .collect::<Result<Vec<_>>>()?;
353
354 let government_path = child_path(path, "GOVERNMENT");
355 let mut posts = vec![];
356 if let Some(government) = faction.record("GOVERNMENT") {
357 let government = self.layout.read_single(&government_path, government)?;
358 let posts_path = child_path(&government_path, "CHARACTER_POSTS");
359 let post_path = child_path(&posts_path, "CHARACTER_POST");
360 if let Some(array) = government.record("CHARACTER_POSTS") {
361 for entry in self.layout.read(&posts_path, array)? {
362 for index in 1.. {
363 let key = if index == 1 { "CHARACTER_POST".to_owned() } else { format!("CHARACTER_POST@{index}") };
364 let Some(post) = entry.record(&key) else { break };
365 let post = self.layout.read_single(&post_path, post)?;
366 posts.push((post.u32("^+1").unwrap_or_default(), post.u32("^+3").unwrap_or_default()));
367 }
368 }
369 }
370 }
371
372 Ok(FactionView {
373 id: faction.u32("^+1").unwrap_or_default(),
374 key: faction.str("^+2").unwrap_or_default().to_owned(),
375 characters,
376 forces,
377 posts,
378 })
379 }
380
381 fn force(&self, path: &str, force: &GroupView) -> Result<ForceView> {
383 let container_path = child_path(path, "UNIT_CONTAINER");
384 let units_path = child_path(&container_path, "UNITS_ARRAY");
385 let mut units = vec![];
386 if let Some(container) = force.record("UNIT_CONTAINER") {
387 let container = self.layout.read_single(&container_path, container)?;
388 for (_, unit) in self.layout.read_items(&units_path, &container, "UNITS_ARRAY", "UNIT")? {
389 units.push(unit.u32("^+1").unwrap_or_default());
390 }
391 }
392
393 let horde_path = child_path(path, "MILITARY_FORCE_HORDE_DETAILS");
394 let slot_manager_path = child_path(&horde_path, "MILITARY_FORCE_SLOT_MANAGER");
395 let slot_block_path = child_path(&slot_manager_path, "SLOT_BLOCK");
396 let mut horde_slots = vec![];
397 if let Some(horde) = force.record("MILITARY_FORCE_HORDE_DETAILS") {
398 let horde = self.layout.read_single(&horde_path, horde)?;
399 if let Some(manager) = horde.record("MILITARY_FORCE_SLOT_MANAGER") {
400 let manager = self.layout.read_single(&slot_manager_path, manager)?;
401 for (_, slot) in self.layout.read_items(&slot_block_path, &manager, "SLOT_BLOCK", "MILITARY_FORCE_SLOT")? {
402 horde_slots.push(slot.u32("^+1").unwrap_or_default());
403 }
404 }
405 }
406
407 Ok(ForceView {
408 id: force.u32("^+1").unwrap_or_default(),
409 commander: force.u32("^+2").unwrap_or_default(),
410 units,
411 residence: force.u32("UNIT_CONTAINER+1").unwrap_or_default(),
412 garrison_army_of: force.u32("UNIT_CONTAINER+11").unwrap_or_default(),
413 garrison_navy_of: force.u32("UNIT_CONTAINER+12").unwrap_or_default(),
414 navy: force.bool("UNIT_CONTAINER+13").unwrap_or_default(),
415 horde_slots,
416 })
417 }
418
419 fn region(&self, path: &str, region: &GroupView) -> Result<RegionView> {
421 let settlement_path = child_path(path, "SETTLEMENT");
422 let mut residence = 0;
423 if let Some(settlement) = region.record("SETTLEMENT") {
424 let settlement = self.layout.read_single(&settlement_path, settlement)?;
425 if let Some(record) = settlement.record("GARRISON_RESIDENCE") {
426 residence = self.layout.read_single(&child_path(&settlement_path, "GARRISON_RESIDENCE"), record)?.u32("^+1").unwrap_or_default();
427 }
428 }
429
430 let slot_manager_path = child_path(path, "REGION_SLOT_MANAGER");
431 let slot_array_path = child_path(&slot_manager_path, "REGION_SLOT_ARRAY");
432 let mut slots = vec![];
433 if let Some(manager) = region.record("REGION_SLOT_MANAGER") {
434 let manager = self.layout.read_single(&slot_manager_path, manager)?;
435 for (_, slot) in self.layout.read_items(&slot_array_path, &manager, "REGION_SLOT_ARRAY", "REGION_SLOT")? {
436 slots.push(slot.u32("^+1").unwrap_or_default());
437 }
438 }
439
440 Ok(RegionView {
441 id: region.u32("^+1").unwrap_or_default(),
442 key: region.str("^+2").unwrap_or_default().to_owned(),
443 owner: region.u32("SETTLEMENT+9").unwrap_or_default(),
444 residence,
445 slots,
446 })
447 }
448
449 fn province_regions(&self, path: &str, province: &GroupView) -> Result<Vec<String>> {
451 let fertility_path = child_path(path, "PROVINCE_FERTILITY");
452 let Some(fertility) = province.record("PROVINCE_FERTILITY") else { return Ok(vec![]) };
453 let fertility = self.layout.read_single(&fertility_path, fertility)?;
454 let block_path = child_path(&fertility_path, "REGION_BLOCK");
455 let Some(block) = fertility.record("REGION_BLOCK") else { return Ok(vec![]) };
456 Ok(self.layout.read(&block_path, block)?.iter().map(|entry| entry.str("^+1").unwrap_or_default().to_owned()).collect())
457 }
458
459 fn parts(&self, path: &str, parent: &GroupView, array_key: &str, item_key: &str) -> Result<Vec<FactionPartView>> {
461 let array_path = child_path(path, array_key);
462 self.layout.read_items(&array_path, parent, array_key, item_key)?.into_iter()
463 .map(|(_, part)| Ok(FactionPartView {
464 id: part.u32("^+1").unwrap_or_default(),
465 faction: part.u32("^+2").unwrap_or_default(),
466 regions: part.u32s("^+3").map(<[u32]>::to_vec).unwrap_or_default(),
467 main_region: part.u32("CAMPAIGN_EFFECT_BUNDLES+4").unwrap_or_default(),
468 }))
469 .collect()
470 }
471}
472
473fn world_error(message: &str) -> RLibError {
475 RLibError::StartposLayoutError(format!("reading the world: {message}"))
476}