Skip to main content

nil_core/infrastructure/building/impl/
wall.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4use crate::check_total_resource_ratio;
5use crate::error::{Error, Result};
6use crate::infrastructure::building::BuildingId;
7use crate::infrastructure::building::level::BuildingLevel;
8use crate::infrastructure::requirements::InfrastructureRequirements;
9use crate::ranking::score::Score;
10use crate::resources::cost::{Cost, ResourceRatio};
11use crate::resources::maintenance::MaintenanceRatio;
12use crate::resources::workforce::Workforce;
13use nil_core_macros::Building;
14use nil_num::growth::growth;
15use nil_util::{ConstDeref, F64Math};
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18
19#[derive(Building, Clone, Debug, Deserialize, Serialize)]
20#[serde(rename_all = "camelCase")]
21#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
22pub struct Wall {
23  level: BuildingLevel,
24  enabled: bool,
25}
26
27impl Wall {
28  pub const ID: BuildingId = BuildingId::Wall;
29
30  pub const MIN_LEVEL: BuildingLevel = BuildingLevel::ZERO;
31  pub const MAX_LEVEL: BuildingLevel = BuildingLevel::new(20);
32
33  pub const MIN_COST: Cost = Cost::new(1_200);
34  pub const MAX_COST: Cost = Cost::new(50_000);
35
36  pub const FOOD_RATIO: ResourceRatio = ResourceRatio::new(0.0);
37  pub const IRON_RATIO: ResourceRatio = ResourceRatio::new(0.2);
38  pub const STONE_RATIO: ResourceRatio = ResourceRatio::new(0.5);
39  pub const WOOD_RATIO: ResourceRatio = ResourceRatio::new(0.3);
40
41  pub const MAINTENANCE_RATIO: MaintenanceRatio = MaintenanceRatio::new(0.005);
42
43  pub const MIN_WORKFORCE: Workforce = Workforce::new(2);
44  pub const MAX_WORKFORCE: Workforce = Workforce::new(200);
45
46  pub const MIN_DEFENSE: WallDefense = WallDefense::new(500);
47  pub const MAX_DEFENSE: WallDefense = WallDefense::new(10_000);
48
49  pub const MIN_DEFENSE_BONUS: WallDefenseBonus = WallDefenseBonus::new(5.0);
50  pub const MAX_DEFENSE_BONUS: WallDefenseBonus = WallDefenseBonus::new(110.0);
51
52  pub const MIN_SCORE: Score = Score::new(8);
53  pub const MAX_SCORE: Score = Score::new(256);
54
55  pub const INFRASTRUCTURE_REQUIREMENTS: InfrastructureRequirements =
56    InfrastructureRequirements::builder()
57      .prefecture(BuildingLevel::new(3))
58      .academy(BuildingLevel::new(1))
59      .build();
60}
61
62impl const Default for Wall {
63  fn default() -> Self {
64    Self {
65      level: BuildingLevel::ZERO,
66      enabled: true,
67    }
68  }
69}
70
71check_total_resource_ratio!(
72  Wall::FOOD_RATIO,
73  Wall::IRON_RATIO,
74  Wall::STONE_RATIO,
75  Wall::WOOD_RATIO
76);
77
78#[derive(Clone, Debug, Deserialize, Serialize)]
79#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
80pub struct WallStatsTable(HashMap<BuildingLevel, WallStats>);
81
82impl WallStatsTable {
83  pub fn new() -> Self {
84    let max_level = Wall::MAX_LEVEL;
85    let mut table = HashMap::with_capacity((*max_level).into());
86
87    let mut defense = f64::from(Wall::MIN_DEFENSE);
88    let defense_growth = growth()
89      .floor(defense)
90      .ceil(f64::from(Wall::MAX_DEFENSE))
91      .max_level(max_level)
92      .call();
93
94    let mut defense_percent = f64::from(Wall::MIN_DEFENSE_BONUS);
95    let defense_percent_growth = growth()
96      .floor(defense_percent)
97      .ceil(f64::from(Wall::MAX_DEFENSE_BONUS))
98      .max_level(max_level)
99      .call();
100
101    for level in 1..=u8::from(max_level) {
102      let level = BuildingLevel::new(level);
103
104      table.insert(
105        level,
106        WallStats {
107          level,
108          defense: WallDefense::from(defense.round()),
109          defense_percent: WallDefenseBonus::from(defense_percent.round()),
110        },
111      );
112
113      defense += defense * defense_growth;
114      defense_percent += defense_percent * defense_percent_growth;
115    }
116
117    table.shrink_to_fit();
118
119    Self(table)
120  }
121
122  #[inline]
123  pub fn get(&self, level: BuildingLevel) -> Result<&WallStats> {
124    self
125      .0
126      .get(&level)
127      .ok_or(Error::WallStatsNotFoundForLevel(level))
128  }
129}
130
131impl Default for WallStatsTable {
132  fn default() -> Self {
133    Self::new()
134  }
135}
136
137#[derive(Debug, Deserialize, Serialize)]
138#[derive_const(Clone)]
139#[serde(rename_all = "camelCase")]
140#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
141pub struct WallStats {
142  pub level: BuildingLevel,
143  pub defense: WallDefense,
144  pub defense_percent: WallDefenseBonus,
145}
146
147#[derive(Copy, Debug, Deserialize, Serialize, ConstDeref, F64Math)]
148#[derive_const(Clone, PartialEq, Eq, PartialOrd, Ord)]
149#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
150pub struct WallDefense(u32);
151
152impl WallDefense {
153  #[inline]
154  pub const fn new(value: u32) -> Self {
155    Self(value)
156  }
157}
158
159impl const From<WallDefense> for f64 {
160  fn from(value: WallDefense) -> Self {
161    f64::from(value.0)
162  }
163}
164
165impl const From<f64> for WallDefense {
166  fn from(value: f64) -> Self {
167    debug_assert!(value >= 0.0);
168    debug_assert!(value.is_finite());
169    Self::new(value.trunc() as u32)
170  }
171}
172
173#[derive(Copy, Debug, Deserialize, Serialize, ConstDeref, F64Math)]
174#[derive_const(Clone, PartialEq, PartialOrd)]
175#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
176pub struct WallDefenseBonus(f64);
177
178impl WallDefenseBonus {
179  pub const MIN: WallDefenseBonus = WallDefenseBonus(0.0);
180
181  #[inline]
182  pub const fn new(value: f64) -> Self {
183    Self(value.max(Self::MIN.0))
184  }
185}
186
187impl const From<WallDefenseBonus> for f64 {
188  fn from(value: WallDefenseBonus) -> Self {
189    value.0
190  }
191}
192
193impl const From<f64> for WallDefenseBonus {
194  fn from(value: f64) -> Self {
195    Self::new(value)
196  }
197}