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