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(Copy, Debug, Display, Hash, Deserialize, Serialize, EnumIter)]
98#[derive_const(Clone, 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::Display for UnitBox {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 self.0.id().fmt(f)
167 }
168}
169
170impl fmt::Debug for UnitBox {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 f.debug_tuple("UnitBox")
173 .field(&self.0.id())
174 .finish()
175 }
176}
177
178impl From<UnitId> for UnitBox {
179 fn from(id: UnitId) -> Self {
180 use prelude::*;
181 match id {
182 UnitId::Archer => Archer::new_boxed(),
183 UnitId::Axeman => Axeman::new_boxed(),
184 UnitId::HeavyCavalry => HeavyCavalry::new_boxed(),
185 UnitId::LightCavalry => LightCavalry::new_boxed(),
186 UnitId::Pikeman => Pikeman::new_boxed(),
187 UnitId::Ram => Ram::new_boxed(),
188 UnitId::Swordsman => Swordsman::new_boxed(),
189 }
190 }
191}
192
193impl From<AcademyUnitId> for UnitBox {
194 fn from(id: AcademyUnitId) -> Self {
195 Self::from(UnitId::from(id))
196 }
197}
198
199impl From<StableUnitId> for UnitBox {
200 fn from(id: StableUnitId) -> Self {
201 Self::from(UnitId::from(id))
202 }
203}
204
205impl From<WorkshopUnitId> for UnitBox {
206 fn from(id: WorkshopUnitId) -> Self {
207 Self::from(UnitId::from(id))
208 }
209}
210
211impl Serialize for UnitBox {
212 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
213 where
214 S: Serializer,
215 {
216 self.0.id().serialize(serializer)
217 }
218}
219
220impl<'de> Deserialize<'de> for UnitBox {
221 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222 where
223 D: Deserializer<'de>,
224 {
225 Ok(Self::from(UnitId::deserialize(deserializer)?))
226 }
227}
228
229#[derive(Clone, Copy, Debug, strum::Display, PartialEq, Eq, Deserialize, Serialize)]
230#[serde(rename_all = "kebab-case")]
231#[strum(serialize_all = "kebab-case")]
232#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
233pub enum UnitKind {
234 Infantry,
235 Cavalry,
236 Ranged,
237}
238
239#[derive(Clone, Debug, Deserialize, Serialize)]
240#[serde(rename_all = "camelCase")]
241#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
242pub struct UnitChunk {
243 size: UnitChunkSize,
244 cost: Cost,
245 food_ratio: ResourceRatio,
246 iron_ratio: ResourceRatio,
247 stone_ratio: ResourceRatio,
248 wood_ratio: ResourceRatio,
249 maintenance_ratio: MaintenanceRatio,
250 workforce: Workforce,
251}
252
253impl UnitChunk {
254 #[inline]
255 pub fn size(&self) -> UnitChunkSize {
256 self.size
257 }
258
259 pub fn resources(&self) -> Resources {
260 Resources {
261 food: Food::from((self.cost * self.food_ratio).round()),
262 iron: Iron::from((self.cost * self.iron_ratio).round()),
263 stone: Stone::from((self.cost * self.stone_ratio).round()),
264 wood: Wood::from((self.cost * self.wood_ratio).round()),
265 }
266 }
267
268 #[inline]
269 pub fn maintenance(&self) -> Maintenance {
270 Maintenance::from((self.cost * self.maintenance_ratio).round())
271 }
272
273 #[inline]
274 pub fn workforce(&self) -> Workforce {
275 self.workforce
276 }
277}
278
279#[derive(Clone, Copy, Debug, Display, From, Into, Deserialize, Serialize, ConstDeref)]
280#[derive_const(Default, PartialEq, Eq, PartialOrd, Ord)]
281#[into(u8, u32)]
282#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
283pub struct UnitChunkSize(u8);
284
285impl UnitChunkSize {
286 #[inline]
287 pub const fn new(size: u8) -> UnitChunkSize {
288 UnitChunkSize(size)
289 }
290}
291
292impl const From<UnitChunkSize> for f64 {
293 fn from(value: UnitChunkSize) -> Self {
294 f64::from(value.0)
295 }
296}
297
298impl const Mul<u32> for UnitChunkSize {
299 type Output = u32;
300
301 fn mul(self, rhs: u32) -> Self::Output {
302 u32::from(self.0).saturating_mul(rhs)
303 }
304}
305
306impl const Mul<NonZeroU32> for UnitChunkSize {
307 type Output = u32;
308
309 fn mul(self, rhs: NonZeroU32) -> Self::Output {
310 self * rhs.get()
311 }
312}