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