Skip to main content

nil_core/infrastructure/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4pub mod building;
5pub mod catalog;
6pub mod mine;
7pub mod prelude;
8pub mod queue;
9pub mod requirements;
10pub mod stats;
11pub mod storage;
12
13use crate::error::Result;
14use crate::military::army::personnel::ArmyPersonnel;
15use crate::military::squad::Squad;
16use crate::ranking::score::Score;
17use crate::resources::Resources;
18use crate::resources::maintenance::Maintenance;
19use crate::world::config::WorldConfig;
20use bon::Builder;
21use building::academy::recruit_queue::{
22  AcademyRecruitOrder,
23  AcademyRecruitOrderId,
24  AcademyRecruitOrderRequest,
25};
26use building::prefecture::build_queue::{
27  PrefectureBuildOrder,
28  PrefectureBuildOrderKind,
29  PrefectureBuildOrderRequest,
30};
31use building::stable::recruit_queue::{
32  StableRecruitOrder,
33  StableRecruitOrderId,
34  StableRecruitOrderRequest,
35};
36use building::workshop::recruit_queue::{
37  WorkshopRecruitOrder,
38  WorkshopRecruitOrderId,
39  WorkshopRecruitOrderRequest,
40};
41use building::{Building, BuildingId, BuildingStatsTable, MineId, StorageId};
42use mine::Mine;
43use prelude::*;
44use requirements::InfrastructureRequirements;
45use serde::{Deserialize, Serialize};
46use stats::InfrastructureStats;
47use storage::Storage;
48use strum::IntoEnumIterator;
49use tap::Pipe;
50
51#[derive(Builder, Clone, Debug, Default, Deserialize, Serialize)]
52#[serde(default, rename_all = "camelCase")]
53pub struct Infrastructure {
54  #[builder(default)]
55  academy: Academy,
56
57  #[builder(default)]
58  farm: Farm,
59
60  #[builder(default)]
61  iron_mine: IronMine,
62
63  #[builder(default)]
64  prefecture: Prefecture,
65
66  #[builder(default)]
67  quarry: Quarry,
68
69  #[builder(default)]
70  sawmill: Sawmill,
71
72  #[builder(default)]
73  silo: Silo,
74
75  #[builder(default)]
76  stable: Stable,
77
78  #[builder(default)]
79  wall: Wall,
80
81  #[builder(default)]
82  warehouse: Warehouse,
83
84  #[builder(default)]
85  workshop: Workshop,
86}
87
88impl Infrastructure {
89  #[inline]
90  pub fn new() -> Self {
91    Self::default()
92  }
93
94  pub const fn storage(&self, id: StorageId) -> &dyn Storage {
95    match id {
96      StorageId::Silo => &self.silo,
97      StorageId::Warehouse => &self.warehouse,
98    }
99  }
100
101  pub const fn mine(&self, id: MineId) -> &dyn Mine {
102    match id {
103      MineId::Farm => &self.farm,
104      MineId::IronMine => &self.iron_mine,
105      MineId::Quarry => &self.quarry,
106      MineId::Sawmill => &self.sawmill,
107    }
108  }
109
110  pub fn score(&self, stats: &InfrastructureStats) -> Result<Score> {
111    let mut score = Score::default();
112    for id in BuildingId::iter() {
113      let level = self.building(id).level();
114      if level > 0u8 {
115        let stats = stats.building(id)?;
116        score += stats.get(level)?.score;
117      }
118    }
119
120    Ok(score)
121  }
122
123  /// Determines the amount of resources generated by the mines at their current level,
124  /// before applying any modifiers, such as city stability.
125  pub fn round_base_production(&self, stats: &InfrastructureStats) -> Result<Resources> {
126    let mut resources = Resources::default();
127
128    macro_rules! set {
129      ($building:ident, $resource:ident) => {
130        paste::paste! {
131          let mine = &self.[<$building:snake>];
132          if mine.level() > 0u8 && mine.is_enabled() {
133            let mine_stats = stats.mine(MineId::$building)?;
134            resources.$resource = mine.production(mine_stats)?.into();
135          }
136        }
137      };
138    }
139
140    set!(Farm, food);
141    set!(IronMine, iron);
142    set!(Quarry, stone);
143    set!(Sawmill, wood);
144
145    Ok(resources)
146  }
147
148  pub(crate) fn add_prefecture_build_order(
149    &mut self,
150    request: &PrefectureBuildOrderRequest,
151    table: &BuildingStatsTable,
152    current_resources: Option<&Resources>,
153  ) -> Result<&PrefectureBuildOrder> {
154    let level = self.building(request.building).level();
155    self
156      .prefecture
157      .build_queue_mut()
158      .build(request, table, level, current_resources)
159  }
160
161  #[must_use]
162  pub(crate) fn cancel_prefecture_build_order(&mut self) -> Option<PrefectureBuildOrder> {
163    self.prefecture.build_queue_mut().cancel()
164  }
165
166  pub(crate) fn process_prefecture_build_queue(&mut self, config: &WorldConfig) {
167    if let Some(orders) = self.prefecture.process_queue(config) {
168      for order in orders {
169        let building = self.building_mut(order.building());
170        match order.kind() {
171          PrefectureBuildOrderKind::Construction => building.increase_level(),
172          PrefectureBuildOrderKind::Demolition => building.decrease_level(),
173        }
174      }
175    }
176  }
177}
178
179macro_rules! impl_infrastructure {
180  ($($building:ident),+) => {
181    paste::paste! {
182      impl Infrastructure {
183        $(
184          #[inline]
185          pub const fn [<$building:snake>](&self) -> &$building {
186            &self.[<$building:snake>]
187          }
188        )+
189
190        /// Creates a new instance with all buildings set to their maximum level.
191        pub fn with_max_level() -> Self {
192          Self {
193            $([<$building:snake>]: $building::with_max_level(),)+
194          }
195        }
196
197        pub const fn building(&self, id: BuildingId) -> &dyn Building {
198          match id {
199            $(BuildingId::$building => &self.[<$building:snake>],)+
200          }
201        }
202
203        pub(crate) const fn building_mut(&mut self, id: BuildingId) -> &mut dyn Building {
204          match id {
205            $(BuildingId::$building => &mut self.[<$building:snake>],)+
206          }
207        }
208
209        /// Determines the maintenance tax required for all buildings at their current levels.
210        pub fn base_maintenance(&self, stats: &InfrastructureStats) -> Result<Maintenance> {
211          let mut maintenance = Maintenance::default();
212          $(
213            let building = &self.[<$building:snake>];
214            if building.level() > 0u8 && building.is_enabled() {
215              let building_stats = stats.building(BuildingId::$building)?;
216              maintenance += building.maintenance(&building_stats)?;
217            }
218          )+
219
220          Ok(maintenance)
221        }
222
223        /// Determines whether the infrastructure meets the requirements.
224        pub fn has_required_levels(&self, requirements: &InfrastructureRequirements) -> bool {
225          $(self.[<$building:snake>].level() >= requirements.[<$building:snake>] &&)+ true
226        }
227      }
228    }
229  };
230}
231
232impl_infrastructure!(
233  Academy, Farm, IronMine, Prefecture, Quarry, Sawmill, Silo, Stable, Wall, Warehouse, Workshop
234);
235
236macro_rules! impl_recruitment {
237  ($building:ident) => {
238    paste::paste! {
239      impl Infrastructure {
240        pub(crate) fn [<add_ $building:snake _recruit_order>](
241          &mut self,
242          request: &[<$building RecruitOrderRequest>],
243          current_resources: Option<&Resources>,
244        ) -> Result<&[<$building RecruitOrder>]> {
245          self
246            .[<$building:snake>]
247            .recruit_queue_mut()
248            .recruit(request, current_resources)
249        }
250
251        #[must_use]
252        pub(crate) fn [<cancel_ $building:snake _recruit_order>](
253          &mut self,
254          id: [<$building RecruitOrderId>]
255        ) -> Option<[<$building RecruitOrder>]> {
256          self.[<$building:snake>].recruit_queue_mut().cancel(id)
257        }
258
259        #[must_use]
260        pub(crate) fn [<process_ $building:snake _recruit_queue>](
261          &mut self,
262          config: &WorldConfig
263        ) -> Option<ArmyPersonnel> {
264          self
265            .[<$building:snake>]
266            .process_queue(config)?
267            .into_iter()
268            .map(Squad::from)
269            .collect::<ArmyPersonnel>()
270            .pipe(Some)
271        }
272      }
273    }
274  };
275}
276
277impl_recruitment!(Academy);
278impl_recruitment!(Stable);
279impl_recruitment!(Workshop);