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