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