Skip to main content

nil_core/world/military/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4#[cfg(test)]
5mod tests;
6
7use crate::error::{Error, Result};
8use crate::military::army::{Army, ArmyState};
9use crate::military::maneuver::{Maneuver, ManeuverId, ManeuverRequest};
10use crate::ruler::Ruler;
11use crate::world::World;
12
13impl World {
14  pub fn cancel_maneuver(&mut self, id: ManeuverId) -> Result<()> {
15    self.military.cancel_maneuver(id)?;
16    self.emit_military_to_maneuver_players(id)?;
17    Ok(())
18  }
19
20  #[inline]
21  pub fn collapse_armies(&mut self) {
22    self.military.collapse_armies();
23  }
24
25  pub fn is_maneuver_army_owned_by<R>(&self, id: ManeuverId, ruler: R) -> Result<bool>
26  where
27    R: Into<Ruler>,
28  {
29    let ruler: Ruler = ruler.into();
30    let army_id = self.military.maneuver(id)?.army();
31    let army_owner = self.military.army(army_id)?.owner();
32    Ok(ruler == *army_owner)
33  }
34
35  #[inline]
36  pub fn request_maneuver(&mut self, request: ManeuverRequest) -> Result<ManeuverId> {
37    self.request_maneuver_with_emit(request, true)
38  }
39
40  pub(crate) fn request_maneuver_with_emit(
41    &mut self,
42    request: ManeuverRequest,
43    emit: bool,
44  ) -> Result<ManeuverId> {
45    self
46      .military
47      .collapse_armies_in(request.origin);
48
49    let origin_ruler = self.city(request.origin)?.owner().clone();
50    let Some(army) = self
51      .military
52      .armies_mut_at(request.origin)
53      .iter_mut()
54      .find(|army| army.is_idle_and_owned_by(&origin_ruler))
55      .filter(|army| army.has_enough_personnel(&request.personnel))
56    else {
57      return Err(Error::InsufficientUnits);
58    };
59
60    let Some(remaining) = army
61      .personnel()
62      .checked_sub(&request.personnel)
63    else {
64      return Err(Error::InsufficientUnits);
65    };
66
67    let army_id = army.id();
68    let army_owner = army.owner().clone();
69    *army.personnel_mut() = request.personnel;
70
71    // We must take the speed of the army only after updating its personnel,
72    // because the slowest unit in it may not have been sent as part of the maneuver.
73    // Related issue: https://github.com/tsukilabs/nil/issues/267
74    let army_speed = army.speed(&self.config);
75
76    let (id, maneuver) = Maneuver::builder()
77      .army(army_id)
78      .kind(request.kind)
79      .origin(request.origin)
80      .destination(request.destination)
81      .speed(army_speed)
82      .build()?;
83
84    self.military.insert_maneuver(maneuver);
85
86    let army = self.military.army_mut(army_id)?;
87    *army.state_mut() = ArmyState::with_maneuver(id);
88
89    // The remaining personnel should only be spawned after updating the state of the original army.
90    // Otherwise, both would be collapsed into a single army again in the `spawn` call.
91    if !remaining.is_empty() {
92      Army::builder()
93        .owner(army_owner)
94        .personnel(remaining)
95        .build()
96        .spawn(&mut self.military, request.origin);
97    }
98
99    if emit {
100      self.emit_military_to_maneuver_players(id)?;
101    }
102
103    Ok(id)
104  }
105}