Skip to main content

nil_core/military/unit/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4pub mod archer;
5pub mod axeman;
6pub mod heavy_cavalry;
7pub mod light_cavalry;
8pub mod pikeman;
9pub mod prelude;
10pub mod ram;
11pub mod stats;
12pub mod swordsman;
13
14use crate::error::Result;
15use crate::infrastructure::prelude::BuildingId;
16use crate::infrastructure::requirements::InfrastructureRequirements;
17use crate::ranking::score::Score;
18use crate::resources::prelude::*;
19use derive_more::{Deref, Display, From, Into};
20use serde::{Deserialize, Deserializer, Serialize, Serializer};
21use stats::prelude::*;
22use std::fmt;
23use std::num::NonZeroU32;
24use std::ops::Mul;
25use strum::EnumIter;
26use subenum::subenum;
27
28pub trait Unit: Send + Sync {
29  fn id(&self) -> UnitId;
30  fn kind(&self) -> UnitKind;
31
32  /// Building where the unit is recruited.
33  fn building(&self) -> BuildingId;
34
35  fn score(&self) -> Score;
36
37  fn stats(&self) -> &UnitStats;
38  fn attack(&self) -> Power;
39  fn infantry_defense(&self) -> Power;
40  fn cavalry_defense(&self) -> Power;
41  fn ranged_defense(&self) -> Power;
42  fn ranged_debuff(&self) -> RangedDebuff;
43  fn speed(&self) -> Speed;
44  fn haul(&self) -> Haul;
45
46  fn chunk(&self) -> &UnitChunk;
47
48  /// Building levels required to recruit the unit.
49  fn infrastructure_requirements(&self) -> &InfrastructureRequirements;
50
51  fn is_cavalry(&self) -> bool {
52    matches!(self.kind(), UnitKind::Cavalry)
53  }
54
55  fn is_infantry(&self) -> bool {
56    matches!(self.kind(), UnitKind::Infantry)
57  }
58
59  fn is_ranged(&self) -> bool {
60    matches!(self.kind(), UnitKind::Ranged)
61  }
62
63  fn is_offensive(&self) -> bool {
64    match self.id() {
65      UnitId::Archer | UnitId::Ram => true,
66      _ => {
67        let infantry = self.infantry_defense();
68        let cavalry = self.cavalry_defense();
69        self.attack() > infantry.max(cavalry)
70      }
71    }
72  }
73
74  fn is_defensive(&self) -> bool {
75    match self.id() {
76      UnitId::Archer => true,
77      UnitId::Ram => false,
78      _ => !self.is_offensive(),
79    }
80  }
81}
82
83#[subenum(AcademyUnitId, StableUnitId, WorkshopUnitId)]
84#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Hash, Deserialize, Serialize, EnumIter)]
85#[serde(rename_all = "kebab-case")]
86#[strum(serialize_all = "kebab-case")]
87#[remain::sorted]
88pub enum UnitId {
89  #[subenum(AcademyUnitId)]
90  Archer,
91  #[subenum(AcademyUnitId)]
92  Axeman,
93  #[subenum(StableUnitId)]
94  HeavyCavalry,
95  #[subenum(StableUnitId)]
96  LightCavalry,
97  #[subenum(AcademyUnitId)]
98  Pikeman,
99  #[subenum(WorkshopUnitId)]
100  Ram,
101  #[subenum(AcademyUnitId)]
102  Swordsman,
103}
104
105impl From<UnitId> for BuildingId {
106  fn from(id: UnitId) -> Self {
107    UnitBox::from(id).building()
108  }
109}
110
111#[derive(Deref)]
112pub struct UnitBox(Box<dyn Unit>);
113
114impl UnitBox {
115  #[inline]
116  pub fn new(unit: Box<dyn Unit>) -> Self {
117    Self(unit)
118  }
119
120  #[inline]
121  pub fn as_dyn(&self) -> &dyn Unit {
122    &*self.0
123  }
124}
125
126impl PartialEq for UnitBox {
127  fn eq(&self, other: &Self) -> bool {
128    self.id() == other.id()
129  }
130}
131
132impl Eq for UnitBox {}
133
134impl Clone for UnitBox {
135  fn clone(&self) -> Self {
136    Self::from(self.0.id())
137  }
138}
139
140impl fmt::Debug for UnitBox {
141  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142    f.debug_tuple("UnitBox")
143      .field(&self.0.id())
144      .finish()
145  }
146}
147
148impl From<UnitId> for UnitBox {
149  fn from(id: UnitId) -> Self {
150    use prelude::*;
151    match id {
152      UnitId::Archer => Archer::new_boxed(),
153      UnitId::Axeman => Axeman::new_boxed(),
154      UnitId::HeavyCavalry => HeavyCavalry::new_boxed(),
155      UnitId::LightCavalry => LightCavalry::new_boxed(),
156      UnitId::Pikeman => Pikeman::new_boxed(),
157      UnitId::Ram => Ram::new_boxed(),
158      UnitId::Swordsman => Swordsman::new_boxed(),
159    }
160  }
161}
162
163impl From<AcademyUnitId> for UnitBox {
164  fn from(id: AcademyUnitId) -> Self {
165    Self::from(UnitId::from(id))
166  }
167}
168
169impl From<StableUnitId> for UnitBox {
170  fn from(id: StableUnitId) -> Self {
171    Self::from(UnitId::from(id))
172  }
173}
174
175impl From<WorkshopUnitId> for UnitBox {
176  fn from(id: WorkshopUnitId) -> Self {
177    Self::from(UnitId::from(id))
178  }
179}
180
181impl Serialize for UnitBox {
182  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
183  where
184    S: Serializer,
185  {
186    self.id().serialize(serializer)
187  }
188}
189
190impl<'de> Deserialize<'de> for UnitBox {
191  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
192  where
193    D: Deserializer<'de>,
194  {
195    Ok(Self::from(UnitId::deserialize(deserializer)?))
196  }
197}
198
199#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
200#[serde(rename_all = "kebab-case")]
201pub enum UnitKind {
202  Infantry,
203  Cavalry,
204  Ranged,
205}
206
207#[derive(Clone, Debug, Deserialize, Serialize)]
208#[serde(rename_all = "camelCase")]
209pub struct UnitChunk {
210  size: UnitChunkSize,
211  cost: Cost,
212  wood_ratio: ResourceRatio,
213  stone_ratio: ResourceRatio,
214  iron_ratio: ResourceRatio,
215  maintenance_ratio: MaintenanceRatio,
216  workforce: Workforce,
217}
218
219impl UnitChunk {
220  #[inline]
221  pub fn size(&self) -> UnitChunkSize {
222    self.size
223  }
224
225  pub fn resources(&self) -> Resources {
226    Resources {
227      food: Food::MIN,
228      iron: Iron::from((self.cost * self.iron_ratio).round()),
229      stone: Stone::from((self.cost * self.stone_ratio).round()),
230      wood: Wood::from((self.cost * self.wood_ratio).round()),
231    }
232  }
233
234  #[inline]
235  pub fn maintenance(&self) -> Maintenance {
236    Maintenance::from((self.cost * self.maintenance_ratio).round())
237  }
238
239  #[inline]
240  pub fn workforce(&self) -> Workforce {
241    self.workforce
242  }
243}
244
245#[derive(Clone, Copy, Debug, Deref, From, Into, Deserialize, Serialize)]
246#[into(u8, u32, f64)]
247pub struct UnitChunkSize(u8);
248
249impl UnitChunkSize {
250  #[inline]
251  pub const fn new(size: u8) -> UnitChunkSize {
252    UnitChunkSize(size)
253  }
254}
255
256impl Mul<u32> for UnitChunkSize {
257  type Output = u32;
258
259  fn mul(self, rhs: u32) -> Self::Output {
260    u32::from(self.0).saturating_mul(rhs)
261  }
262}
263
264impl Mul<NonZeroU32> for UnitChunkSize {
265  type Output = u32;
266
267  fn mul(self, rhs: NonZeroU32) -> Self::Output {
268    self * rhs.get()
269  }
270}