nil_core/military/army/
mod.rs1pub 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}