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