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::r#impl::academy::recruit_queue::{
22  AcademyRecruitOrder,
23  AcademyRecruitOrderId,
24  AcademyRecruitOrderRequest,
25};
26use building::r#impl::prefecture::build_queue::{
27  PrefectureBuildOrder,
28  PrefectureBuildOrderKind,
29  PrefectureBuildOrderRequest,
30};
31use building::r#impl::stable::recruit_queue::{
32  StableRecruitOrder,
33  StableRecruitOrderId,
34  StableRecruitOrderRequest,
35};
36use building::r#impl::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")]
53#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
54pub struct Infrastructure {
55  #[builder(default)]
56  academy: Academy,
57
58  #[builder(default)]
59  farm: Farm,
60
61  #[builder(default)]
62  iron_mine: IronMine,
63
64  #[builder(default)]
65  prefecture: Prefecture,
66
67  #[builder(default)]
68  quarry: Quarry,
69
70  #[builder(default)]
71  sawmill: Sawmill,
72
73  #[builder(default)]
74  silo: Silo,
75
76  #[builder(default)]
77  stable: Stable,
78
79  #[builder(default)]
80  wall: Wall,
81
82  #[builder(default)]
83  warehouse: Warehouse,
84
85  #[builder(default)]
86  workshop: Workshop,
87}
88
89impl Infrastructure {
90  #[inline]
91  pub fn new() -> Self {
92    Self::default()
93  }
94
95  pub const fn storage(&self, id: StorageId) -> &dyn Storage {
96    match id {
97      StorageId::Silo => &self.silo,
98      StorageId::Warehouse => &self.warehouse,
99    }
100  }
101
102  pub const fn mine(&self, id: MineId) -> &dyn Mine {
103    match id {
104      MineId::Farm => &self.farm,
105      MineId::IronMine => &self.iron_mine,
106      MineId::Quarry => &self.quarry,
107      MineId::Sawmill => &self.sawmill,
108    }
109  }
110
111  pub gen fn iter(&self) -> &dyn Building {
112    let Self {
113      academy,
114      farm,
115      iron_mine,
116      prefecture,
117      quarry,
118      sawmill,
119      silo,
120      stable,
121      wall,
122      warehouse,
123      workshop,
124    } = self;
125
126    yield academy.as_dyn();
127    yield farm.as_dyn();
128    yield iron_mine.as_dyn();
129    yield prefecture.as_dyn();
130    yield quarry.as_dyn();
131    yield sawmill.as_dyn();
132    yield silo.as_dyn();
133    yield stable.as_dyn();
134    yield wall.as_dyn();
135    yield warehouse.as_dyn();
136    yield workshop.as_dyn();
137  }
138
139  pub gen fn iter_mut(&mut self) -> &mut dyn Building {
140    let Self {
141      academy,
142      farm,
143      iron_mine,
144      prefecture,
145      quarry,
146      sawmill,
147      silo,
148      stable,
149      wall,
150      warehouse,
151      workshop,
152    } = self;
153
154    yield academy.as_dyn_mut();
155    yield farm.as_dyn_mut();
156    yield iron_mine.as_dyn_mut();
157    yield prefecture.as_dyn_mut();
158    yield quarry.as_dyn_mut();
159    yield sawmill.as_dyn_mut();
160    yield silo.as_dyn_mut();
161    yield stable.as_dyn_mut();
162    yield wall.as_dyn_mut();
163    yield warehouse.as_dyn_mut();
164    yield workshop.as_dyn_mut();
165  }
166
167  pub fn score(&self, stats: &InfrastructureStats) -> Result<Score> {
168    let mut score = Score::default();
169    for id in BuildingId::iter() {
170      let level = self.building(id).level();
171      if level > 0u8 {
172        let stats = stats.building(id)?;
173        score += stats.get(level)?.score;
174      }
175    }
176
177    Ok(score)
178  }
179
180  /// Determines the amount of resources generated by the mines at their current level,
181  /// before applying any modifiers, such as city stability.
182  pub fn round_base_production(&self, stats: &InfrastructureStats) -> Result<Resources> {
183    let mut resources = Resources::default();
184
185    macro_rules! set {
186      ($building:ident, $resource:ident) => {
187        paste::paste! {
188          let mine = &self.[<$building:snake>];
189          if mine.level() > 0u8 && mine.is_enabled() {
190            let mine_stats = stats.mine(MineId::$building)?;
191            resources.$resource = mine.production(mine_stats)?.into();
192          }
193        }
194      };
195    }
196
197    set!(Farm, food);
198    set!(IronMine, iron);
199    set!(Quarry, stone);
200    set!(Sawmill, wood);
201
202    Ok(resources)
203  }
204
205  pub(crate) fn add_prefecture_build_order(
206    &mut self,
207    request: &PrefectureBuildOrderRequest,
208    table: &BuildingStatsTable,
209    available_resources: Resources,
210  ) -> Result<&PrefectureBuildOrder> {
211    let level = self.building(request.building).level();
212    self
213      .prefecture
214      .build_queue_mut()
215      .build(request, table, level, available_resources)
216  }
217
218  #[must_use]
219  pub(crate) fn cancel_prefecture_build_order(&mut self) -> Option<PrefectureBuildOrder> {
220    self.prefecture.build_queue_mut().cancel()
221  }
222
223  pub(crate) fn process_prefecture_build_queue(&mut self, config: &WorldConfig) {
224    if let Some(orders) = self.prefecture.process_queue(config) {
225      for order in orders {
226        let building = self.building_mut(order.building());
227        match order.kind() {
228          PrefectureBuildOrderKind::Construction => building.increase_level(),
229          PrefectureBuildOrderKind::Demolition => building.decrease_level(),
230        }
231      }
232    }
233  }
234}
235
236macro_rules! impl_infrastructure {
237  ($($building:ident),+) => {
238    paste::paste! {
239      impl Infrastructure {
240        $(
241          #[inline]
242          pub const fn [<$building:snake>](&self) -> &$building {
243            &self.[<$building:snake>]
244          }
245        )+
246
247        /// Creates a new instance with all buildings set to their maximum level.
248        pub fn with_max_level() -> Self {
249          Self {
250            $([<$building:snake>]: $building::with_max_level(),)+
251          }
252        }
253
254        pub const fn building(&self, id: BuildingId) -> &dyn Building {
255          match id {
256            $(BuildingId::$building => &self.[<$building:snake>],)+
257          }
258        }
259
260        pub(crate) const fn building_mut(&mut self, id: BuildingId) -> &mut dyn Building {
261          match id {
262            $(BuildingId::$building => &mut self.[<$building:snake>],)+
263          }
264        }
265
266        /// Determines the maintenance tax required for all buildings at their current levels.
267        pub fn base_maintenance(&self, stats: &InfrastructureStats) -> Result<Maintenance> {
268          let mut maintenance = Maintenance::default();
269          $(
270            let building = &self.[<$building:snake>];
271            if building.level() > 0u8 && building.is_enabled() {
272              let building_stats = stats.building(BuildingId::$building)?;
273              maintenance += building.maintenance(&building_stats)?;
274            }
275          )+
276
277          Ok(maintenance)
278        }
279
280        /// Determines whether the infrastructure meets the requirements.
281        pub fn has_required_levels(&self, requirements: &InfrastructureRequirements) -> bool {
282          $(self.[<$building:snake>].level() >= requirements.[<$building:snake>] &&)+ true
283        }
284      }
285    }
286  };
287}
288
289impl_infrastructure!(
290  Academy, Farm, IronMine, Prefecture, Quarry, Sawmill, Silo, Stable, Wall, Warehouse, Workshop
291);
292
293macro_rules! impl_recruitment {
294  ($building:ident) => {
295    paste::paste! {
296      impl Infrastructure {
297        pub(crate) fn [<add_ $building:snake _recruit_order>](
298          &mut self,
299          request: &[<$building RecruitOrderRequest>],
300          available_resources: Resources,
301        ) -> Result<&[<$building RecruitOrder>]> {
302          self
303            .[<$building:snake>]
304            .recruit_queue_mut()
305            .recruit(request, available_resources)
306        }
307
308        #[must_use]
309        pub(crate) fn [<cancel_ $building:snake _recruit_order>](
310          &mut self,
311          id: [<$building RecruitOrderId>]
312        ) -> Option<[<$building RecruitOrder>]> {
313          self.[<$building:snake>].recruit_queue_mut().cancel(id)
314        }
315
316        #[must_use]
317        pub(crate) fn [<process_ $building:snake _recruit_queue>](
318          &mut self,
319          config: &WorldConfig
320        ) -> Option<ArmyPersonnel> {
321          self
322            .[<$building:snake>]
323            .process_queue(config)?
324            .into_iter()
325            .map(Squad::from)
326            .collect::<ArmyPersonnel>()
327            .pipe(Some)
328        }
329      }
330    }
331  };
332}
333
334impl_recruitment!(Academy);
335impl_recruitment!(Stable);
336impl_recruitment!(Workshop);