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