Skip to main content

rpfm_extensions/startpos/world/
rebels.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//! Rebel setup of each region (`REGION/REGION_REBEL_MANAGER/REBEL_SETUP`).
12//!
13//! Regions owned by the rebels get a setup for their alternative rebel faction. Every other region stores the same default
14//! setup (in Attila: Persis's flag with the Arian religion and an empire government, apparently a leftover).
15//! Verified on Attila's main campaign: 186 of 186 regions match.
16
17use getset::Getters;
18
19use rpfm_lib::error::{RLibError, Result};
20
21use super::RegionEntry;
22use super::factions::{FactionFlag, faction_row, flag};
23use crate::startpos::tables::StartposTables;
24
25#[cfg(test)] #[path = "rebels_test.rs"] mod rebels_test;
26
27//---------------------------------------------------------------------------//
28//                              Types
29//---------------------------------------------------------------------------//
30
31/// The rebel setup of a region owned by the rebels.
32#[derive(Debug, Clone, PartialEq, Eq, Default, Getters)]
33#[getset(get = "pub")]
34pub struct RebelSetup {
35
36    /// Rebel faction key (`start_pos_regions.alternative_rebel_faction`).
37    faction: String,
38
39    /// The region's majority religion.
40    religion: String,
41
42    /// The rebel faction's flag, with its primary, secondary and uniform colours.
43    flag: FactionFlag,
44}
45
46//---------------------------------------------------------------------------//
47//                              Implementations
48//---------------------------------------------------------------------------//
49
50/// Returns the rebel setup of a region.
51///
52/// # Arguments
53///
54/// * `tables` - Tables with `start_pos_regions`, `start_pos_region_religions` and `factions`.
55/// * `region` - The region.
56///
57/// # Returns
58///
59/// The setup for regions owned by the rebels, `None` for the others (which store the default setup).
60///
61/// # Errors
62///
63/// Returns an error if a table or column is missing, or the rebel faction isn't in `factions`.
64pub fn rebel_setup(tables: &StartposTables, region: &RegionEntry) -> Result<Option<RebelSetup>> {
65    if region.owner().is_some() {
66        return Ok(None);
67    }
68
69    let mut faction = None;
70    for row in tables.table("start_pos_regions")?.rows() {
71        if row.str("id")? == *region.id() {
72            faction = Some(row.str("alternative_rebel_faction")?.into_owned());
73        }
74    }
75    let faction = faction.ok_or_else(|| RLibError::StartposInvalidValue("start_pos_regions".to_owned(), "id".to_owned(), region.id().to_owned()))?;
76
77    let flag = flag(&faction_row(tables, &faction)?, "flags_path", ["primary_colour_hex", "secondary_colour_hex", "uniform_colour_hex"])?;
78    Ok(Some(RebelSetup {
79        religion: majority_religion(tables, region)?.unwrap_or_default(),
80        faction,
81        flag,
82    }))
83}
84
85/// Returns the religion with the largest share of a region, the first in table order on ties.
86///
87/// # Arguments
88///
89/// * `tables` - Tables with `start_pos_region_religions`.
90/// * `region` - The region.
91///
92/// # Returns
93///
94/// The religion key, or `None` if the region has no religion rows.
95///
96/// # Errors
97///
98/// Returns an error if a table or column is missing.
99pub fn majority_religion(tables: &StartposTables, region: &RegionEntry) -> Result<Option<String>> {
100    let mut best: Option<(f64, String)> = None;
101    for row in tables.table("start_pos_region_religions")?.rows() {
102        if row.str("region")? != *region.id() {
103            continue;
104        }
105        let share = row.number("percentage")?;
106        if best.as_ref().is_none_or(|(best_share, _)| share > *best_share) {
107            best = Some((share, row.str("religion")?.into_owned()));
108        }
109    }
110    Ok(best.map(|(_, religion)| religion))
111}