Skip to main content

nil_core/military/army/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4pub mod personnel;
5
6use crate::continent::index::ContinentKey;
7use crate::military::Military;
8use crate::military::army::personnel::{ArmyPersonnel, ArmyPersonnelIter};
9use crate::military::maneuver::ManeuverId;
10use crate::military::squad::Squad;
11use crate::military::unit::UnitId;
12use crate::military::unit::stats::haul::Haul;
13use crate::military::unit::stats::power::{AttackPower, DefensePower, Power};
14use crate::military::unit::stats::speed::Speed;
15use crate::ranking::score::Score;
16use crate::resources::maintenance::Maintenance;
17use crate::ruler::Ruler;
18use crate::world::config::WorldConfig;
19use bon::Builder;
20use derive_more::Display;
21use serde::{Deserialize, Serialize};
22use std::mem;
23use std::ops::{Mul, MulAssign};
24use strum::{EnumIs, IntoEnumIterator};
25use uuid::Uuid;
26
27#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
28#[builder(builder_type(vis = "pub(crate)"))]
29#[serde(rename_all = "camelCase")]
30#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
31pub struct Army {
32  #[builder(skip)]
33  id: ArmyId,
34
35  #[builder(default)]
36  personnel: ArmyPersonnel,
37
38  #[builder(into)]
39  owner: Ruler,
40
41  #[builder(default, into)]
42  state: ArmyState,
43}
44
45impl Army {
46  #[inline]
47  pub fn id(&self) -> ArmyId {
48    self.id
49  }
50
51  #[inline]
52  pub fn personnel(&self) -> &ArmyPersonnel {
53    &self.personnel
54  }
55
56  pub(crate) fn personnel_mut(&mut self) -> &mut ArmyPersonnel {
57    &mut self.personnel
58  }
59
60  #[inline]
61  pub fn iter(&self) -> ArmyPersonnelIter<'_> {
62    self.personnel.iter()
63  }
64
65  #[inline]
66  pub fn squad(&self, id: UnitId) -> &Squad {
67    self.personnel.squad(id)
68  }
69
70  fn squad_mut(&mut self, id: UnitId) -> &mut Squad {
71    self.personnel.squad_mut(id)
72  }
73
74  #[inline]
75  pub fn owner(&self) -> &Ruler {
76    &self.owner
77  }
78
79  #[inline]
80  pub fn state(&self) -> &ArmyState {
81    &self.state
82  }
83
84  pub(crate) fn state_mut(&mut self) -> &mut ArmyState {
85    &mut self.state
86  }
87
88  #[inline]
89  pub fn slowest_squad(&self, config: &WorldConfig) -> Option<&Squad> {
90    self.personnel.slowest_squad(config)
91  }
92
93  #[inline]
94  pub fn speed(&self, config: &WorldConfig) -> Speed {
95    self.personnel.speed(config)
96  }
97
98  #[inline]
99  pub fn haul(&self) -> Haul {
100    self.personnel.haul()
101  }
102
103  #[inline]
104  pub fn score(&self) -> Score {
105    self.personnel.score()
106  }
107
108  #[inline]
109  pub fn maintenance(&self) -> Maintenance {
110    self.personnel.maintenance()
111  }
112
113  #[inline]
114  pub fn power(&self) -> Power {
115    self.personnel.power()
116  }
117
118  #[inline]
119  pub fn attack(&self) -> AttackPower {
120    self.personnel.attack()
121  }
122
123  #[inline]
124  pub fn defense(&self) -> DefensePower {
125    self.personnel.defense()
126  }
127
128  #[inline]
129  pub fn is_owned_by(&self, ruler: &Ruler) -> bool {
130    self.owner.eq(ruler)
131  }
132
133  #[inline]
134  pub fn is_idle_and_owned_by(&self, ruler: &Ruler) -> bool {
135    self.is_idle() && self.is_owned_by(ruler)
136  }
137
138  #[inline]
139  pub fn is_empty(&self) -> bool {
140    self.personnel.is_empty()
141  }
142
143  #[inline]
144  pub fn is_idle(&self) -> bool {
145    self.state.is_idle()
146  }
147
148  #[inline]
149  pub fn is_maneuvering(&self) -> bool {
150    self.state.is_maneuvering()
151  }
152
153  #[inline]
154  pub fn has_enough_personnel(&self, required: &ArmyPersonnel) -> bool {
155    self.personnel.has_enough_personnel(required)
156  }
157
158  pub(crate) fn spawn<K>(self, military: &mut Military, key: K)
159  where
160    K: ContinentKey,
161  {
162    military.spawn(key, self.owner, self.personnel);
163  }
164}
165
166macro_rules! impl_army {
167  ($($unit:ident),+) => {
168    paste::paste! {
169      impl Army {
170        $(
171          #[inline]
172          pub fn [<$unit:snake>](&self) -> &Squad {
173            &self.personnel.[<$unit:snake>]()
174          }
175        )+
176      }
177    }
178  };
179}
180
181impl_army!(
182  Archer,
183  Axeman,
184  HeavyCavalry,
185  LightCavalry,
186  Pikeman,
187  Ram,
188  Swordsman
189);
190
191impl<'a> IntoIterator for &'a Army {
192  type Item = &'a Squad;
193  type IntoIter = ArmyPersonnelIter<'a>;
194
195  fn into_iter(self) -> Self::IntoIter {
196    self.iter()
197  }
198}
199
200impl Mul<f64> for Army {
201  type Output = Army;
202
203  fn mul(mut self, rhs: f64) -> Self::Output {
204    self *= rhs;
205    self
206  }
207}
208
209impl MulAssign<f64> for Army {
210  fn mul_assign(&mut self, rhs: f64) {
211    for id in UnitId::iter() {
212      *self.squad_mut(id) *= rhs;
213    }
214  }
215}
216
217#[must_use]
218#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Deserialize, Serialize)]
219#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
220pub struct ArmyId(Uuid);
221
222impl ArmyId {
223  #[inline]
224  pub fn new() -> Self {
225    Self(Uuid::new_v4())
226  }
227}
228
229impl Default for ArmyId {
230  fn default() -> Self {
231    Self::new()
232  }
233}
234
235#[derive(Clone, Debug, Default, Deserialize, Serialize, EnumIs)]
236#[serde(tag = "kind", rename_all = "kebab-case")]
237#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
238pub enum ArmyState {
239  #[default]
240  Idle,
241  Maneuvering {
242    maneuver: ManeuverId,
243  },
244}
245
246impl ArmyState {
247  #[inline]
248  pub fn with_maneuver(maneuver: ManeuverId) -> Self {
249    Self::Maneuvering { maneuver }
250  }
251}
252
253pub fn collapse_armies(armies: &mut Vec<Army>) {
254  for army in mem::take(armies)
255    .into_iter()
256    .filter(|army| !army.is_empty())
257  {
258    if army.is_idle()
259      && let Some(previous) = find_idle_owned_by(armies, army.owner())
260    {
261      *previous.personnel_mut() += ArmyPersonnel::from(army);
262    } else {
263      armies.push(army);
264    }
265  }
266}
267
268fn find_idle_owned_by<'a>(armies: &'a mut [Army], ruler: &Ruler) -> Option<&'a mut Army> {
269  armies
270    .iter_mut()
271    .find(|army| army.is_idle_and_owned_by(ruler))
272}