Skip to main content

nil_core/city/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4mod stability;
5
6use crate::continent::Coord;
7use crate::error::Result;
8use crate::infrastructure::Infrastructure;
9use crate::infrastructure::building::StorageId;
10use crate::infrastructure::stats::InfrastructureStats;
11use crate::infrastructure::storage::OverallStorageCapacity;
12use crate::npc::bot::BotId;
13use crate::npc::precursor::PrecursorId;
14use crate::player::PlayerId;
15use crate::ranking::score::Score;
16use crate::resources::Resources;
17use crate::resources::maintenance::Maintenance;
18use crate::ruler::Ruler;
19use bon::Builder;
20use derive_more::{Deref, DerefMut, From};
21use serde::{Deserialize, Serialize};
22use std::sync::Arc;
23
24pub use stability::Stability;
25
26#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
27#[serde(rename_all = "camelCase")]
28#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
29pub struct City {
30  #[builder(start_fn, into)]
31  coord: Coord,
32
33  #[builder(into)]
34  name: CityName,
35
36  #[builder(into)]
37  owner: Ruler,
38
39  #[builder(default)]
40  infrastructure: Infrastructure,
41
42  #[builder(default)]
43  stability: Stability,
44}
45
46impl City {
47  #[inline]
48  pub fn coord(&self) -> Coord {
49    self.coord
50  }
51
52  #[inline]
53  pub fn name(&self) -> &CityName {
54    &self.name
55  }
56
57  pub(crate) fn name_mut(&mut self) -> &mut CityName {
58    &mut self.name
59  }
60
61  #[inline]
62  pub fn owner(&self) -> &Ruler {
63    &self.owner
64  }
65
66  #[inline]
67  pub fn infrastructure(&self) -> &Infrastructure {
68    &self.infrastructure
69  }
70
71  #[inline]
72  pub fn infrastructure_mut(&mut self) -> &mut Infrastructure {
73    &mut self.infrastructure
74  }
75
76  #[inline]
77  pub fn stability(&self) -> Stability {
78    self.stability
79  }
80
81  pub(crate) fn stability_mut(&mut self) -> &mut Stability {
82    &mut self.stability
83  }
84
85  #[inline]
86  pub fn player(&self) -> Option<PlayerId> {
87    self.owner().player().cloned()
88  }
89
90  /// Checks whether the city belongs to a player.
91  #[inline]
92  pub fn is_owned_by_player(&self) -> bool {
93    self.owner.player().is_some()
94  }
95
96  pub fn is_owned_by_player_and<F>(&self, f: F) -> bool
97  where
98    F: FnOnce(&PlayerId) -> bool,
99  {
100    self.owner.player().is_some_and(f)
101  }
102
103  /// Checks whether the city belongs to a bot.
104  #[inline]
105  pub fn is_owned_by_bot(&self) -> bool {
106    self.owner.bot().is_some()
107  }
108
109  pub fn is_owned_by_bot_and<F>(&self, f: F) -> bool
110  where
111    F: FnOnce(&BotId) -> bool,
112  {
113    self.owner.bot().is_some_and(f)
114  }
115
116  /// Checks whether the city belongs to a precursor.
117  #[inline]
118  pub fn is_owned_by_precursor(&self) -> bool {
119    self.owner.precursor().is_some()
120  }
121
122  pub fn is_owned_by_precursor_and<F>(&self, f: F) -> bool
123  where
124    F: FnOnce(PrecursorId) -> bool,
125  {
126    self.owner.precursor().is_some_and(f)
127  }
128
129  #[inline]
130  pub fn score(&self, stats: &InfrastructureStats) -> Result<Score> {
131    self.infrastructure.score(stats)
132  }
133
134  /// Determines the amount of resources generated by the city's mines
135  /// while applying all relevant modifiers, such as city stability.
136  pub fn round_production(&self, stats: &InfrastructureStats) -> Result<Resources> {
137    let mut resources = self
138      .infrastructure
139      .round_base_production(stats)?;
140
141    resources.food *= self.stability;
142    resources.iron *= self.stability;
143    resources.stone *= self.stability;
144    resources.wood *= self.stability;
145
146    Ok(resources)
147  }
148
149  /// Determines the maintenance tax required for the city buildings.
150  pub fn maintenance(&self, stats: &InfrastructureStats) -> Result<Maintenance> {
151    self.infrastructure.base_maintenance(stats)
152  }
153
154  pub fn storage_capacity(&self, stats: &InfrastructureStats) -> Result<OverallStorageCapacity> {
155    let silo_stats = stats.storage(StorageId::Silo)?;
156    let warehouse_stats = stats.storage(StorageId::Warehouse)?;
157
158    Ok(OverallStorageCapacity {
159      silo: self
160        .infrastructure
161        .storage(StorageId::Silo)
162        .capacity(silo_stats)?,
163      warehouse: self
164        .infrastructure
165        .storage(StorageId::Warehouse)
166        .capacity(warehouse_stats)?,
167    })
168  }
169}
170
171impl From<&City> for Ruler {
172  fn from(city: &City) -> Self {
173    city.owner.clone()
174  }
175}
176
177#[derive(Clone, Debug, Deref, DerefMut, From, Deserialize, Serialize)]
178#[from(String, &str)]
179#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
180pub struct CityName(String);
181
182impl CityName {
183  pub fn new(name: impl AsRef<str>) -> Self {
184    Self(name.as_ref().to_owned())
185  }
186}
187
188/// Public data about a city, to which any player can have access.
189#[derive(Clone, Debug, Deserialize, Serialize)]
190#[serde(rename_all = "camelCase")]
191#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
192pub struct PublicCity {
193  coord: Coord,
194  name: Arc<str>,
195  owner: Ruler,
196}
197
198impl From<&City> for PublicCity {
199  fn from(city: &City) -> Self {
200    Self {
201      coord: city.coord,
202      name: Arc::from(city.name.as_str()),
203      owner: city.owner.clone(),
204    }
205  }
206}
207
208#[derive(Builder, Clone, Debug, Default, Deserialize, Serialize)]
209#[serde(default, rename_all = "camelCase")]
210#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
211#[cfg_attr(feature = "typescript", ts(optional_fields))]
212pub struct CitySearch {
213  #[builder(default, with = FromIterator::from_iter)]
214  #[cfg_attr(feature = "typescript", ts(as = "Option<Vec<Coord>>"))]
215  pub coord: Vec<Coord>,
216
217  #[builder(default, with = FromIterator::from_iter)]
218  #[cfg_attr(feature = "typescript", ts(as = "Option<Vec<CityName>>"))]
219  pub name: Vec<CityName>,
220}
221
222impl From<Coord> for CitySearch {
223  fn from(coord: Coord) -> Self {
224    Self::from_iter([coord])
225  }
226}
227
228impl From<CityName> for CitySearch {
229  fn from(name: CityName) -> Self {
230    Self::from_iter([name])
231  }
232}
233
234impl FromIterator<Coord> for CitySearch {
235  fn from_iter<T>(iter: T) -> Self
236  where
237    T: IntoIterator<Item = Coord>,
238  {
239    Self::builder().coord(iter).build()
240  }
241}
242
243impl FromIterator<CityName> for CitySearch {
244  fn from_iter<T>(iter: T) -> Self
245  where
246    T: IntoIterator<Item = CityName>,
247  {
248    Self::builder().name(iter).build()
249  }
250}