Skip to main content

rpfm_extensions/startpos/world/
availability.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//! Building chains a faction can have (`building_chain_availabilities`). The game drops the starting buildings of a
12//! settlement whose chain its owner can't have.
13
14use std::collections::HashSet;
15
16use rpfm_lib::error::Result;
17
18use crate::startpos::tables::StartposTables;
19use crate::startpos::world::FactionEntry;
20
21#[cfg(test)] #[path = "availability_test.rs"] mod availability_test;
22
23/// Returns the building chains available to a faction.
24///
25/// The chains are the ones of the availability sets of the faction's rows if it has any, else of its subculture's
26/// (rows without faction), else of its culture's (rows without faction nor subculture), looking first at the
27/// campaign's rows and then at the ones without campaign. The rows without culture, subculture nor faction add their
28/// sets to every faction.
29///
30/// # Arguments
31///
32/// * `tables` - Tables with `building_chain_availabilities` and `building_chain_availability_sets`.
33/// * `campaign` - Campaign key.
34/// * `faction` - The faction.
35///
36/// # Returns
37///
38/// The chain keys, or `None` if the tables don't restrict chains.
39///
40/// # Errors
41///
42/// Returns an error if a column is missing.
43pub fn available_chains(tables: &StartposTables, campaign: &str, faction: &FactionEntry) -> Result<Option<HashSet<String>>> {
44    let (Ok(availabilities), Ok(sets)) = (tables.table("building_chain_availabilities"), tables.table("building_chain_availability_sets")) else { return Ok(None) };
45
46    let mut campaign_rows = vec![];
47    let mut generic_rows = vec![];
48    let mut set_ids = HashSet::new();
49    for row in availabilities.rows() {
50        let row_campaign = row.str("campaign")?;
51        if !row_campaign.is_empty() && row_campaign != campaign {
52            continue;
53        }
54
55        let (row_faction, row_subculture, row_culture) = (row.str("faction")?, row.str("sub_culture")?, row.str("culture")?);
56        let set_id = row.str("set_id")?.into_owned();
57        if row_faction.is_empty() && row_subculture.is_empty() && row_culture.is_empty() {
58            set_ids.insert(set_id);
59            continue;
60        }
61
62        let level = if row_faction == *faction.key() {
63            Some(0)
64        } else if row_faction.is_empty() && !row_subculture.is_empty() && row_subculture == *faction.subculture() {
65            Some(1)
66        } else if row_faction.is_empty() && row_subculture.is_empty() && row_culture == *faction.culture() {
67            Some(2)
68        } else {
69            None
70        };
71        if let Some(level) = level {
72            let rows = if row_campaign.is_empty() { &mut generic_rows } else { &mut campaign_rows };
73            rows.push((level, set_id));
74        }
75    }
76
77    let chosen = [campaign_rows, generic_rows].into_iter()
78        .flat_map(|rows| (0..3).map(move |level| rows.iter().filter(|(row_level, _)| *row_level == level).map(|(_, set)| set.to_owned()).collect::<Vec<_>>()))
79        .find(|level_sets| !level_sets.is_empty())
80        .unwrap_or_default();
81    set_ids.extend(chosen);
82
83    let mut chains = HashSet::new();
84    for row in sets.rows() {
85        if set_ids.contains(row.str("id")?.as_ref()) {
86            chains.insert(row.str("building_chain")?.into_owned());
87        }
88    }
89    Ok(Some(chains))
90}