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")]
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  /// Checks whether the maneuver's origin or destination matches the coord.
182  #[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}