nil_core/infrastructure/building/
mod.rs1pub mod r#impl;
5pub mod level;
6
7use crate::error::{Error, Result};
8use crate::infrastructure::building::level::BuildingLevel;
9use crate::infrastructure::requirements::InfrastructureRequirements;
10use crate::ranking::score::Score;
11use crate::resources::prelude::*;
12use nil_num::growth::growth;
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use strum::{EnumIs, EnumIter};
16use subenum::subenum;
17
18pub trait Building: Send + Sync {
19 fn id(&self) -> BuildingId;
20
21 fn is_enabled(&self) -> bool;
23 fn toggle(&mut self, enabled: bool);
25
26 fn level(&self) -> BuildingLevel;
28 fn min_level(&self) -> BuildingLevel;
30 fn max_level(&self) -> BuildingLevel;
32 fn set_level(&mut self, level: BuildingLevel);
34
35 fn set_min_level(&mut self) {
37 self.set_level(self.min_level());
38 }
39
40 fn set_max_level(&mut self) {
42 self.set_level(self.max_level());
43 }
44
45 fn increase_level(&mut self) {
47 self.increase_level_by(1);
48 }
49
50 fn increase_level_by(&mut self, amount: u8);
52
53 fn decrease_level(&mut self) {
55 self.decrease_level_by(1);
56 }
57
58 fn decrease_level_by(&mut self, amount: u8);
60
61 fn is_min_level(&self) -> bool {
63 self.level() == self.min_level()
64 }
65
66 fn is_max_level(&self) -> bool {
68 self.level() >= self.max_level()
69 }
70
71 fn min_cost(&self) -> Cost;
73 fn max_cost(&self) -> Cost;
75
76 fn food_ratio(&self) -> ResourceRatio;
78 fn iron_ratio(&self) -> ResourceRatio;
80 fn stone_ratio(&self) -> ResourceRatio;
82 fn wood_ratio(&self) -> ResourceRatio;
84
85 fn maintenance(&self, stats: &BuildingStatsTable) -> Result<Maintenance>;
87 fn maintenance_ratio(&self) -> MaintenanceRatio;
89
90 fn min_workforce(&self) -> Workforce;
92 fn max_workforce(&self) -> Workforce;
94
95 fn score(&self, stats: &BuildingStatsTable) -> Result<Score>;
97 fn min_score(&self) -> Score;
99 fn max_score(&self) -> Score;
101
102 fn infrastructure_requirements(&self) -> &InfrastructureRequirements;
104
105 fn is_civil(&self) -> bool {
106 self.id().is_civil()
107 }
108
109 fn is_military(&self) -> bool {
110 self.id().is_military()
111 }
112
113 fn is_mine(&self) -> bool {
114 self.id().is_mine()
115 }
116
117 fn is_storage(&self) -> bool {
118 self.id().is_storage()
119 }
120}
121
122#[subenum(CivilBuildingId, MilitaryBuildingId, MineId, StorageId)]
123#[derive(Copy, Debug, strum::Display, EnumIs, EnumIter, Hash, Deserialize, Serialize)]
124#[derive_const(Clone, PartialEq, Eq)]
125#[serde(rename_all = "kebab-case")]
126#[strum(serialize_all = "kebab-case")]
127#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
128pub enum BuildingId {
129 #[subenum(MilitaryBuildingId)]
130 Academy,
131
132 #[subenum(CivilBuildingId, MineId)]
133 Farm,
134
135 #[subenum(CivilBuildingId, MineId)]
136 IronMine,
137
138 #[subenum(CivilBuildingId)]
139 Prefecture,
140
141 #[subenum(CivilBuildingId, MineId)]
142 Quarry,
143
144 #[subenum(CivilBuildingId, MineId)]
145 Sawmill,
146
147 #[subenum(CivilBuildingId, StorageId)]
148 Silo,
149
150 #[subenum(MilitaryBuildingId)]
151 Stable,
152
153 Wall,
154
155 #[subenum(CivilBuildingId, StorageId)]
156 Warehouse,
157
158 #[subenum(MilitaryBuildingId)]
159 Workshop,
160}
161
162impl BuildingId {
163 #[inline]
164 pub fn is_civil(self) -> bool {
165 CivilBuildingId::try_from(self).is_ok()
166 }
167
168 #[inline]
169 pub fn is_military(self) -> bool {
170 MilitaryBuildingId::try_from(self).is_ok()
171 }
172
173 #[inline]
174 pub fn is_mine(self) -> bool {
175 MineId::try_from(self).is_ok()
176 }
177
178 #[inline]
179 pub fn is_storage(self) -> bool {
180 StorageId::try_from(self).is_ok()
181 }
182}
183
184#[derive(Clone, Debug, Deserialize, Serialize)]
186#[serde(rename_all = "camelCase")]
187#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
188pub struct BuildingStats {
189 pub level: BuildingLevel,
190 pub cost: Cost,
191 pub resources: Resources,
192 pub maintenance: Maintenance,
193 pub workforce: Workforce,
194 pub score: Score,
195}
196
197#[derive(Clone, Debug, Deserialize, Serialize)]
198#[serde(rename_all = "camelCase")]
199#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
200pub struct BuildingStatsTable {
201 id: BuildingId,
202 min_level: BuildingLevel,
203 max_level: BuildingLevel,
204 table: HashMap<BuildingLevel, BuildingStats>,
205}
206
207impl BuildingStatsTable {
208 pub(crate) fn new(building: &dyn Building) -> Self {
209 let min_level = building.min_level();
210 let max_level = building.max_level();
211 let mut table = HashMap::with_capacity(max_level.into());
212
213 let mut cost = f64::from(building.min_cost());
214 let cost_growth = growth()
215 .floor(cost)
216 .ceil(building.max_cost())
217 .max_level(max_level)
218 .call();
219
220 let mut workforce = f64::from(building.min_workforce());
221 let workforce_growth = growth()
222 .floor(workforce)
223 .ceil(building.max_workforce())
224 .max_level(max_level)
225 .call();
226
227 let mut score = f64::from(building.min_score());
228 let score_growth = growth()
229 .floor(score)
230 .ceil(building.max_score())
231 .max_level(max_level)
232 .call();
233
234 let food_ratio = *building.food_ratio();
235 let iron_ratio = *building.iron_ratio();
236 let stone_ratio = *building.stone_ratio();
237 let wood_ratio = *building.wood_ratio();
238
239 let maintenance_ratio = *building.maintenance_ratio();
240 let mut maintenance = cost * maintenance_ratio;
241
242 for level in 1..=u8::from(max_level) {
243 let level = BuildingLevel::new(level);
244 let resources = Resources {
245 food: Food::from((cost * food_ratio).round()),
246 iron: Iron::from((cost * iron_ratio).round()),
247 stone: Stone::from((cost * stone_ratio).round()),
248 wood: Wood::from((cost * wood_ratio).round()),
249 };
250
251 table.insert(
252 level,
253 BuildingStats {
254 level,
255 cost: Cost::from(cost.round()),
256 resources,
257 maintenance: Maintenance::from(maintenance.round()),
258 workforce: Workforce::from(workforce.round()),
259 score: Score::from(score.round()),
260 },
261 );
262
263 debug_assert!(cost.is_normal());
264 debug_assert!(workforce.is_normal());
265
266 debug_assert!(maintenance.is_finite());
267 debug_assert!(maintenance >= 0.0);
268
269 debug_assert!(score.is_finite());
270 debug_assert!(score >= 0.0);
271
272 cost += cost * cost_growth;
273 workforce += workforce * workforce_growth;
274 score += score * score_growth;
275
276 maintenance = cost * maintenance_ratio;
277 }
278
279 table.shrink_to_fit();
280
281 Self {
282 id: building.id(),
283 min_level,
284 max_level,
285 table,
286 }
287 }
288
289 #[inline]
290 pub fn id(&self) -> BuildingId {
291 self.id
292 }
293
294 #[inline]
295 pub fn min_level(&self) -> BuildingLevel {
296 self.min_level
297 }
298
299 #[inline]
300 pub fn max_level(&self) -> BuildingLevel {
301 self.max_level
302 }
303
304 #[inline]
305 pub fn get(&self, level: BuildingLevel) -> Result<&BuildingStats> {
306 self
307 .table
308 .get(&level)
309 .ok_or(Error::BuildingStatsNotFoundForLevel(self.id, level))
310 }
311}