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