Skip to main content

rpfm_extensions/startpos/cai/
technologies.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//! The technologies of each faction's tree, in the order the game builds it.
12
13use std::collections::BTreeMap;
14
15use rpfm_lib::error::Result;
16
17use crate::startpos::tables::StartposTables;
18use crate::startpos::world::FactionEntry;
19
20#[cfg(test)] #[path = "technologies_test.rs"] mod technologies_test;
21
22//---------------------------------------------------------------------------//
23//                              Implementations
24//---------------------------------------------------------------------------//
25
26/// Returns the technologies of a faction's tree.
27///
28/// The tree uses the node sets of the faction if it has any, else the ones of its subculture (for no faction), else
29/// the ones of its culture (for no faction nor subculture), looking first at the campaign's sets and then at the ones
30/// without campaign. The sets go by technology category from the last (military before civil), then in reverse
31/// table order. Each set lists its
32/// nodes for the campaign and the faction by (tier, indent), the faction's own node replacing the generic one of its
33/// slot, and technologies appear once.
34///
35/// # Arguments
36///
37/// * `tables` - Tables with `technology_node_sets` and `technology_nodes`.
38/// * `campaign` - Campaign key.
39/// * `faction` - The faction.
40///
41/// # Returns
42///
43/// The technology keys, in tree order.
44///
45/// # Errors
46///
47/// Returns an error if a table or column is missing.
48pub fn faction_technologies(tables: &StartposTables, campaign: &str, faction: &FactionEntry) -> Result<Vec<String>> {
49    let for_campaign = |key: &str| key.is_empty() || key == campaign;
50
51    let mut campaign_sets = vec![];
52    let mut generic_sets = vec![];
53    for row in tables.table("technology_node_sets")?.rows() {
54        let set_campaign = row.str("campaign_key")?;
55        if for_campaign(&set_campaign) {
56            let set = (row.str("key")?.into_owned(), row.str("faction_key")?.into_owned(), row.str("subculture")?.into_owned(), row.str("culture")?.into_owned(), row.str("technology_category")?.into_owned());
57            if set_campaign.is_empty() { generic_sets.push(set) } else { campaign_sets.push(set) }
58        }
59    }
60    let mut chosen = [&campaign_sets, &generic_sets].into_iter()
61        .flat_map(|sets| [
62            sets.iter().filter(|set| set.1 == *faction.key()).collect::<Vec<_>>(),
63            sets.iter().filter(|set| set.1.is_empty() && set.2 == *faction.subculture()).collect(),
64            sets.iter().filter(|set| set.1.is_empty() && set.2.is_empty() && set.3 == *faction.culture()).collect(),
65        ])
66        .find(|sets| !sets.is_empty())
67        .unwrap_or_default();
68    chosen.reverse();
69    chosen.sort_by(|a, b| b.4.cmp(&a.4));
70
71    let nodes = tables.table("technology_nodes")?;
72    let mut technologies: Vec<String> = vec![];
73    for (set, ..) in chosen {
74        let mut slots = BTreeMap::new();
75        for row in nodes.rows() {
76            let node_faction = row.str("faction_key")?;
77            if row.str("technology_node_set")? != *set || !for_campaign(&row.str("campaign_key")?) || !(node_faction.is_empty() || node_faction == *faction.key()) {
78                continue;
79            }
80
81            // The faction's own node wins its slot over the generic one.
82            let slot = (row.int("tier")?, row.int("indent")?);
83            let own = !node_faction.is_empty();
84            let technology = row.str("technology_key")?.into_owned();
85            match slots.get(&slot) {
86                Some((true, _)) if !own => {},
87                _ => { slots.insert(slot, (own, technology)); },
88            }
89        }
90        for (_, technology) in slots.into_values() {
91            if !technologies.contains(&technology) {
92                technologies.push(technology);
93            }
94        }
95    }
96    Ok(technologies)
97}