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