Skip to main content

nil_core/military/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4#[cfg(test)]
5mod tests;
6
7pub mod army;
8pub mod maneuver;
9pub mod squad;
10pub mod unit;
11
12use crate::continent::{ContinentIndex, ContinentKey, ContinentSize, Coord};
13use crate::error::{Error, Result};
14use crate::military::unit::stats::power::{AttackPower, DefensePower, Power};
15use crate::ranking::score::Score;
16use crate::resources::maintenance::Maintenance;
17use crate::ruler::Ruler;
18use army::personnel::ArmyPersonnel;
19use army::{Army, ArmyId, collapse_armies};
20use itertools::Itertools;
21use maneuver::{Maneuver, ManeuverId};
22use serde::{Deserialize, Serialize};
23use squad::Squad;
24use std::collections::HashMap;
25
26#[derive(Clone, Debug, Deserialize, Serialize)]
27#[serde(rename_all = "camelCase")]
28#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
29pub struct Military {
30  continent: HashMap<ContinentIndex, Vec<Army>>,
31  continent_size: ContinentSize,
32  maneuvers: HashMap<ManeuverId, Maneuver>,
33}
34
35impl Military {
36  pub(crate) fn new(size: ContinentSize) -> Self {
37    Self {
38      continent: HashMap::new(),
39      continent_size: size,
40      maneuvers: HashMap::new(),
41    }
42  }
43
44  pub(crate) fn spawn<K, R>(&mut self, key: K, owner: R, personnel: ArmyPersonnel)
45  where
46    K: ContinentKey,
47    R: Into<Ruler>,
48  {
49    let ruler: Ruler = owner.into();
50    let army = Army::builder()
51      .owner(ruler)
52      .personnel(personnel)
53      .build();
54
55    let index = key.into_index(self.continent_size);
56    self
57      .continent
58      .entry(index)
59      .or_default()
60      .push(army);
61
62    self.collapse_armies_in(index);
63  }
64
65  pub fn collapse_armies(&mut self) {
66    self
67      .continent
68      .values_mut()
69      .for_each(collapse_armies);
70
71    self
72      .continent
73      .retain(|_, armies| !armies.is_empty());
74  }
75
76  pub fn collapse_armies_in<K>(&mut self, key: K)
77  where
78    K: ContinentKey,
79  {
80    let index = key.into_index(self.continent_size);
81    if let Some(armies) = self.continent.get_mut(&index) {
82      collapse_armies(armies);
83    }
84  }
85
86  /// Creates a new instance containing only entries related to a given set of coords.
87  pub fn intersection<K, I>(&self, keys: I) -> Result<Self>
88  where
89    K: ContinentKey,
90    I: IntoIterator<Item = K>,
91  {
92    let mut military = Self::new(self.continent_size);
93    for key in keys {
94      let coord = key.into_coord(self.continent_size)?;
95      let index = coord.into_index(self.continent_size);
96      if let Some(armies) = self.continent.get(&index).cloned() {
97        military.continent.insert(index, armies);
98      }
99
100      let maneuvers = self
101        .maneuvers_at(coord)
102        .map(|maneuver| (maneuver.id(), maneuver.clone()));
103
104      military.maneuvers.extend(maneuvers);
105    }
106
107    Ok(military)
108  }
109
110  pub(crate) fn remove_army(&mut self, id: ArmyId) -> Result<Army> {
111    let (curr_vec, pos) = self
112      .continent
113      .values_mut()
114      .find_map(|armies| {
115        armies
116          .iter()
117          .position(|army| army.id() == id)
118          .map(|pos| (armies, pos))
119      })
120      .ok_or(Error::ArmyNotFound(id))?;
121
122    Ok(curr_vec.swap_remove(pos))
123  }
124
125  pub(crate) fn remove_armies<I>(&mut self, armies: I) -> Result<Vec<Army>>
126  where
127    I: IntoIterator<Item = ArmyId>,
128  {
129    armies
130      .into_iter()
131      .map(|id| self.remove_army(id))
132      .try_collect()
133  }
134
135  pub(crate) fn relocate_army<K>(&mut self, id: ArmyId, new_key: K) -> Result<()>
136  where
137    K: ContinentKey,
138  {
139    let army = self.remove_army(id)?;
140    let index = new_key.into_index(self.continent_size);
141    self
142      .continent
143      .entry(index)
144      .or_default()
145      .push(army);
146
147    self.collapse_armies_in(index);
148
149    Ok(())
150  }
151
152  pub fn army(&self, id: ArmyId) -> Result<&Army> {
153    self
154      .armies()
155      .find(|army| army.id() == id)
156      .ok_or(Error::ArmyNotFound(id))
157  }
158
159  pub(crate) fn army_mut(&mut self, id: ArmyId) -> Result<&mut Army> {
160    self
161      .armies_mut()
162      .find(|army| army.id() == id)
163      .ok_or(Error::ArmyNotFound(id))
164  }
165
166  pub fn armies(&self) -> impl Iterator<Item = &Army> {
167    self.continent.values().flatten()
168  }
169
170  pub(crate) fn armies_mut(&mut self) -> impl Iterator<Item = &mut Army> {
171    self.continent.values_mut().flatten()
172  }
173
174  #[inline]
175  pub fn count_armies(&self) -> usize {
176    self.armies().count()
177  }
178
179  pub fn armies_at<K>(&self, key: K) -> &[Army]
180  where
181    K: ContinentKey,
182  {
183    let index = key.into_index(self.continent_size);
184    self
185      .continent
186      .get(&index)
187      .map(Vec::as_slice)
188      .unwrap_or_default()
189  }
190
191  pub(crate) fn armies_mut_at<K>(&mut self, key: K) -> &mut [Army]
192  where
193    K: ContinentKey,
194  {
195    let index = key.into_index(self.continent_size);
196    self
197      .continent
198      .get_mut(&index)
199      .map(Vec::as_mut_slice)
200      .unwrap_or_default()
201  }
202
203  pub fn count_armies_at<K>(&self, key: K) -> usize
204  where
205    K: ContinentKey,
206  {
207    self.armies_at(key).iter().count()
208  }
209
210  pub fn idle_armies_at<K>(&self, key: K) -> impl Iterator<Item = &Army>
211  where
212    K: ContinentKey,
213  {
214    self
215      .armies_at(key)
216      .iter()
217      .filter(|army| army.is_idle())
218  }
219
220  pub(crate) fn idle_armies_mut_at<K>(&mut self, key: K) -> impl Iterator<Item = &mut Army>
221  where
222    K: ContinentKey,
223  {
224    self
225      .armies_mut_at(key)
226      .iter_mut()
227      .filter(|army| army.is_idle())
228  }
229
230  pub fn armies_of<R>(&self, owner: R) -> impl Iterator<Item = &Army>
231  where
232    R: Into<Ruler>,
233  {
234    let owner: Ruler = owner.into();
235    self
236      .continent
237      .values()
238      .flatten()
239      .filter(move |army| army.is_owned_by(&owner))
240  }
241
242  #[inline]
243  pub fn personnel(&self, id: ArmyId) -> Result<&ArmyPersonnel> {
244    self.army(id).map(Army::personnel)
245  }
246
247  pub fn personnel_of<R>(&self, owner: R) -> impl Iterator<Item = &ArmyPersonnel>
248  where
249    R: Into<Ruler>,
250  {
251    self.armies_of(owner).map(Army::personnel)
252  }
253
254  pub fn fold_personnel_of<R>(&self, owner: R) -> ArmyPersonnel
255  where
256    R: Into<Ruler>,
257  {
258    self.personnel_of(owner).sum()
259  }
260
261  pub fn idle_personnel_at<K>(&self, key: K) -> impl Iterator<Item = &ArmyPersonnel>
262  where
263    K: ContinentKey,
264  {
265    self.idle_armies_at(key).map(Army::personnel)
266  }
267
268  pub fn fold_idle_personnel_at<K>(&self, key: K) -> ArmyPersonnel
269  where
270    K: ContinentKey,
271  {
272    self.idle_personnel_at(key).sum()
273  }
274
275  #[inline]
276  pub fn squads(&self, id: ArmyId) -> Result<Vec<Squad>> {
277    self
278      .personnel(id)
279      .cloned()
280      .map(ArmyPersonnel::to_vec)
281  }
282
283  pub fn idle_squads_at<K>(&self, key: K) -> Vec<Squad>
284  where
285    K: ContinentKey,
286  {
287    self.fold_idle_personnel_at(key).to_vec()
288  }
289
290  #[inline]
291  pub fn maneuver(&self, id: ManeuverId) -> Result<&Maneuver> {
292    self
293      .maneuvers
294      .get(&id)
295      .ok_or(Error::ManeuverNotFound(id))
296  }
297
298  pub fn maneuvers(&self) -> impl Iterator<Item = &Maneuver> {
299    self.maneuvers.values()
300  }
301
302  /// Find all maneuvers whose origin or destination matches the specified coord.
303  pub fn maneuvers_at(&self, coord: Coord) -> impl Iterator<Item = &Maneuver> {
304    self
305      .maneuvers()
306      .filter(move |maneuver| maneuver.matches_coord(coord))
307  }
308
309  pub(crate) fn insert_maneuver(&mut self, maneuver: Maneuver) {
310    self
311      .maneuvers
312      .insert(maneuver.id(), maneuver);
313  }
314
315  pub(crate) fn advance_maneuvers(&mut self) -> Result<Vec<Maneuver>> {
316    let mut done = Vec::new();
317    for (id, maneuver) in &mut self.maneuvers {
318      maneuver.advance()?;
319
320      if maneuver.is_done() {
321        done.push(*id);
322      }
323    }
324
325    let done = done
326      .into_iter()
327      .filter_map(|id| self.maneuvers.remove(&id))
328      .collect_vec();
329
330    self.maneuvers.shrink_to_fit();
331
332    Ok(done)
333  }
334
335  /// Retains only the maneuvers specified by the predicate.
336  pub(crate) fn retain_maneuvers<F>(&mut self, f: F)
337  where
338    F: Fn(&Maneuver) -> bool,
339  {
340    self
341      .maneuvers
342      .retain(|_, maneuver| f(maneuver));
343  }
344
345  pub fn score_of<R>(&self, owner: R) -> Score
346  where
347    R: Into<Ruler>,
348  {
349    self.armies_of(owner).sum()
350  }
351
352  pub fn maintenance_of<R>(&self, owner: R) -> Maintenance
353  where
354    R: Into<Ruler>,
355  {
356    self.armies_of(owner).sum()
357  }
358
359  pub fn power_of<R>(&self, owner: R) -> Power
360  where
361    R: Into<Ruler>,
362  {
363    self.armies_of(owner).sum()
364  }
365
366  pub fn attack_of<R>(&self, owner: R) -> AttackPower
367  where
368    R: Into<Ruler>,
369  {
370    self.armies_of(owner).sum()
371  }
372
373  pub fn defense_of<R>(&self, owner: R) -> DefensePower
374  where
375    R: Into<Ruler>,
376  {
377    self.armies_of(owner).sum()
378  }
379}