rpfm_extensions/startpos/world/
garrisons.rs1use getset::Getters;
19
20use std::collections::{HashMap, HashSet};
21
22use rpfm_lib::error::Result;
23
24use super::CampaignIndex;
25use crate::startpos::tables::StartposTables;
26
27#[cfg(test)] #[path = "garrisons_test.rs"] pub(crate) mod garrisons_test;
28
29#[derive(Debug, Clone, PartialEq, Eq, Default, Getters)]
35#[getset(get = "pub")]
36pub struct Garrison {
37
38 region: String,
40
41 faction: String,
43
44 army: Vec<String>,
46
47 navy: Vec<String>,
49}
50
51struct UnitRules {
53
54 building_groups: Vec<(String, String)>,
57
58 group_members: HashMap<String, Vec<String>>,
60
61 permissions: HashMap<String, HashSet<String>>,
63
64 naval: HashSet<String>,
66
67 units: HashSet<String>,
69
70 static_units: HashSet<String>,
72}
73
74pub fn garrisons(tables: &StartposTables, index: &CampaignIndex) -> Result<Vec<Garrison>> {
99 let rules = UnitRules::new(tables)?;
100 let mut garrisons = vec![];
101
102 for settlement in index.settlements() {
103 let Some(faction) = index.region(settlement.region()).and_then(|region| region.owner().as_deref()).and_then(|key| index.faction(key)) else { continue };
104 let allowed = rules.permissions.get(faction.military_group());
105 let mut garrison = Garrison {
106 region: settlement.region().to_owned(),
107 faction: faction.key().to_owned(),
108 ..Default::default()
109 };
110
111 let buildings = settlement.all_buildings().collect::<Vec<_>>();
112 for (level, group) in &rules.building_groups {
113 for _ in buildings.iter().filter(|building| **building == level) {
114 if let Some(unit) = rules.unit_for_group(group, allowed) {
115 if rules.naval.contains(unit) {
116 garrison.navy.push(unit.to_owned());
117 } else {
118 garrison.army.push(unit.to_owned());
119 }
120 }
121 }
122 }
123
124 garrisons.push(garrison);
125 }
126
127 Ok(garrisons)
128}
129
130impl UnitRules {
131
132 fn new(tables: &StartposTables) -> Result<Self> {
134 let mut building_groups = vec![];
135 for row in tables.table("building_level_armed_citizenry_junctions")?.rows_in_key_order("id")? {
136 building_groups.push((row.str("building_level")?.into_owned(), row.str("unit_group")?.into_owned()));
137 }
138
139 let mut members = HashMap::<String, Vec<(i64, i64, String)>>::new();
140 for row in tables.table("armed_citizenry_units_to_unit_groups_junctions")?.rows() {
141 members.entry(row.str("unit_group")?.into_owned()).or_default().push((row.int("priority")?, row.int("id")?, row.str("unit")?.into_owned()));
142 }
143 let group_members = members.into_iter().map(|(group, mut list)| {
144 list.sort();
145 (group, list.into_iter().map(|(_, _, unit)| unit).collect())
146 }).collect();
147
148 let mut permissions = HashMap::<String, HashSet<String>>::new();
149 for row in tables.table("units_to_groupings_military_permissions")?.rows() {
150 permissions.entry(row.str("military_group")?.into_owned()).or_default().insert(row.str("unit")?.into_owned());
151 }
152
153 let mut fixed_engines = HashSet::new();
154 for row in tables.table("battlefield_engines")?.rows() {
155 if !row.bool("can_move")? {
156 fixed_engines.insert(row.str("key")?.into_owned());
157 }
158 }
159
160 let mut static_land_units = HashSet::new();
161 for row in tables.table("land_units")?.rows() {
162 if fixed_engines.contains(row.str("engine")?.as_ref()) {
163 static_land_units.insert(row.str("key")?.into_owned());
164 }
165 }
166
167 let mut naval = HashSet::new();
168 let mut units = HashSet::new();
169 let mut static_units = HashSet::new();
170 for row in tables.table("main_units")?.rows() {
171 let unit = row.str("unit")?.into_owned();
172 if row.bool("is_naval")? {
173 naval.insert(unit.to_owned());
174 } else if static_land_units.contains(row.str("land_unit")?.as_ref()) {
175 static_units.insert(unit.to_owned());
176 }
177 units.insert(unit);
178 }
179
180 Ok(Self { building_groups, group_members, permissions, naval, units, static_units })
181 }
182
183 fn unit_for_group<'a>(&'a self, group: &'a str, allowed: Option<&HashSet<String>>) -> Option<&'a str> {
185 if !self.units.contains(group) {
186 return None;
187 }
188
189 let is_allowed = |unit: &str| allowed.is_some_and(|allowed| allowed.contains(unit));
190 let unit = if is_allowed(group) {
191 group
192 } else {
193 self.group_members.get(group)?.iter().map(String::as_str).find(|unit| is_allowed(unit))?
194 };
195
196 (!self.static_units.contains(unit)).then_some(unit)
197 }
198}