Skip to main content

nil_core/world/round/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4mod behavior;
5mod maneuver;
6
7#[cfg(test)]
8mod tests;
9
10use crate::error::Result;
11use crate::player::{Player, PlayerId};
12use crate::resources::prelude::*;
13use crate::round::Round;
14use crate::ruler::Ruler;
15use crate::world::World;
16use std::collections::HashMap;
17
18impl World {
19  #[inline]
20  pub fn round(&self) -> &Round {
21    &self.round
22  }
23
24  pub fn start_round(&mut self) -> Result<()> {
25    let ids = self
26      .player_manager
27      .active_players()
28      .map(Player::id);
29
30    self.round.start(ids)?;
31    self.emit_round()?;
32
33    Ok(())
34  }
35
36  pub fn set_player_ready(&mut self, player: &PlayerId, is_ready: bool) -> Result<()> {
37    self.round.set_ready(player, is_ready);
38
39    if self.round.is_done() {
40      self.next_round(true)?;
41    } else {
42      self.emit_round()?;
43    }
44
45    Ok(())
46  }
47
48  /// Forcefully ends the current round.
49  pub fn dangerously_end_round(&mut self, emit: bool) -> Result<()> {
50    self.round.dangerously_set_done();
51    self.next_round(emit)?;
52    Ok(())
53  }
54
55  pub(super) fn next_round(&mut self, emit: bool) -> Result<()> {
56    let ids = self
57      .player_manager
58      .active_players()
59      .map(Player::id);
60
61    self.round.next(ids)?;
62    self.prepare_next_round()?;
63    self.consume_pending_save()?;
64
65    if emit {
66      self.emit_round()?;
67    }
68
69    if let Some(on_next_round) = self.on_next_round.clone() {
70      on_next_round.call(self);
71    }
72
73    Ok(())
74  }
75
76  fn prepare_next_round(&mut self) -> Result<()> {
77    self.update_resources()?;
78    self.process_city_queues();
79    self.collapse_armies();
80    self.process_maneuvers()?;
81    self.update_ranking()?;
82    self.process_npc_behavior()?;
83    Ok(())
84  }
85
86  /// Updates all rulers' resources by increasing them with the amount generated
87  /// in the current round and then deducting all maintenance-related costs.
88  fn update_resources(&mut self) -> Result<()> {
89    let stats = self.stats.infrastructure.as_ref();
90    let mut diff: HashMap<Ruler, ResourcesDiff> = HashMap::new();
91
92    for city in self.continent.cities() {
93      let owner = city.owner().clone();
94      let resources = diff.entry(owner).or_default();
95      *resources += city.round_production(stats)?;
96      resources.food -= city.maintenance(stats)?;
97    }
98
99    for (ruler, mut resources) in diff {
100      resources.food -= self.military.maintenance_of(ruler.clone());
101      let capacity = self.get_storage_capacity(ruler.clone())?;
102      self
103        .ruler_mut(&ruler)?
104        .resources_mut()
105        .add_within_capacity(&resources, &capacity);
106    }
107
108    Ok(())
109  }
110
111  /// Processes the build and recruitment queues for all cities.
112  fn process_city_queues(&mut self) {
113    let config = self.config();
114    for city in self.continent.cities_mut() {
115      let coord = city.coord();
116      let owner = city.owner().clone();
117      let infrastructure = city.infrastructure_mut();
118
119      infrastructure.process_prefecture_build_queue(&config);
120
121      macro_rules! process_recruit_queue {
122        ($building:ident) => {
123          paste::paste! {
124            if let Some(personnel) = infrastructure.[<process_ $building:snake _recruit_queue>](&config) {
125              self.military.spawn(coord, owner.clone(), personnel);
126            }
127          }
128        };
129      }
130
131      process_recruit_queue!(Academy);
132      process_recruit_queue!(Stable);
133      process_recruit_queue!(Workshop);
134    }
135  }
136}