Skip to main content

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}