nil_core/military/maneuver/
mod.rs1mod distance;
5
6use crate::continent::Coord;
7use crate::error::{Error, Result};
8use crate::military::army::ArmyId;
9use crate::military::army::personnel::ArmyPersonnel;
10use crate::military::unit::stats::speed::Speed;
11use crate::resources::Resources;
12use crate::ruler::Ruler;
13use bon::Builder;
14use serde::{Deserialize, Serialize};
15use strum::EnumIs;
16use uuid::Uuid;
17
18pub use distance::ManeuverDistance;
19
20#[must_use]
21#[derive(Clone, Debug, Deserialize, Serialize)]
22#[serde(rename_all = "camelCase")]
23#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
24pub struct Maneuver {
25 id: ManeuverId,
26 origin: Coord,
27 destination: Coord,
28 army: ArmyId,
29 kind: ManeuverKind,
30 direction: ManeuverDirection,
31 state: ManeuverState,
32 speed: Speed,
33 hauled_resources: Option<ManeuverHaul>,
34}
35
36#[bon::bon]
37impl Maneuver {
38 #[builder]
39 pub(crate) fn new(
40 army: ArmyId,
41 kind: ManeuverKind,
42 origin: Coord,
43 destination: Coord,
44 speed: Speed,
45 ) -> Result<(ManeuverId, Self)> {
46 let distance = origin.distance(destination);
47 if origin == destination || distance == 0u8 {
48 return Err(Error::OriginIsDestination(origin));
49 }
50
51 let id = ManeuverId::new();
52 let state = ManeuverState::with_distance(distance.into());
53 let maneuver = Self {
54 id,
55 origin,
56 destination,
57 army,
58 kind,
59 direction: ManeuverDirection::Going,
60 state,
61 speed,
62 hauled_resources: None,
63 };
64
65 Ok((id, maneuver))
66 }
67
68 pub(super) fn advance(&mut self) -> Result<()> {
69 let is_done = match &mut self.state {
70 ManeuverState::Done => {
71 return Err(Error::ManeuverIsDone(self.id));
72 }
73 ManeuverState::Pending { distance } => {
74 *distance -= self.speed;
75 debug_assert!(distance.is_finite());
76 *distance <= 0.0
77 }
78 };
79
80 if is_done {
81 self.state = ManeuverState::Done;
82 }
83
84 Ok(())
85 }
86
87 pub(crate) fn reverse(&mut self) -> Result<()> {
88 if self.is_pending() {
89 return Err(Error::ManeuverIsPending(self.id));
90 } else if self.is_returning() {
91 return Err(Error::ManeuverIsReturning(self.id));
92 }
93
94 let distance = self.origin.distance(self.destination);
95 self.state = ManeuverState::with_distance(distance.into());
96 self.direction = ManeuverDirection::Returning;
97
98 Ok(())
99 }
100
101 #[inline]
102 pub fn id(&self) -> ManeuverId {
103 self.id
104 }
105
106 #[inline]
107 pub fn origin(&self) -> Coord {
108 self.origin
109 }
110
111 #[inline]
112 pub fn destination(&self) -> Coord {
113 self.destination
114 }
115
116 #[inline]
117 pub fn army(&self) -> ArmyId {
118 self.army
119 }
120
121 #[inline]
122 pub fn kind(&self) -> ManeuverKind {
123 self.kind
124 }
125
126 #[inline]
127 pub fn direction(&self) -> ManeuverDirection {
128 self.direction
129 }
130
131 #[inline]
132 pub fn state(&self) -> &ManeuverState {
133 &self.state
134 }
135
136 #[inline]
137 pub fn speed(&self) -> Speed {
138 self.speed
139 }
140
141 #[inline]
142 pub fn hauled_resources(&self) -> Option<&ManeuverHaul> {
143 self.hauled_resources.as_ref()
144 }
145
146 #[inline]
147 pub(crate) fn hauled_resources_mut(&mut self) -> &mut Option<ManeuverHaul> {
148 &mut self.hauled_resources
149 }
150
151 #[inline]
152 pub fn is_attack(&self) -> bool {
153 self.kind.is_attack()
154 }
155
156 #[inline]
157 pub fn is_support(&self) -> bool {
158 self.kind.is_support()
159 }
160
161 #[inline]
162 pub fn is_done(&self) -> bool {
163 self.state.is_done()
164 }
165
166 #[inline]
167 pub fn is_pending(&self) -> bool {
168 self.state.is_pending()
169 }
170
171 #[inline]
172 pub fn is_going(&self) -> bool {
173 self.direction.is_going()
174 }
175
176 #[inline]
177 pub fn is_returning(&self) -> bool {
178 self.direction.is_returning()
179 }
180
181 #[inline]
183 pub fn matches_coord(&self, coord: Coord) -> bool {
184 coord == self.origin || coord == self.destination
185 }
186
187 pub fn pending_distance(&self) -> Option<ManeuverDistance> {
188 if let ManeuverState::Pending { distance } = self.state {
189 Some(distance)
190 } else {
191 None
192 }
193 }
194}
195
196#[must_use]
197#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
198#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
199pub struct ManeuverId(Uuid);
200
201impl ManeuverId {
202 pub fn new() -> Self {
203 Self(Uuid::now_v7())
204 }
205}
206
207impl Default for ManeuverId {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213#[derive(Clone, Copy, Debug, Deserialize, Serialize, EnumIs)]
214#[serde(rename_all = "kebab-case")]
215#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
216pub enum ManeuverKind {
217 Attack,
218 Support,
219}
220
221#[derive(Clone, Copy, Debug, Deserialize, Serialize, EnumIs)]
222#[serde(rename_all = "kebab-case")]
223#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
224pub enum ManeuverDirection {
225 Going,
226 Returning,
227}
228
229#[derive(Clone, Debug, Deserialize, Serialize, EnumIs)]
230#[serde(tag = "kind", rename_all = "kebab-case")]
231#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
232pub enum ManeuverState {
233 Done,
234 Pending { distance: ManeuverDistance },
235}
236
237impl ManeuverState {
238 fn with_distance(distance: ManeuverDistance) -> Self {
239 Self::Pending { distance }
240 }
241}
242
243#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
244#[serde(rename_all = "camelCase")]
245#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
246pub struct ManeuverHaul {
247 ruler: Ruler,
248 resources: Resources,
249}
250
251impl ManeuverHaul {
252 #[inline]
253 pub fn ruler(&self) -> &Ruler {
254 &self.ruler
255 }
256
257 #[inline]
258 pub fn resources(&self) -> &Resources {
259 &self.resources
260 }
261}
262
263impl From<ManeuverHaul> for Resources {
264 fn from(haul: ManeuverHaul) -> Self {
265 haul.resources
266 }
267}
268
269#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
270#[serde(rename_all = "camelCase")]
271#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
272pub struct ManeuverRequest {
273 pub kind: ManeuverKind,
274 pub origin: Coord,
275 pub destination: Coord,
276 pub personnel: ArmyPersonnel,
277}