Skip to main content

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