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