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::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 fn score(&self, stats: &InfrastructureStats) -> Result<Score> {
112 let mut score = Score::default();
113 for id in BuildingId::iter() {
114 let level = self.building(id).level();
115 if level > 0u8 {
116 let stats = stats.building(id)?;
117 score += stats.get(level)?.score;
118 }
119 }
120
121 Ok(score)
122 }
123
124 pub fn round_base_production(&self, stats: &InfrastructureStats) -> Result<Resources> {
127 let mut resources = Resources::default();
128
129 macro_rules! set {
130 ($building:ident, $resource:ident) => {
131 paste::paste! {
132 let mine = &self.[<$building:snake>];
133 if mine.level() > 0u8 && mine.is_enabled() {
134 let mine_stats = stats.mine(MineId::$building)?;
135 resources.$resource = mine.production(mine_stats)?.into();
136 }
137 }
138 };
139 }
140
141 set!(Farm, food);
142 set!(IronMine, iron);
143 set!(Quarry, stone);
144 set!(Sawmill, wood);
145
146 Ok(resources)
147 }
148
149 pub(crate) fn add_prefecture_build_order(
150 &mut self,
151 request: &PrefectureBuildOrderRequest,
152 table: &BuildingStatsTable,
153 current_resources: Option<&Resources>,
154 ) -> Result<&PrefectureBuildOrder> {
155 let level = self.building(request.building).level();
156 self
157 .prefecture
158 .build_queue_mut()
159 .build(request, table, level, current_resources)
160 }
161
162 #[must_use]
163 pub(crate) fn cancel_prefecture_build_order(&mut self) -> Option<PrefectureBuildOrder> {
164 self.prefecture.build_queue_mut().cancel()
165 }
166
167 pub(crate) fn process_prefecture_build_queue(&mut self, config: &WorldConfig) {
168 if let Some(orders) = self.prefecture.process_queue(config) {
169 for order in orders {
170 let building = self.building_mut(order.building());
171 match order.kind() {
172 PrefectureBuildOrderKind::Construction => building.increase_level(),
173 PrefectureBuildOrderKind::Demolition => building.decrease_level(),
174 }
175 }
176 }
177 }
178}
179
180macro_rules! impl_infrastructure {
181 ($($building:ident),+) => {
182 paste::paste! {
183 impl Infrastructure {
184 $(
185 #[inline]
186 pub const fn [<$building:snake>](&self) -> &$building {
187 &self.[<$building:snake>]
188 }
189 )+
190
191 pub fn with_max_level() -> Self {
193 Self {
194 $([<$building:snake>]: $building::with_max_level(),)+
195 }
196 }
197
198 pub const fn building(&self, id: BuildingId) -> &dyn Building {
199 match id {
200 $(BuildingId::$building => &self.[<$building:snake>],)+
201 }
202 }
203
204 pub(crate) const fn building_mut(&mut self, id: BuildingId) -> &mut dyn Building {
205 match id {
206 $(BuildingId::$building => &mut self.[<$building:snake>],)+
207 }
208 }
209
210 pub fn base_maintenance(&self, stats: &InfrastructureStats) -> Result<Maintenance> {
212 let mut maintenance = Maintenance::default();
213 $(
214 let building = &self.[<$building:snake>];
215 if building.level() > 0u8 && building.is_enabled() {
216 let building_stats = stats.building(BuildingId::$building)?;
217 maintenance += building.maintenance(&building_stats)?;
218 }
219 )+
220
221 Ok(maintenance)
222 }
223
224 pub fn has_required_levels(&self, requirements: &InfrastructureRequirements) -> bool {
226 $(self.[<$building:snake>].level() >= requirements.[<$building:snake>] &&)+ true
227 }
228 }
229 }
230 };
231}
232
233impl_infrastructure!(
234 Academy, Farm, IronMine, Prefecture, Quarry, Sawmill, Silo, Stable, Wall, Warehouse, Workshop
235);
236
237macro_rules! impl_recruitment {
238 ($building:ident) => {
239 paste::paste! {
240 impl Infrastructure {
241 pub(crate) fn [<add_ $building:snake _recruit_order>](
242 &mut self,
243 request: &[<$building RecruitOrderRequest>],
244 current_resources: Option<&Resources>,
245 ) -> Result<&[<$building RecruitOrder>]> {
246 self
247 .[<$building:snake>]
248 .recruit_queue_mut()
249 .recruit(request, current_resources)
250 }
251
252 #[must_use]
253 pub(crate) fn [<cancel_ $building:snake _recruit_order>](
254 &mut self,
255 id: [<$building RecruitOrderId>]
256 ) -> Option<[<$building RecruitOrder>]> {
257 self.[<$building:snake>].recruit_queue_mut().cancel(id)
258 }
259
260 #[must_use]
261 pub(crate) fn [<process_ $building:snake _recruit_queue>](
262 &mut self,
263 config: &WorldConfig
264 ) -> Option<ArmyPersonnel> {
265 self
266 .[<$building:snake>]
267 .process_queue(config)?
268 .into_iter()
269 .map(Squad::from)
270 .collect::<ArmyPersonnel>()
271 .pipe(Some)
272 }
273 }
274 }
275 };
276}
277
278impl_recruitment!(Academy);
279impl_recruitment!(Stable);
280impl_recruitment!(Workshop);