Skip to main content

nil_core/military/maneuver/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4mod 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  /// Checks whether the maneuver's origin or destination matches the coord.
181  #[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}