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