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