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