Skip to main content

nil_core/infrastructure/building/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4pub 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  /// Checks whether the building is enabled.
33  fn is_enabled(&self) -> bool;
34  /// Enables or disables the building.
35  fn toggle(&mut self, enabled: bool);
36
37  /// Current building level.
38  fn level(&self) -> BuildingLevel;
39  /// Minimum building level.
40  fn min_level(&self) -> BuildingLevel;
41  /// Maximum building level.
42  fn max_level(&self) -> BuildingLevel;
43  /// Sets the building's level while ensuring it remains within the level limit.
44  fn set_level(&mut self, level: BuildingLevel);
45
46  /// Sets the building to its **minimum** level.
47  fn set_min_level(&mut self) {
48    self.set_level(self.min_level());
49  }
50
51  /// Sets the building to its **maximum** level.
52  fn set_max_level(&mut self) {
53    self.set_level(self.max_level());
54  }
55
56  /// Increases the building level by one, if possible.
57  fn increase_level(&mut self) {
58    self.increase_level_by(1);
59  }
60
61  /// Increases the level of the building by a certain amount, if possible.
62  fn increase_level_by(&mut self, amount: u8);
63
64  /// Decreases the building level by one, if possible.
65  fn decrease_level(&mut self) {
66    self.decrease_level_by(1);
67  }
68
69  /// Decreases the level of the building by a certain amount, if possible.
70  fn decrease_level_by(&mut self, amount: u8);
71
72  /// Checks whether the building is at its minimum level.
73  fn is_min_level(&self) -> bool {
74    self.level() == self.min_level()
75  }
76
77  /// Checks whether the building is at its maximum level.
78  fn is_max_level(&self) -> bool {
79    self.level() >= self.max_level()
80  }
81
82  /// Total cost for the **minimum** level of the building.
83  fn min_cost(&self) -> Cost;
84  /// Total cost for the **maximum** level of the building.
85  fn max_cost(&self) -> Cost;
86  /// Percentage of the total cost related to wood.
87  fn wood_ratio(&self) -> ResourceRatio;
88  /// Percentage of the total cost related to stone.
89  fn stone_ratio(&self) -> ResourceRatio;
90  /// Percentage of the total cost related to iron.
91  fn iron_ratio(&self) -> ResourceRatio;
92
93  /// Building maintenance tax at its current level.
94  fn maintenance(&self, stats: &BuildingStatsTable) -> Result<Maintenance>;
95  /// Proportion of the base cost used as a maintenance tax.
96  fn maintenance_ratio(&self) -> MaintenanceRatio;
97
98  /// Workforce required for the **minimum** level of the building.
99  fn min_workforce(&self) -> Workforce;
100  /// Workforce required for the **maximum** level of the building.
101  fn max_workforce(&self) -> Workforce;
102
103  // Current score.
104  fn score(&self, stats: &BuildingStatsTable) -> Result<Score>;
105  // Building score at its **minimum** level.
106  fn min_score(&self) -> Score;
107  // Building score at its **maximum** level.
108  fn max_score(&self) -> Score;
109
110  /// Levels required to construct the building.
111  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/// Information about a building at a given level.
193#[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/// Shorthand for [`BuildingLevel::new`].
579#[macro_export]
580macro_rules! lv {
581  ($level:expr) => {
582    const { $crate::infrastructure::building::BuildingLevel::new($level) }
583  };
584}
585
586/// Creates a building with a random level.
587#[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}