Skip to main content

nil_core/resources/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4pub mod cost;
5pub mod diff;
6pub mod gold;
7pub mod influence;
8pub mod maintenance;
9pub mod prelude;
10pub mod workforce;
11
12use crate::city::stability::Stability;
13use crate::error::{Error, Result};
14use crate::infrastructure::mine::MineProduction;
15use crate::infrastructure::storage::{OverallStorageCapacity, StorageCapacity};
16use crate::market::fee::MarketFee;
17use bon::Builder;
18use derive_more::Display;
19use diff::{FoodDiff, IronDiff, ResourcesDiff, StoneDiff, WoodDiff};
20use nil_num::impl_mul_ceil;
21use nil_num::mul_ceil::MulCeil;
22use nil_util::{ConstDeref, F64Math};
23use serde::{Deserialize, Serialize};
24use std::cmp::Ordering;
25use std::num::NonZeroU32;
26use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
27
28/// Basic resources, such as food.
29#[derive(Builder, Copy, Debug, Deserialize, Serialize)]
30#[derive_const(Clone, PartialEq, Eq)]
31#[serde(default, rename_all = "camelCase")]
32#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
33pub struct Resources {
34  #[builder(default)]
35  pub food: Food,
36
37  #[builder(default)]
38  pub iron: Iron,
39
40  #[builder(default)]
41  pub stone: Stone,
42
43  #[builder(default)]
44  pub wood: Wood,
45}
46
47impl Resources {
48  /// Minimum possible amount of resources.
49  pub const MIN: Self = Self {
50    food: Food::MIN,
51    iron: Iron::MIN,
52    stone: Stone::MIN,
53    wood: Wood::MIN,
54  };
55
56  /// Maximum possible amount of resources.
57  pub const MAX: Self = Self {
58    food: Food::MAX,
59    iron: Iron::MAX,
60    stone: Stone::MAX,
61    wood: Wood::MAX,
62  };
63
64  /// Default amount of resources for a player.
65  pub const PLAYER: Self = Self::splat(800);
66
67  /// Default amount of resources for a bot.
68  pub const BOT: Self = Self::splat(2500);
69
70  /// Default amount of resources for a precursor.
71  pub const PRECURSOR: Self = Self::splat(5_000_000);
72
73  #[inline]
74  #[must_use]
75  pub const fn new() -> Self {
76    Self::MIN
77  }
78
79  #[must_use]
80  pub const fn splat(value: u32) -> Self {
81    Self {
82      food: Food::new(value),
83      iron: Iron::new(value),
84      stone: Stone::new(value),
85      wood: Wood::new(value),
86    }
87  }
88
89  #[inline]
90  #[must_use]
91  pub const fn with_food(self, food: Food) -> Self {
92    Self { food, ..self }
93  }
94
95  #[inline]
96  #[must_use]
97  pub const fn with_iron(self, iron: Iron) -> Self {
98    Self { iron, ..self }
99  }
100
101  #[inline]
102  #[must_use]
103  pub const fn with_stone(self, stone: Stone) -> Self {
104    Self { stone, ..self }
105  }
106
107  #[inline]
108  #[must_use]
109  pub const fn with_wood(self, wood: Wood) -> Self {
110    Self { wood, ..self }
111  }
112
113  #[must_use]
114  pub const fn silo(&self) -> Self {
115    Self {
116      food: self.food,
117      iron: Iron::MIN,
118      stone: Stone::MIN,
119      wood: Wood::MIN,
120    }
121  }
122
123  #[must_use]
124  pub const fn warehouse(&self) -> Self {
125    Self {
126      food: Food::MIN,
127      iron: self.iron,
128      stone: self.stone,
129      wood: self.wood,
130    }
131  }
132
133  /// Adds resources, respecting the storage capacity.
134  pub const fn add_within_capacity(
135    &mut self,
136    diff: ResourcesDiff,
137    capacity: OverallStorageCapacity,
138  ) {
139    macro_rules! add {
140      ($($resource:ident => $storage:ident),+ $(,)?) => {
141        $(
142          let resource = diff.$resource;
143          let storage = capacity.$storage;
144          self.$resource.add_within_capacity(resource, storage);
145        )+
146      };
147    }
148
149    add!(food => silo, iron => warehouse, stone => warehouse, wood => warehouse);
150  }
151
152  pub fn set(&mut self, resources: impl Into<Resources>) {
153    *self = resources.into();
154  }
155
156  /// Checked resource subtraction.
157  /// Returns `None` if there are not enough resources available.
158  pub const fn checked_sub(&self, rhs: Resources) -> Option<Self> {
159    Some(Self {
160      food: self.food.checked_sub(rhs.food)?,
161      iron: self.iron.checked_sub(rhs.iron)?,
162      stone: self.stone.checked_sub(rhs.stone)?,
163      wood: self.wood.checked_sub(rhs.wood)?,
164    })
165  }
166
167  pub const fn sum(&self) -> u32 {
168    0u32
169      .saturating_add(self.food.0)
170      .saturating_add(self.iron.0)
171      .saturating_add(self.stone.0)
172      .saturating_add(self.wood.0)
173  }
174
175  #[inline]
176  pub const fn sum_silo(&self) -> u32 {
177    self.silo().sum()
178  }
179
180  #[inline]
181  pub const fn sum_warehouse(&self) -> u32 {
182    self.warehouse().sum()
183  }
184
185  #[inline]
186  pub const fn is_empty(&self) -> bool {
187    self.sum() == 0
188  }
189}
190
191const impl Default for Resources {
192  fn default() -> Self {
193    Self::new()
194  }
195}
196
197const impl From<u32> for Resources {
198  fn from(value: u32) -> Self {
199    Self::splat(value)
200  }
201}
202
203const impl Add for Resources {
204  type Output = Self;
205
206  fn add(self, rhs: Self) -> Self {
207    Self {
208      food: self.food + rhs.food,
209      iron: self.iron + rhs.iron,
210      stone: self.stone + rhs.stone,
211      wood: self.wood + rhs.wood,
212    }
213  }
214}
215
216const impl AddAssign for Resources {
217  fn add_assign(&mut self, rhs: Self) {
218    *self = Self {
219      food: self.food + rhs.food,
220      iron: self.iron + rhs.iron,
221      stone: self.stone + rhs.stone,
222      wood: self.wood + rhs.wood,
223    };
224  }
225}
226
227const impl Sub for Resources {
228  type Output = Self;
229
230  fn sub(self, rhs: Self) -> Self {
231    Self {
232      food: self.food - rhs.food,
233      iron: self.iron - rhs.iron,
234      stone: self.stone - rhs.stone,
235      wood: self.wood - rhs.wood,
236    }
237  }
238}
239
240const impl SubAssign for Resources {
241  fn sub_assign(&mut self, rhs: Self) {
242    *self = Self {
243      food: self.food - rhs.food,
244      iron: self.iron - rhs.iron,
245      stone: self.stone - rhs.stone,
246      wood: self.wood - rhs.wood,
247    };
248  }
249}
250
251const impl Mul<u32> for Resources {
252  type Output = Resources;
253
254  fn mul(self, rhs: u32) -> Self::Output {
255    Resources {
256      food: self.food * rhs,
257      iron: self.iron * rhs,
258      stone: self.stone * rhs,
259      wood: self.wood * rhs,
260    }
261  }
262}
263
264const impl Mul<NonZeroU32> for Resources {
265  type Output = Resources;
266
267  fn mul(self, rhs: NonZeroU32) -> Self::Output {
268    self * rhs.get()
269  }
270}
271
272const impl Mul<MarketFee> for Resources {
273  type Output = Resources;
274
275  fn mul(self, rhs: MarketFee) -> Self::Output {
276    Self {
277      food: self.food * rhs,
278      iron: self.iron * rhs,
279      stone: self.stone * rhs,
280      wood: self.wood * rhs,
281    }
282  }
283}
284
285macro_rules! decl_resource {
286  ($($resource:ident),+ $(,)?) => {
287    paste::paste! {
288      $(
289        #[derive(Copy, Debug, Display, Deserialize, Serialize, ConstDeref, F64Math)]
290        #[derive_const(Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
291        #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
292        pub struct $resource(u32);
293
294        impl $resource {
295          pub const MIN: Self = Self::new(0);
296          pub const MAX: Self = Self::new(u32::MAX);
297
298          #[inline]
299          pub const fn new(value: u32) -> Self {
300            Self(value)
301          }
302
303          #[inline]
304          pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
305            self.0.checked_sub(rhs.0).map(Self::new)
306          }
307
308          pub const fn add_within_capacity(
309            &mut self,
310            diff: [<$resource Diff>],
311            capacity: StorageCapacity
312          ) {
313            if diff < 0i32 {
314              *self += diff;
315            } else if self.0 < *capacity {
316              let capacity = $resource::from(capacity);
317              *self = (*self + diff).min(capacity);
318            }
319          }
320        }
321
322        const impl From<u32> for $resource {
323          fn from(value: u32) -> Self {
324            Self::new(value)
325          }
326        }
327
328        const impl From<$resource> for u32 {
329          fn from(value: $resource) -> Self {
330            value.0
331          }
332        }
333
334        const impl From<f64> for $resource {
335          fn from(value: f64) -> Self {
336            debug_assert!(value.is_finite());
337            debug_assert!(value >= 0.0);
338            Self(value.trunc() as u32)
339          }
340        }
341
342        const impl From<$resource> for f64 {
343          fn from(value: $resource) -> Self {
344            f64::from(value.0)
345          }
346        }
347
348        const impl From<MineProduction> for $resource {
349          fn from(value: MineProduction) -> Self {
350            Self(*value)
351          }
352        }
353
354        const impl From<StorageCapacity> for $resource {
355          fn from(value: StorageCapacity) -> Self {
356            Self(*value)
357          }
358        }
359
360        const impl From<$resource> for Resources {
361          fn from(value: $resource) -> Self {
362            let mut resources = Resources::new();
363            resources.[<$resource:snake>] = value;
364            resources
365          }
366        }
367
368        impl TryFrom<$resource> for i32 {
369          type Error = $crate::error::Error;
370
371          fn try_from(value: $resource) -> Result<Self> {
372            match i32::try_from(value.0) {
373              Ok(value) => Ok(value),
374              Err(_) =>  {
375                let resources = Resources::from(value);
376                Err(Error::TooManyResources(resources))
377              },
378            }
379          }
380        }
381
382        const impl PartialEq<u32> for $resource {
383          fn eq(&self, other: &u32) -> bool {
384            self.0.eq(other)
385          }
386        }
387
388        const impl PartialOrd<u32> for $resource {
389          fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
390            self.0.partial_cmp(other)
391          }
392        }
393
394        const impl Add for $resource {
395          type Output = Self;
396
397          fn add(self, rhs: Self) -> Self {
398            Self(self.0.saturating_add(rhs.0))
399          }
400        }
401
402        const impl Add<u32> for $resource {
403          type Output = Self;
404
405          fn add(self, rhs: u32) -> Self {
406            Self(self.0.saturating_add(rhs))
407          }
408        }
409
410        const impl AddAssign for $resource {
411          fn add_assign(&mut self, rhs: Self) {
412            *self = *self + rhs;
413          }
414        }
415
416        const impl Sub for $resource {
417          type Output = Self;
418
419          fn sub(self, rhs: Self) -> Self {
420            Self(self.0.saturating_sub(rhs.0))
421          }
422        }
423
424        const impl Sub<u32> for $resource {
425          type Output = Self;
426
427          fn sub(self, rhs: u32) -> Self {
428            Self(self.0.saturating_sub(rhs))
429          }
430        }
431
432        const impl SubAssign for $resource {
433          fn sub_assign(&mut self, rhs: Self) {
434            *self = *self - rhs;
435          }
436        }
437
438        const impl Mul<u32> for $resource {
439          type Output = Self;
440
441          fn mul(self, rhs: u32) -> Self::Output {
442            Self(self.0.saturating_mul(rhs))
443          }
444        }
445
446        const impl Mul<NonZeroU32> for $resource {
447          type Output = Self;
448
449          fn mul(self, rhs: NonZeroU32) -> Self::Output {
450            self * rhs.get()
451          }
452        }
453
454        const impl Mul<MarketFee> for $resource {
455          type Output = Self;
456
457          fn mul(self, rhs: MarketFee) -> Self::Output {
458            Self::from(self.mul_ceil(*rhs))
459          }
460        }
461
462        const impl Mul<Stability> for $resource {
463          type Output = $resource;
464
465          fn mul(self, rhs: Stability) -> Self::Output {
466            Self::from(self.mul_ceil(*rhs))
467          }
468        }
469
470        const impl MulAssign<u32> for $resource {
471          fn mul_assign(&mut self, rhs: u32) {
472            *self = *self * rhs;
473          }
474        }
475
476        const impl MulAssign<MarketFee> for $resource {
477          fn mul_assign(&mut self, rhs: MarketFee) {
478            *self = *self * rhs;
479          }
480        }
481
482        const impl MulAssign<Stability> for $resource {
483          fn mul_assign(&mut self, rhs: Stability) {
484            *self = *self * rhs;
485          }
486        }
487
488        impl_mul_ceil!($resource);
489      )+
490    }
491  };
492}
493
494decl_resource!(Food, Iron, Stone, Wood);