rpfm_extensions/startpos/world/writer/government.rs
1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! Plan and writer of the government posts of the factions (`FACTION/GOVERNMENT/CHARACTER_POSTS`).
12//!
13//! Post ids are one counter over the factions, in startpos order. The game lists each faction's post groups in a
14//! hash order; this plan uses table order.
15
16use std::collections::HashMap;
17
18use rpfm_lib::error::Result;
19use rpfm_lib::files::esf::{NodeType, RecordNode};
20
21use super::{child, WorldInputs, WorldPlan};
22use crate::startpos::world::government::{government_posts, PostGroup, FACTION_LEADER_POSITION};
23
24/// Path of a faction's government.
25const GOVERNMENT_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/FACTION_ARRAY/FACTION/GOVERNMENT";
26
27/// Path of the post groups of a government.
28const POSTS_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/FACTION_ARRAY/FACTION/GOVERNMENT/CHARACTER_POSTS";
29
30//---------------------------------------------------------------------------//
31// Types
32//---------------------------------------------------------------------------//
33
34/// Government posts of every faction, with their runtime ids.
35#[derive(Debug, Clone, Default)]
36pub struct PostPlan {
37
38 /// Post groups of each faction with the id of their first post, by faction key.
39 groups: HashMap<String, Vec<(PostGroup, u32)>>,
40}
41
42//---------------------------------------------------------------------------//
43// Implementations
44//---------------------------------------------------------------------------//
45
46impl PostPlan {
47
48 /// Plans the posts of every faction.
49 ///
50 /// # Errors
51 ///
52 /// Returns an error if a table or column is missing.
53 pub fn new(inputs: &WorldInputs, plan: &WorldPlan) -> Result<Self> {
54 let mut groups = HashMap::new();
55 let mut next_post = 1;
56 for faction in plan.factions() {
57 let mut faction_groups = vec![];
58 for group in government_posts(inputs.tables, inputs.index, faction)? {
59 let count = group.count();
60 faction_groups.push((group, next_post));
61 next_post += count;
62 }
63 groups.insert(faction.key().to_owned(), faction_groups);
64 }
65 Ok(Self { groups })
66 }
67
68 /// Returns the post groups of a faction, with the id of their first post.
69 pub fn groups_of(&self, faction: &str) -> &[(PostGroup, u32)] {
70 self.groups.get(faction).map(Vec::as_slice).unwrap_or_default()
71 }
72
73 /// Returns the id of a faction's leader post, if it has one.
74 pub fn leader_post(&self, faction: &str) -> Option<u32> {
75 self.groups_of(faction).iter().find(|(group, _)| group.position() == FACTION_LEADER_POSITION).map(|(_, id)| *id)
76 }
77}
78
79/// Writes the posts of a faction's government (`CHARACTER_POSTS`): one group per post group, with its post count and
80/// that many posts. Only the leader post has a holder.
81///
82/// # Arguments
83///
84/// * `inputs` - Tables, startpos rows and layouts.
85/// * `posts` - Government posts of the factions.
86/// * `faction` - Faction key.
87/// * `leader` - Runtime id of the faction's leader, if it has one.
88///
89/// # Errors
90///
91/// Returns an error if the layout lacks a record, or a group has more posts than the layout knows.
92pub fn write_character_posts(inputs: &WorldInputs, posts: &PostPlan, faction: &str, leader: Option<u32>) -> Result<RecordNode> {
93 let mut array = child(inputs.layout, GOVERNMENT_PATH, "CHARACTER_POSTS")?;
94 for (group, first_id) in posts.groups_of(faction) {
95 let holder = if group.position() == FACTION_LEADER_POSITION { leader.unwrap_or_default() } else { 0 };
96 let entry = array.push_group()?;
97 entry.set_u32("^+1", group.count())?;
98 for index in 0..group.count() {
99 let mut post = child(inputs.layout, POSTS_PATH, "CHARACTER_POST")?;
100 post.group()?
101 .set_u32("^+1", first_id + index)?
102 .set_str("^+2", group.position())?
103 .set_u32("^+3", holder)?
104 .set("^+5", NodeType::I64(group.unique_id()))?;
105 let key = if index == 0 { "CHARACTER_POST".to_owned() } else { format!("CHARACTER_POST@{}", index + 1) };
106 entry.set_record(&key, Some(post.build()?))?;
107 }
108 }
109 array.build()
110}