1pub 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 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 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 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 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);