nil_core/infrastructure/building/
mod.rs1pub mod academy;
5pub mod farm;
6pub mod iron_mine;
7pub mod prefecture;
8pub mod quarry;
9pub mod sawmill;
10pub mod silo;
11pub mod stable;
12pub mod wall;
13pub mod warehouse;
14pub mod workshop;
15
16use crate::error::{Error, Result};
17use crate::infrastructure::requirements::InfrastructureRequirements;
18use crate::ranking::score::Score;
19use crate::resources::prelude::*;
20use derive_more::{Deref, Into};
21use nil_num::growth::growth;
22use serde::{Deserialize, Serialize};
23use std::cmp;
24use std::collections::HashMap;
25use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
26use strum::{EnumIs, EnumIter};
27use subenum::subenum;
28
29pub trait Building: Send + Sync {
30 fn id(&self) -> BuildingId;
31
32 fn is_enabled(&self) -> bool;
34 fn toggle(&mut self, enabled: bool);
36
37 fn level(&self) -> BuildingLevel;
39 fn min_level(&self) -> BuildingLevel;
41 fn max_level(&self) -> BuildingLevel;
43 fn set_level(&mut self, level: BuildingLevel);
45
46 fn set_min_level(&mut self) {
48 self.set_level(self.min_level());
49 }
50
51 fn set_max_level(&mut self) {
53 self.set_level(self.max_level());
54 }
55
56 fn increase_level(&mut self) {
58 self.increase_level_by(1);
59 }
60
61 fn increase_level_by(&mut self, amount: u8);
63
64 fn decrease_level(&mut self) {
66 self.decrease_level_by(1);
67 }
68
69 fn decrease_level_by(&mut self, amount: u8);
71
72 fn is_min_level(&self) -> bool {
74 self.level() == self.min_level()
75 }
76
77 fn is_max_level(&self) -> bool {
79 self.level() >= self.max_level()
80 }
81
82 fn min_cost(&self) -> Cost;
84 fn max_cost(&self) -> Cost;
86 fn wood_ratio(&self) -> ResourceRatio;
88 fn stone_ratio(&self) -> ResourceRatio;
90 fn iron_ratio(&self) -> ResourceRatio;
92
93 fn maintenance(&self, stats: &BuildingStatsTable) -> Result<Maintenance>;
95 fn maintenance_ratio(&self) -> MaintenanceRatio;
97
98 fn min_workforce(&self) -> Workforce;
100 fn max_workforce(&self) -> Workforce;
102
103 fn score(&self, stats: &BuildingStatsTable) -> Result<Score>;
105 fn min_score(&self) -> Score;
107 fn max_score(&self) -> Score;
109
110 fn infrastructure_requirements(&self) -> &InfrastructureRequirements;
112
113 fn is_civil(&self) -> bool {
114 self.id().is_civil()
115 }
116
117 fn is_military(&self) -> bool {
118 self.id().is_military()
119 }
120
121 fn is_mine(&self) -> bool {
122 self.id().is_mine()
123 }
124
125 fn is_storage(&self) -> bool {
126 self.id().is_storage()
127 }
128}
129
130#[subenum(CivilBuildingId, MilitaryBuildingId, MineId, StorageId)]
131#[derive(
132 Clone, Copy, Debug, strum::Display, EnumIs, EnumIter, PartialEq, Eq, Hash, Deserialize, Serialize,
133)]
134#[serde(rename_all = "kebab-case")]
135#[strum(serialize_all = "kebab-case")]
136pub enum BuildingId {
137 #[subenum(MilitaryBuildingId)]
138 Academy,
139
140 #[subenum(CivilBuildingId, MineId)]
141 Farm,
142
143 #[subenum(CivilBuildingId, MineId)]
144 IronMine,
145
146 #[subenum(CivilBuildingId)]
147 Prefecture,
148
149 #[subenum(CivilBuildingId, MineId)]
150 Quarry,
151
152 #[subenum(CivilBuildingId, MineId)]
153 Sawmill,
154
155 #[subenum(CivilBuildingId, StorageId)]
156 Silo,
157
158 #[subenum(MilitaryBuildingId)]
159 Stable,
160
161 Wall,
162
163 #[subenum(CivilBuildingId, StorageId)]
164 Warehouse,
165
166 #[subenum(MilitaryBuildingId)]
167 Workshop,
168}
169
170impl BuildingId {
171 #[inline]
172 pub fn is_civil(self) -> bool {
173 CivilBuildingId::try_from(self).is_ok()
174 }
175
176 #[inline]
177 pub fn is_military(self) -> bool {
178 MilitaryBuildingId::try_from(self).is_ok()
179 }
180
181 #[inline]
182 pub fn is_mine(self) -> bool {
183 MineId::try_from(self).is_ok()
184 }
185
186 #[inline]
187 pub fn is_storage(self) -> bool {
188 StorageId::try_from(self).is_ok()
189 }
190}
191
192#[derive(Clone, Debug, Deserialize, Serialize)]
194#[serde(rename_all = "camelCase")]
195pub struct BuildingStats {
196 pub level: BuildingLevel,
197 pub cost: Cost,
198 pub resources: Resources,
199 pub maintenance: Maintenance,
200 pub workforce: Workforce,
201 pub score: Score,
202}
203
204#[derive(Clone, Debug, Deserialize, Serialize)]
205#[serde(rename_all = "camelCase")]
206pub struct BuildingStatsTable {
207 id: BuildingId,
208 min_level: BuildingLevel,
209 max_level: BuildingLevel,
210 table: HashMap<BuildingLevel, BuildingStats>,
211}
212
213impl BuildingStatsTable {
214 pub(crate) fn new(building: &dyn Building) -> Self {
215 let min_level = building.min_level();
216 let max_level = building.max_level();
217 let mut table = HashMap::with_capacity((max_level.0).into());
218
219 let mut cost = f64::from(building.min_cost());
220 let cost_growth = growth()
221 .floor(cost)
222 .ceil(building.max_cost())
223 .max_level(max_level)
224 .call();
225
226 let mut workforce = f64::from(building.min_workforce());
227 let workforce_growth = growth()
228 .floor(workforce)
229 .ceil(building.max_workforce())
230 .max_level(max_level)
231 .call();
232
233 let mut score = f64::from(building.min_score());
234 let score_growth = growth()
235 .floor(score)
236 .ceil(building.max_score())
237 .max_level(max_level)
238 .call();
239
240 let wood_ratio = *building.wood_ratio();
241 let stone_ratio = *building.stone_ratio();
242 let iron_ratio = *building.iron_ratio();
243
244 let maintenance_ratio = *building.maintenance_ratio();
245 let mut maintenance = cost * maintenance_ratio;
246
247 for level in 1..=max_level.0 {
248 let level = BuildingLevel::new(level);
249 let resources = Resources {
250 food: Food::MIN,
251 iron: Iron::from((cost * iron_ratio).round()),
252 stone: Stone::from((cost * stone_ratio).round()),
253 wood: Wood::from((cost * wood_ratio).round()),
254 };
255
256 table.insert(
257 level,
258 BuildingStats {
259 level,
260 cost: Cost::from(cost.round()),
261 resources,
262 maintenance: Maintenance::from(maintenance.round()),
263 workforce: Workforce::from(workforce.round()),
264 score: Score::from(score.round()),
265 },
266 );
267
268 debug_assert!(cost.is_normal());
269 debug_assert!(workforce.is_normal());
270
271 debug_assert!(maintenance.is_finite());
272 debug_assert!(maintenance >= 0.0);
273
274 debug_assert!(score.is_finite());
275 debug_assert!(score >= 0.0);
276
277 cost += cost * cost_growth;
278 workforce += workforce * workforce_growth;
279 score += score * score_growth;
280
281 maintenance = cost * maintenance_ratio;
282 }
283
284 table.shrink_to_fit();
285
286 Self {
287 id: building.id(),
288 min_level,
289 max_level,
290 table,
291 }
292 }
293
294 #[inline]
295 pub fn id(&self) -> BuildingId {
296 self.id
297 }
298
299 #[inline]
300 pub fn min_level(&self) -> BuildingLevel {
301 self.min_level
302 }
303
304 #[inline]
305 pub fn max_level(&self) -> BuildingLevel {
306 self.max_level
307 }
308
309 #[inline]
310 pub fn get(&self, level: BuildingLevel) -> Result<&BuildingStats> {
311 self
312 .table
313 .get(&level)
314 .ok_or(Error::BuildingStatsNotFoundForLevel(self.id, level))
315 }
316}
317
318#[derive(
319 Clone,
320 Copy,
321 Debug,
322 Default,
323 Deref,
324 derive_more::Display,
325 Into,
326 PartialEq,
327 Eq,
328 PartialOrd,
329 Ord,
330 Hash,
331 Deserialize,
332 Serialize,
333)]
334#[into(i16, i32, u8, u16, u32, u64, usize, f64)]
335pub struct BuildingLevel(u8);
336
337impl BuildingLevel {
338 pub const ZERO: BuildingLevel = BuildingLevel(0);
339
340 #[inline]
341 pub const fn new(level: u8) -> Self {
342 Self(level)
343 }
344}
345
346impl From<BuildingLevel> for i8 {
347 fn from(level: BuildingLevel) -> Self {
348 debug_assert!(i8::try_from(level.0).is_ok());
349 i8::try_from(level.0).unwrap_or(i8::MAX)
350 }
351}
352
353impl PartialEq<u8> for BuildingLevel {
354 fn eq(&self, other: &u8) -> bool {
355 self.0.eq(other)
356 }
357}
358
359impl PartialEq<BuildingLevel> for u8 {
360 fn eq(&self, other: &BuildingLevel) -> bool {
361 self.eq(&other.0)
362 }
363}
364
365impl PartialEq<f64> for BuildingLevel {
366 fn eq(&self, other: &f64) -> bool {
367 f64::from(self.0).eq(other)
368 }
369}
370
371impl PartialEq<BuildingLevel> for f64 {
372 fn eq(&self, other: &BuildingLevel) -> bool {
373 self.eq(&f64::from(other.0))
374 }
375}
376
377impl PartialOrd<u8> for BuildingLevel {
378 fn partial_cmp(&self, other: &u8) -> Option<cmp::Ordering> {
379 self.0.partial_cmp(other)
380 }
381}
382
383impl PartialOrd<BuildingLevel> for u8 {
384 fn partial_cmp(&self, other: &BuildingLevel) -> Option<cmp::Ordering> {
385 self.partial_cmp(&other.0)
386 }
387}
388
389impl PartialOrd<f64> for BuildingLevel {
390 fn partial_cmp(&self, other: &f64) -> Option<cmp::Ordering> {
391 f64::from(self.0).partial_cmp(other)
392 }
393}
394
395impl PartialOrd<BuildingLevel> for f64 {
396 fn partial_cmp(&self, other: &BuildingLevel) -> Option<cmp::Ordering> {
397 self.partial_cmp(&f64::from(other.0))
398 }
399}
400
401impl Add for BuildingLevel {
402 type Output = Self;
403
404 fn add(self, rhs: Self) -> Self {
405 Self(self.0.saturating_add(rhs.0))
406 }
407}
408
409impl Add<u8> for BuildingLevel {
410 type Output = Self;
411
412 fn add(self, rhs: u8) -> Self {
413 Self(self.0.saturating_add(rhs))
414 }
415}
416
417impl Add<i8> for BuildingLevel {
418 type Output = Self;
419
420 fn add(self, rhs: i8) -> Self {
421 Self(self.0.saturating_add_signed(rhs))
422 }
423}
424
425impl Add<BuildingLevelDiff> for BuildingLevel {
426 type Output = Self;
427
428 fn add(self, rhs: BuildingLevelDiff) -> Self {
429 self + rhs.0
430 }
431}
432
433impl AddAssign for BuildingLevel {
434 fn add_assign(&mut self, rhs: Self) {
435 *self = *self + rhs;
436 }
437}
438
439impl AddAssign<u8> for BuildingLevel {
440 fn add_assign(&mut self, rhs: u8) {
441 *self = *self + rhs;
442 }
443}
444
445impl AddAssign<i8> for BuildingLevel {
446 fn add_assign(&mut self, rhs: i8) {
447 *self = *self + rhs;
448 }
449}
450
451impl AddAssign<BuildingLevelDiff> for BuildingLevel {
452 fn add_assign(&mut self, rhs: BuildingLevelDiff) {
453 *self = *self + rhs;
454 }
455}
456
457impl Sub for BuildingLevel {
458 type Output = Self;
459
460 fn sub(self, rhs: Self) -> Self {
461 Self(self.0.saturating_sub(rhs.0))
462 }
463}
464
465impl Sub<u8> for BuildingLevel {
466 type Output = Self;
467
468 fn sub(self, rhs: u8) -> Self {
469 Self(self.0.saturating_sub(rhs))
470 }
471}
472
473impl Sub<i8> for BuildingLevel {
474 type Output = Self;
475
476 fn sub(self, rhs: i8) -> Self {
477 Self(self.0.saturating_sub_signed(rhs))
478 }
479}
480
481impl Sub<BuildingLevelDiff> for BuildingLevel {
482 type Output = Self;
483
484 fn sub(self, rhs: BuildingLevelDiff) -> Self {
485 self - rhs.0
486 }
487}
488
489impl SubAssign for BuildingLevel {
490 fn sub_assign(&mut self, rhs: Self) {
491 *self = *self - rhs;
492 }
493}
494
495impl SubAssign<u8> for BuildingLevel {
496 fn sub_assign(&mut self, rhs: u8) {
497 *self = *self - rhs;
498 }
499}
500
501impl SubAssign<i8> for BuildingLevel {
502 fn sub_assign(&mut self, rhs: i8) {
503 *self = *self - rhs;
504 }
505}
506
507impl SubAssign<BuildingLevelDiff> for BuildingLevel {
508 fn sub_assign(&mut self, rhs: BuildingLevelDiff) {
509 *self = *self - rhs;
510 }
511}
512
513impl Mul<f64> for BuildingLevel {
514 type Output = f64;
515
516 fn mul(self, rhs: f64) -> f64 {
517 f64::from(self.0) * rhs
518 }
519}
520
521impl Neg for BuildingLevel {
522 type Output = BuildingLevelDiff;
523
524 fn neg(self) -> BuildingLevelDiff {
525 BuildingLevelDiff::new(i8::from(self).neg())
526 }
527}
528
529#[derive(
530 Clone,
531 Copy,
532 Debug,
533 Default,
534 Deref,
535 derive_more::Display,
536 Into,
537 PartialEq,
538 Eq,
539 PartialOrd,
540 Ord,
541 Hash,
542 Deserialize,
543 Serialize,
544)]
545pub struct BuildingLevelDiff(i8);
546
547impl BuildingLevelDiff {
548 pub const ZERO: BuildingLevelDiff = BuildingLevelDiff(0);
549
550 #[inline]
551 pub const fn new(level_diff: i8) -> Self {
552 Self(level_diff)
553 }
554}
555
556impl From<f64> for BuildingLevelDiff {
557 fn from(mut value: f64) -> Self {
558 value = value.round();
559 debug_assert!(value.is_finite());
560 debug_assert!(value >= f64::from(i8::MIN));
561 debug_assert!(value <= f64::from(i8::MAX));
562 Self::new(value as i8)
563 }
564}
565
566impl PartialEq<i8> for BuildingLevelDiff {
567 fn eq(&self, other: &i8) -> bool {
568 self.0.eq(other)
569 }
570}
571
572impl PartialOrd<i8> for BuildingLevelDiff {
573 fn partial_cmp(&self, other: &i8) -> Option<cmp::Ordering> {
574 self.0.partial_cmp(other)
575 }
576}
577
578#[macro_export]
580macro_rules! lv {
581 ($level:expr) => {
582 const { $crate::infrastructure::building::BuildingLevel::new($level) }
583 };
584}
585
586#[macro_export]
588macro_rules! with_random_level {
589 ($building:ident) => {{ $crate::infrastructure::prelude::$building::with_random_level() }};
590 ($building:ident, $max:expr) => {{
591 $crate::infrastructure::prelude::$building::with_random_level_in()
592 .max($crate::lv!($max))
593 .call()
594 }};
595 ($building:ident, $min:expr, $max:expr) => {{
596 $crate::infrastructure::prelude::$building::with_random_level_in()
597 .min($crate::lv!($min))
598 .max($crate::lv!($max))
599 .call()
600 }};
601}