rpfm_extensions/startpos/world/writer/
forces.rs1use std::collections::HashMap;
18
19use getset::{CopyGetters, Getters};
20
21use rpfm_lib::error::Result;
22
23use super::WorldPlan;
24use crate::startpos::tables::StartposTables;
25use crate::startpos::world::{CampaignIndex, CharacterEntry, FactionEntry};
26use crate::startpos::world::bodyguards::bodyguard;
27use crate::startpos::world::characters::{placement, Placement};
28use crate::startpos::world::garrisons::garrisons;
29use crate::startpos::world::government::faction_leader;
30use crate::startpos::world::legacy::ForceKind;
31
32#[cfg(test)] #[path = "forces_test.rs"] pub(crate) mod forces_test;
33
34const FULL_UNIT: u32 = 100;
36
37const GENERAL_AGENT: &str = "general";
39
40#[derive(Debug, Clone, Default, Getters)]
46#[getset(get = "pub")]
47pub struct ForcePlan<'a> {
48
49 characters: Vec<PlannedCharacter<'a>>,
51
52 forces: Vec<PlannedForce<'a>>,
54}
55
56#[derive(Debug, Clone, Getters, CopyGetters)]
58pub struct PlannedCharacter<'a> {
59
60 #[getset(get_copy = "pub")]
62 id: u32,
63
64 #[getset(get_copy = "pub")]
66 faction: &'a FactionEntry,
67
68 #[getset(get_copy = "pub")]
70 origin: &'a FactionEntry,
71
72 #[getset(get_copy = "pub")]
74 entry: Option<&'a CharacterEntry>,
75
76 #[getset(get_copy = "pub")]
78 force: Option<usize>,
79
80 moved: bool,
82}
83
84#[derive(Debug, Clone, Getters, CopyGetters)]
86pub struct PlannedForce<'a> {
87
88 #[getset(get_copy = "pub")]
90 id: u32,
91
92 #[getset(get_copy = "pub")]
94 faction: &'a FactionEntry,
95
96 #[getset(get_copy = "pub")]
98 kind: ForceKind,
99
100 #[getset(get = "pub")]
102 region: Option<String>,
103
104 #[getset(get = "pub")]
106 stationed_in: Option<String>,
107
108 #[getset(get = "pub")]
110 units: Vec<PlannedUnit>,
111
112 #[getset(get_copy = "pub")]
114 first_unit_id: u32,
115
116 #[getset(get_copy = "pub")]
118 commander: usize,
119
120 moved: bool,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Getters, CopyGetters)]
126pub struct PlannedUnit {
127
128 #[getset(get = "pub")]
130 unit: String,
131
132 #[getset(get_copy = "pub")]
134 soldiers: u32,
135
136 #[getset(get_copy = "pub")]
138 commands: bool,
139}
140
141impl<'a> ForcePlan<'a> {
146
147 pub fn new(tables: &StartposTables, index: &'a CampaignIndex, plan: &WorldPlan<'a>) -> Result<Self> {
159 let mut land_units = HashMap::<String, Vec<PlannedUnit>>::new();
160 for row in tables.table("start_pos_land_units")?.rows() {
161 let soldiers = u32::try_from(row.int("soldiers")?).unwrap_or(FULL_UNIT);
162 land_units.entry(row.str("general")?.into_owned()).or_default().push(PlannedUnit::new(row.str("unit_type")?.into_owned(), soldiers));
163 }
164 let mut naval_units = HashMap::<String, Vec<PlannedUnit>>::new();
165 for row in tables.table("start_pos_naval_units")?.rows() {
166 naval_units.entry(row.str("admiral")?.into_owned()).or_default().push(PlannedUnit::new(row.str("unit_type")?.into_owned(), FULL_UNIT));
167 }
168
169 let mut force_plan = Self::default();
170 for faction in plan.factions() {
171 let bodyguards = [bodyguard(tables, faction, false)?, bodyguard(tables, faction, true)?];
172 for character in characters_in_startpos_order(index, plan, faction) {
173 let placement = placement(tables, index, character)?;
174 let override_unit = Some(character.override_general_unit()).filter(|unit| !unit.is_empty());
175 let (kind, units) = match (land_units.get(character.id()), naval_units.get(character.id())) {
176 (Some(units), _) => (Some(ForceKind::Army), units.as_slice()),
177 (None, Some(units)) => (Some(ForceKind::Navy), units.as_slice()),
178 (None, None) if override_unit.is_some() && *character.agent() == GENERAL_AGENT => (Some(ForceKind::Army), [].as_slice()),
180 (None, None) => (None, [].as_slice()),
181 };
182 let commander = force_plan.characters.len();
183 let force = kind.map(|kind| {
184 let own_unit = override_unit.cloned().or_else(|| bodyguards[usize::from(kind == ForceKind::Navy)].as_ref().map(|bodyguard| bodyguard.unit().to_owned()));
185 let mut all_units = own_unit.map(|unit| PlannedUnit { unit, soldiers: FULL_UNIT, commands: true }).into_iter().collect::<Vec<_>>();
186 all_units.extend(grouped(units));
187 force_plan.push_force(faction, kind, None, all_units, commander)
188 });
189 if let (Some(force), Placement::Settlement(region)) = (force, placement) {
190 if force_plan.forces[force].kind == ForceKind::Army {
191 force_plan.forces[force].stationed_in = Some(region);
192 }
193 }
194 force_plan.push_character(faction, Some(character), force);
195 }
196 }
197
198 let garrisons = garrisons(tables, index)?.into_iter().map(|garrison| (garrison.region().to_owned(), garrison)).collect::<HashMap<_, _>>();
199 for faction in plan.factions() {
200 for region in plan.regions().iter().filter(|region| region.owner().as_deref() == Some(faction.key().as_str())) {
201 let Some(garrison) = garrisons.get(region.key()) else { continue };
202 force_plan.push_garrison(faction, ForceKind::GarrisonArmy, region.key(), garrison.army().to_vec());
203 if !garrison.navy().is_empty() {
204 force_plan.push_garrison(faction, ForceKind::GarrisonNavy, region.key(), garrison.navy().to_vec());
205 }
206 }
207 }
208
209 force_plan.move_wives(index);
210 Ok(force_plan)
211 }
212
213 fn move_wives(&mut self, index: &'a CampaignIndex) {
215 for position in 0..self.characters.len() {
216 let character = &self.characters[position];
217 let Some(husband_faction) = character.entry.and_then(|entry| entry.husband_faction().as_deref()).and_then(|key| index.faction(key)) else { continue };
218 let force = character.force;
219 self.characters[position].faction = husband_faction;
220 self.characters[position].moved = true;
221 if let Some(force) = force {
222 self.forces[force].faction = husband_faction;
223 self.forces[force].moved = true;
224 }
225 }
226 }
227
228 fn push_garrison(&mut self, faction: &'a FactionEntry, kind: ForceKind, region: &str, units: Vec<String>) {
230 let commander = self.characters.len();
231 let units = units.into_iter().enumerate().map(|(index, unit)| PlannedUnit { unit, soldiers: FULL_UNIT, commands: index == 0 }).collect();
232 let force = self.push_force(faction, kind, Some(region.to_owned()), units, commander);
233 self.push_character(faction, None, Some(force));
234 }
235
236 fn push_force(&mut self, faction: &'a FactionEntry, kind: ForceKind, region: Option<String>, units: Vec<PlannedUnit>, commander: usize) -> usize {
238 let id = u32::try_from(self.forces.len() + 1).unwrap_or(u32::MAX);
239 let first_unit_id = self.forces.last().map(|force| force.first_unit_id + u32::try_from(force.units.len()).unwrap_or(u32::MAX)).unwrap_or(1);
240 self.forces.push(PlannedForce { id, faction, kind, region, stationed_in: None, units, first_unit_id, commander, moved: false });
241 self.forces.len() - 1
242 }
243
244 fn push_character(&mut self, faction: &'a FactionEntry, entry: Option<&'a CharacterEntry>, force: Option<usize>) {
246 let id = u32::try_from(self.characters.len() + 1).unwrap_or(u32::MAX);
247 self.characters.push(PlannedCharacter { id, faction, origin: faction, entry, force, moved: false });
248 }
249
250 pub fn character_positions(&self, faction: &str) -> Vec<usize> {
253 let of = |moved: bool| self.characters.iter().enumerate()
254 .filter(move |(_, character)| *character.faction.key() == faction && character.moved == moved)
255 .map(|(position, _)| position);
256 of(false).chain(of(true)).collect()
257 }
258
259 pub fn characters_of(&self, faction: &str) -> impl Iterator<Item = &PlannedCharacter<'a>> {
261 self.character_positions(faction).into_iter().map(|position| &self.characters[position])
262 }
263
264 pub fn forces_of(&self, faction: &str) -> impl Iterator<Item = &PlannedForce<'a>> {
266 let of = |moved: bool| {
267 let faction = faction.to_owned();
268 self.forces.iter().filter(move |force| *force.faction.key() == faction && force.moved == moved)
269 };
270 of(false).chain(of(true))
271 }
272
273 pub fn stationed_in(&self, region: &str) -> Option<&PlannedForce<'a>> {
275 self.forces.iter().find(|force| force.stationed_in.as_deref() == Some(region))
276 }
277
278 pub fn garrison_of(&self, region: &str, kind: ForceKind) -> Option<&PlannedForce<'a>> {
280 self.forces.iter().find(|force| force.kind == kind && force.region.as_deref() == Some(region))
281 }
282}
283
284impl PlannedForce<'_> {
285
286 pub fn unit_ids(&self) -> Vec<u32> {
289 let count = u32::try_from(self.units.len()).unwrap_or(u32::MAX);
290 let general = matches!(self.kind, ForceKind::Army | ForceKind::Navy) && self.units.first().is_some_and(|unit| unit.commands);
291 (0..count).map(|index| match (general, index) {
292 (true, 0) => self.first_unit_id + count - 1,
293 (true, index) => self.first_unit_id + index - 1,
294 (false, index) => self.first_unit_id + index,
295 }).collect()
296 }
297
298 pub fn commander_unit_id(&self) -> Option<u32> {
300 self.units.iter().position(|unit| unit.commands).and_then(|position| self.unit_ids().get(position).copied())
301 }
302}
303
304impl PlannedUnit {
305
306 fn new(unit: String, soldiers: u32) -> Self {
308 Self { unit, soldiers, commands: false }
309 }
310}
311
312fn grouped(units: &[PlannedUnit]) -> Vec<PlannedUnit> {
315 let mut keys = Vec::<&str>::new();
316 for unit in units {
317 if !keys.contains(&unit.unit.as_str()) {
318 keys.push(&unit.unit);
319 }
320 }
321 keys.iter().flat_map(|key| units.iter().filter(move |unit| unit.unit == *key).cloned()).collect()
322}
323
324fn characters_in_startpos_order<'a>(index: &'a CampaignIndex, plan: &WorldPlan<'a>, faction: &'a FactionEntry) -> Vec<&'a CharacterEntry> {
328 let first = match plan.character_order(faction.key()) {
329 Some(order) => order.to_vec(),
330 None => faction_leader(index, faction.key()).into_iter().collect(),
331 };
332 let mut characters = Vec::<&CharacterEntry>::new();
333 for character in first.into_iter().chain(index.characters_of(faction.key()).filter(|character| character.death_type().is_empty())) {
334 if !characters.contains(&character) {
335 characters.push(character);
336 }
337 }
338 characters
339}