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: HashMap<String, Vec<String>>,
56
57 group_members: HashMap<String, Vec<String>>,
59
60 permissions: HashMap<String, HashSet<String>>,
62
63 naval: HashSet<String>,
65
66 units: HashSet<String>,
68
69 static_units: HashSet<String>,
71}
72
73pub fn garrisons(tables: &StartposTables, index: &CampaignIndex) -> Result<Vec<Garrison>> {
97 let rules = UnitRules::new(tables)?;
98 let mut garrisons = vec![];
99
100 for settlement in index.settlements() {
101 let Some(faction) = index.region(settlement.region()).and_then(|region| region.owner().as_deref()).and_then(|key| index.faction(key)) else { continue };
102 let allowed = rules.permissions.get(faction.military_group());
103 let mut garrison = Garrison {
104 region: settlement.region().to_owned(),
105 faction: faction.key().to_owned(),
106 ..Default::default()
107 };
108
109 for building in settlement.all_buildings() {
110 for group in rules.building_groups.get(building).into_iter().flatten() {
111 if let Some(unit) = rules.unit_for_group(group, allowed) {
112 if rules.naval.contains(unit) {
113 garrison.navy.push(unit.to_owned());
114 } else {
115 garrison.army.push(unit.to_owned());
116 }
117 }
118 }
119 }
120
121 garrisons.push(garrison);
122 }
123
124 Ok(garrisons)
125}
126
127impl UnitRules {
128
129 fn new(tables: &StartposTables) -> Result<Self> {
131 let mut building_groups = HashMap::<String, Vec<String>>::new();
132 for row in tables.table("building_level_armed_citizenry_junctions")?.rows() {
133 building_groups.entry(row.str("building_level")?.into_owned()).or_default().push(row.str("unit_group")?.into_owned());
134 }
135
136 let mut members = HashMap::<String, Vec<(i64, i64, String)>>::new();
137 for row in tables.table("armed_citizenry_units_to_unit_groups_junctions")?.rows() {
138 members.entry(row.str("unit_group")?.into_owned()).or_default().push((row.int("priority")?, row.int("id")?, row.str("unit")?.into_owned()));
139 }
140 let group_members = members.into_iter().map(|(group, mut list)| {
141 list.sort();
142 (group, list.into_iter().map(|(_, _, unit)| unit).collect())
143 }).collect();
144
145 let mut permissions = HashMap::<String, HashSet<String>>::new();
146 for row in tables.table("units_to_groupings_military_permissions")?.rows() {
147 permissions.entry(row.str("military_group")?.into_owned()).or_default().insert(row.str("unit")?.into_owned());
148 }
149
150 let mut fixed_engines = HashSet::new();
151 for row in tables.table("battlefield_engines")?.rows() {
152 if !row.bool("can_move")? {
153 fixed_engines.insert(row.str("key")?.into_owned());
154 }
155 }
156
157 let mut static_land_units = HashSet::new();
158 for row in tables.table("land_units")?.rows() {
159 if fixed_engines.contains(row.str("engine")?.as_ref()) {
160 static_land_units.insert(row.str("key")?.into_owned());
161 }
162 }
163
164 let mut naval = HashSet::new();
165 let mut units = HashSet::new();
166 let mut static_units = HashSet::new();
167 for row in tables.table("main_units")?.rows() {
168 let unit = row.str("unit")?.into_owned();
169 if row.bool("is_naval")? {
170 naval.insert(unit.to_owned());
171 } else if static_land_units.contains(row.str("land_unit")?.as_ref()) {
172 static_units.insert(unit.to_owned());
173 }
174 units.insert(unit);
175 }
176
177 Ok(Self { building_groups, group_members, permissions, naval, units, static_units })
178 }
179
180 fn unit_for_group<'a>(&'a self, group: &'a str, allowed: Option<&HashSet<String>>) -> Option<&'a str> {
182 if !self.units.contains(group) {
183 return None;
184 }
185
186 let is_allowed = |unit: &str| allowed.is_some_and(|allowed| allowed.contains(unit));
187 let unit = if is_allowed(group) {
188 group
189 } else {
190 self.group_members.get(group)?.iter().map(String::as_str).find(|unit| is_allowed(unit))?
191 };
192
193 (!self.static_units.contains(unit)).then_some(unit)
194 }
195}