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