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