1use std::collections::HashMap;
29
30use getset::{CopyGetters, Getters};
31
32use rpfm_lib::error::Result;
33
34use super::WorldPlan;
35use crate::startpos::tables::{Row, StartposTables};
36use crate::startpos::world::{CampaignIndex, CharacterEntry, FactionEntry};
37use crate::startpos::world::bodyguards::bodyguard;
38use crate::startpos::world::characters::{placement, Placement};
39use crate::startpos::world::garrisons::garrisons;
40use crate::startpos::world::government::faction_leader;
41use crate::startpos::world::legacy::ForceKind;
42
43#[cfg(test)] #[path = "forces_test.rs"] pub(crate) mod forces_test;
44
45const FULL_UNIT: u32 = 100;
47
48const GENERAL_AGENT: &str = "general";
50
51#[derive(Debug, Clone, Default, Getters)]
57#[getset(get = "pub")]
58pub struct ForcePlan<'a> {
59
60 characters: Vec<PlannedCharacter<'a>>,
62
63 forces: Vec<PlannedForce<'a>>,
65
66 #[getset(skip)]
69 list_rank: HashMap<String, i64>,
70}
71
72#[derive(Debug, Clone, Getters, CopyGetters)]
74pub struct PlannedCharacter<'a> {
75
76 #[getset(get_copy = "pub")]
78 id: u32,
79
80 #[getset(get_copy = "pub")]
82 faction: &'a FactionEntry,
83
84 #[getset(get_copy = "pub")]
86 origin: &'a FactionEntry,
87
88 #[getset(get_copy = "pub")]
90 entry: Option<&'a CharacterEntry>,
91
92 #[getset(get_copy = "pub")]
94 force: Option<usize>,
95
96 moved: bool,
98}
99
100#[derive(Debug, Clone, Getters, CopyGetters)]
102pub struct PlannedForce<'a> {
103
104 #[getset(get_copy = "pub")]
106 id: u32,
107
108 #[getset(get_copy = "pub")]
110 faction: &'a FactionEntry,
111
112 #[getset(get_copy = "pub")]
114 kind: ForceKind,
115
116 #[getset(get = "pub")]
118 region: Option<String>,
119
120 #[getset(get = "pub")]
122 stationed_in: Option<String>,
123
124 #[getset(get = "pub")]
126 units: Vec<PlannedUnit>,
127
128 unit_ids: Vec<u32>,
130
131 #[getset(get_copy = "pub")]
133 first_unit_id: u32,
134
135 #[getset(get_copy = "pub")]
137 commander: usize,
138
139 moved: bool,
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, Getters, CopyGetters)]
145pub struct PlannedUnit {
146
147 #[getset(get = "pub")]
149 unit: String,
150
151 #[getset(get_copy = "pub")]
153 soldiers: u32,
154
155 #[getset(get_copy = "pub")]
157 commands: bool,
158}
159
160impl<'a> ForcePlan<'a> {
165
166 pub fn new(tables: &StartposTables, index: &'a CampaignIndex, plan: &WorldPlan<'a>) -> Result<Self> {
178 let mut land_units = HashMap::<String, Vec<PlannedUnit>>::new();
179 for row in unit_rows(tables, "start_pos_land_units")? {
180 let soldiers = u32::try_from(row.int("soldiers")?).unwrap_or(FULL_UNIT);
181 land_units.entry(row.str("general")?.into_owned()).or_default().push(PlannedUnit::new(row.str("unit_type")?.into_owned(), soldiers));
182 }
183 let mut naval_units = HashMap::<String, Vec<PlannedUnit>>::new();
184 for row in unit_rows(tables, "start_pos_naval_units")? {
185 naval_units.entry(row.str("admiral")?.into_owned()).or_default().push(PlannedUnit::new(row.str("unit_type")?.into_owned(), FULL_UNIT));
186 }
187
188 let mut force_plan = Self { list_rank: list_ranks(tables), ..Self::default() };
189 for faction in plan.factions() {
190 let bodyguards = [bodyguard(tables, faction, false)?, bodyguard(tables, faction, true)?];
191 for character in characters_in_startpos_order(index, plan, faction) {
192 let placement = placement(tables, index, character)?;
193 let override_unit = Some(character.override_general_unit()).filter(|unit| !unit.is_empty());
194 let (kind, units) = match (land_units.get(character.id()), naval_units.get(character.id())) {
195 (Some(units), _) => (Some(ForceKind::Army), units.as_slice()),
196 (None, Some(units)) => (Some(ForceKind::Navy), units.as_slice()),
197 (None, None) if override_unit.is_some() && *character.agent() == GENERAL_AGENT => (Some(ForceKind::Army), [].as_slice()),
199 (None, None) => (None, [].as_slice()),
200 };
201 let commander = force_plan.characters.len();
202 let force = kind.map(|kind| {
203 let own_unit = override_unit.cloned().or_else(|| bodyguards[usize::from(kind == ForceKind::Navy)].as_ref().map(|bodyguard| bodyguard.unit().to_owned()));
204 let mut all_units = own_unit.map(|unit| PlannedUnit { unit, soldiers: FULL_UNIT, commands: true }).into_iter().collect::<Vec<_>>();
205 all_units.extend(units.iter().cloned());
206 force_plan.push_force(faction, kind, None, all_units, commander)
207 });
208 if let (Some(force), Placement::Settlement(region)) = (force, placement) {
209 if force_plan.forces[force].kind == ForceKind::Army {
210 force_plan.forces[force].stationed_in = Some(region);
211 }
212 }
213 force_plan.push_character(faction, Some(character), force);
214 }
215 }
216
217 let garrisons = garrisons(tables, index)?.into_iter().map(|garrison| (garrison.region().to_owned(), garrison)).collect::<HashMap<_, _>>();
218 for faction in plan.factions() {
219 for region in plan.regions().iter().filter(|region| region.owner().as_deref() == Some(faction.key().as_str())) {
220 let Some(garrison) = garrisons.get(region.key()) else { continue };
221
222 if !garrison.army().is_empty() {
224 force_plan.push_garrison(faction, ForceKind::GarrisonArmy, region.key(), garrison.army().to_vec());
225 }
226 if !garrison.navy().is_empty() {
227 force_plan.push_garrison(faction, ForceKind::GarrisonNavy, region.key(), garrison.navy().to_vec());
228 }
229 }
230 }
231
232 force_plan.move_wives(index);
233 Ok(force_plan)
234 }
235
236 fn move_wives(&mut self, index: &'a CampaignIndex) {
238 for position in 0..self.characters.len() {
239 let character = &self.characters[position];
240 let Some(husband_faction) = character.entry.and_then(|entry| entry.husband_faction().as_deref()).and_then(|key| index.faction(key)) else { continue };
241 let force = character.force;
242 self.characters[position].faction = husband_faction;
243 self.characters[position].moved = true;
244 if let Some(force) = force {
245 self.forces[force].faction = husband_faction;
246 self.forces[force].moved = true;
247 }
248 }
249 }
250
251 fn push_garrison(&mut self, faction: &'a FactionEntry, kind: ForceKind, region: &str, units: Vec<String>) {
253 let commander = self.characters.len();
254 let units = units.into_iter().enumerate().map(|(index, unit)| PlannedUnit { unit, soldiers: FULL_UNIT, commands: index == 0 }).collect();
255 let force = self.push_force(faction, kind, Some(region.to_owned()), units, commander);
256 self.push_character(faction, None, Some(force));
257 }
258
259 fn push_force(&mut self, faction: &'a FactionEntry, kind: ForceKind, region: Option<String>, units: Vec<PlannedUnit>, commander: usize) -> usize {
261 let id = u32::try_from(self.forces.len() + 1).unwrap_or(u32::MAX);
262 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);
263 let mut force = PlannedForce { id, faction, kind, region, stationed_in: None, units, unit_ids: vec![], first_unit_id, commander, moved: false };
264 force.unit_ids = force.creation_ids();
265
266 if kind == ForceKind::Army && !self.list_rank.is_empty() {
268 let skip = usize::from(force.units.first().is_some_and(|unit| unit.commands));
269 let mut listed = force.units.drain(skip..).zip(force.unit_ids.drain(skip..)).collect::<Vec<_>>();
270 listed.sort_by_key(|(unit, _)| self.list_rank.get(&unit.unit).copied().unwrap_or(i64::MAX));
271 for (unit, id) in listed {
272 force.units.push(unit);
273 force.unit_ids.push(id);
274 }
275 }
276 self.forces.push(force);
277 self.forces.len() - 1
278 }
279
280 fn push_character(&mut self, faction: &'a FactionEntry, entry: Option<&'a CharacterEntry>, force: Option<usize>) {
282 let id = u32::try_from(self.characters.len() + 1).unwrap_or(u32::MAX);
283 self.characters.push(PlannedCharacter { id, faction, origin: faction, entry, force, moved: false });
284 }
285
286 pub fn character_positions(&self, faction: &str) -> Vec<usize> {
289 let of = |moved: bool| self.characters.iter().enumerate()
290 .filter(move |(_, character)| *character.faction.key() == faction && character.moved == moved)
291 .map(|(position, _)| position);
292 of(false).chain(of(true)).collect()
293 }
294
295 pub fn characters_of(&self, faction: &str) -> impl Iterator<Item = &PlannedCharacter<'a>> {
297 self.character_positions(faction).into_iter().map(|position| &self.characters[position])
298 }
299
300 pub fn forces_of(&self, faction: &str) -> impl Iterator<Item = &PlannedForce<'a>> {
302 let of = |moved: bool| {
303 let faction = faction.to_owned();
304 self.forces.iter().filter(move |force| *force.faction.key() == faction && force.moved == moved)
305 };
306 of(false).chain(of(true))
307 }
308
309 pub fn stationed_in(&self, region: &str) -> Option<&PlannedForce<'a>> {
311 self.forces.iter().find(|force| force.stationed_in.as_deref() == Some(region))
312 }
313
314 pub fn garrison_of(&self, region: &str, kind: ForceKind) -> Option<&PlannedForce<'a>> {
316 self.forces.iter().find(|force| force.kind == kind && force.region.as_deref() == Some(region))
317 }
318}
319
320impl PlannedForce<'_> {
321
322 pub fn unit_ids(&self) -> Vec<u32> {
324 self.unit_ids.clone()
325 }
326
327 fn creation_ids(&self) -> Vec<u32> {
330 let count = u32::try_from(self.units.len()).unwrap_or(u32::MAX);
331 let general = matches!(self.kind, ForceKind::Army | ForceKind::Navy) && self.units.first().is_some_and(|unit| unit.commands);
332 (0..count).map(|index| match (general, index) {
333 (true, 0) => self.first_unit_id + count - 1,
334 (true, index) => self.first_unit_id + index - 1,
335 (false, index) => self.first_unit_id + index,
336 }).collect()
337 }
338
339 pub fn commander_unit_id(&self) -> Option<u32> {
341 self.units.iter().position(|unit| unit.commands).and_then(|position| self.unit_ids().get(position).copied())
342 }
343}
344
345impl PlannedUnit {
346
347 fn new(unit: String, soldiers: u32) -> Self {
349 Self { unit, soldiers, commands: false }
350 }
351}
352
353fn list_ranks(tables: &StartposTables) -> HashMap<String, i64> {
356 let ranks = || -> Result<HashMap<String, i64>> {
357 let mut group_order = HashMap::new();
358 for row in tables.table("ui_unit_groupings")?.rows() {
359 group_order.insert(row.str("key")?.into_owned(), row.int("order")?);
360 }
361 let mut ranks = HashMap::new();
362 for row in tables.table("main_units")?.rows() {
363 if let Some(order) = group_order.get(row.str("ui_unit_group_land")?.as_ref()) {
364 ranks.insert(row.str("unit")?.into_owned(), *order);
365 }
366 }
367 Ok(ranks)
368 };
369 ranks().unwrap_or_default()
370}
371
372fn unit_rows<'t>(tables: &'t StartposTables, name: &str) -> Result<Vec<Row<'t>>> {
375 let table = tables.table(name)?;
376 let column = if table.column("id").is_ok() { "id" } else { "ID" };
377 table.rows_in_key_order(column)
378}
379
380fn characters_in_startpos_order<'a>(index: &'a CampaignIndex, plan: &WorldPlan<'a>, faction: &'a FactionEntry) -> Vec<&'a CharacterEntry> {
384 let first = match plan.character_order(faction.key()) {
385 Some(order) => order.to_vec(),
386 None => faction_leader(index, faction.key()).into_iter().collect(),
387 };
388 let mut characters = Vec::<&CharacterEntry>::new();
389 for character in first.into_iter().chain(index.characters_of(faction.key()).filter(|character| character.death_type().is_empty())) {
390 if !characters.contains(&character) {
391 characters.push(character);
392 }
393 }
394 characters
395}