nil_core/infrastructure/
mod.rs1pub 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 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 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 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 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);