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