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};
29use strum::{EnumIs, EnumIter};
30use subenum::subenum;
31
32/// Basic resources, such as food.
33#[derive(Builder, Copy, Debug, Deserialize, Serialize)]
34#[derive_const(Clone, PartialEq, Eq)]
35#[serde(default, rename_all = "camelCase")]
36#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
37pub struct Resources {
38  #[builder(default)]
39  pub food: Food,
40
41  #[builder(default)]
42  pub iron: Iron,
43
44  #[builder(default)]
45  pub stone: Stone,
46
47  #[builder(default)]
48  pub wood: Wood,
49}
50
51impl Resources {
52  /// Minimum possible amount of resources.
53  pub const MIN: Self = Self {
54    food: Food::MIN,
55    iron: Iron::MIN,
56    stone: Stone::MIN,
57    wood: Wood::MIN,
58  };
59
60  /// Maximum possible amount of resources.
61  pub const MAX: Self = Self {
62    food: Food::MAX,
63    iron: Iron::MAX,
64    stone: Stone::MAX,
65    wood: Wood::MAX,
66  };
67
68  /// Default amount of resources for a player.
69  pub const PLAYER: Self = Self::splat(800);
70
71  /// Default amount of resources for a bot.
72  pub const BOT: Self = Self::splat(2500);
73
74  /// Default amount of resources for a precursor.
75  pub const PRECURSOR: Self = Self::splat(5_000_000);
76
77  #[inline]
78  #[must_use]
79  pub const fn new() -> Self {
80    Self::MIN
81  }
82
83  #[must_use]
84  pub const fn splat(value: u32) -> Self {
85    Self {
86      food: Food::new(value),
87      iron: Iron::new(value),
88      stone: Stone::new(value),
89      wood: Wood::new(value),
90    }
91  }
92
93  pub fn with_resource<T>(resource: &T) -> Self
94  where
95    T: Resource,
96  {
97    match resource.id() {
98      ResourceId::Food => Self::with_food(Food::new(resource.as_u32())),
99      ResourceId::Iron => Self::with_iron(Iron::new(resource.as_u32())),
100      ResourceId::Stone => Self::with_stone(Stone::new(resource.as_u32())),
101      ResourceId::Wood => Self::with_wood(Wood::new(resource.as_u32())),
102    }
103  }
104
105  #[inline]
106  #[must_use]
107  pub const fn with_food(food: Food) -> Self {
108    Self { food, ..Self::default() }
109  }
110
111  #[inline]
112  #[must_use]
113  pub const fn with_iron(iron: Iron) -> Self {
114    Self { iron, ..Self::default() }
115  }
116
117  #[inline]
118  #[must_use]
119  pub const fn with_stone(stone: Stone) -> Self {
120    Self { stone, ..Self::default() }
121  }
122
123  #[inline]
124  #[must_use]
125  pub const fn with_wood(wood: Wood) -> Self {
126    Self { wood, ..Self::default() }
127  }
128
129  #[must_use]
130  pub const fn silo(&self) -> Self {
131    Self {
132      food: self.food,
133      iron: Iron::MIN,
134      stone: Stone::MIN,
135      wood: Wood::MIN,
136    }
137  }
138
139  #[must_use]
140  pub const fn warehouse(&self) -> Self {
141    Self {
142      food: Food::MIN,
143      iron: self.iron,
144      stone: self.stone,
145      wood: self.wood,
146    }
147  }
148
149  #[inline]
150  pub fn is_empty(&self) -> bool {
151    self.sum() == 0
152  }
153
154  pub fn replace(&mut self, resources: impl Into<Resources>) {
155    *self = resources.into();
156  }
157
158  pub const fn get(&self, id: ResourceId) -> &dyn Resource {
159    match id {
160      ResourceId::Food => self.food.as_dyn(),
161      ResourceId::Iron => self.iron.as_dyn(),
162      ResourceId::Stone => self.stone.as_dyn(),
163      ResourceId::Wood => self.wood.as_dyn(),
164    }
165  }
166
167  pub const fn get_mut(&mut self, id: ResourceId) -> &mut dyn Resource {
168    match id {
169      ResourceId::Food => &mut self.food,
170      ResourceId::Iron => &mut self.iron,
171      ResourceId::Stone => &mut self.stone,
172      ResourceId::Wood => &mut self.wood,
173    }
174  }
175
176  #[inline]
177  pub fn set(&mut self, id: ResourceId, value: u32) {
178    self.get_mut(id).set(value);
179  }
180
181  /// Adds resources, respecting the storage capacity.
182  pub const fn add_within_capacity(
183    &mut self,
184    diff: ResourcesDiff,
185    capacity: OverallStorageCapacity,
186  ) {
187    macro_rules! add {
188      ($($resource:ident => $storage:ident),+ $(,)?) => {
189        $(
190          let resource = diff.$resource;
191          let storage = capacity.$storage;
192          self.$resource.add_within_capacity(resource, storage);
193        )+
194      };
195    }
196
197    add!(food => silo, iron => warehouse, stone => warehouse, wood => warehouse);
198  }
199
200  /// Checked resource subtraction.
201  /// Returns `None` if there are not enough resources available.
202  pub const fn checked_sub(&self, rhs: Resources) -> Option<Self> {
203    Some(Self {
204      food: self.food.checked_sub(rhs.food)?,
205      iron: self.iron.checked_sub(rhs.iron)?,
206      stone: self.stone.checked_sub(rhs.stone)?,
207      wood: self.wood.checked_sub(rhs.wood)?,
208    })
209  }
210
211  pub gen fn iter(&self) -> &dyn Resource {
212    let Self { food, iron, stone, wood } = self;
213    yield food.as_dyn();
214    yield iron.as_dyn();
215    yield stone.as_dyn();
216    yield wood.as_dyn();
217  }
218
219  pub gen fn iter_mut(&mut self) -> &mut dyn Resource {
220    let Self { food, iron, stone, wood } = self;
221    yield food.as_dyn_mut();
222    yield iron.as_dyn_mut();
223    yield stone.as_dyn_mut();
224    yield wood.as_dyn_mut();
225  }
226
227  /// Returns the total amount of resources, ignoring their type.
228  #[inline]
229  pub fn sum(&self) -> u32 {
230    self.iter().map(Resource::as_u32).sum()
231  }
232
233  /// Returns the total amount of resources in the silo, ignoring their type.
234  #[inline]
235  pub fn sum_silo(&self) -> u32 {
236    self.silo().sum()
237  }
238
239  /// Returns the total amount of resources in the warehouse, ignoring their type.
240  #[inline]
241  pub fn sum_warehouse(&self) -> u32 {
242    self.warehouse().sum()
243  }
244}
245
246const impl Default for Resources {
247  fn default() -> Self {
248    Self::new()
249  }
250}
251
252const impl From<u32> for Resources {
253  fn from(value: u32) -> Self {
254    Self::splat(value)
255  }
256}
257
258const impl Add for Resources {
259  type Output = Self;
260
261  fn add(self, rhs: Self) -> Self {
262    Self {
263      food: self.food + rhs.food,
264      iron: self.iron + rhs.iron,
265      stone: self.stone + rhs.stone,
266      wood: self.wood + rhs.wood,
267    }
268  }
269}
270
271const impl AddAssign for Resources {
272  fn add_assign(&mut self, rhs: Self) {
273    *self = Self {
274      food: self.food + rhs.food,
275      iron: self.iron + rhs.iron,
276      stone: self.stone + rhs.stone,
277      wood: self.wood + rhs.wood,
278    };
279  }
280}
281
282const impl Sub for Resources {
283  type Output = Self;
284
285  fn sub(self, rhs: Self) -> Self {
286    Self {
287      food: self.food - rhs.food,
288      iron: self.iron - rhs.iron,
289      stone: self.stone - rhs.stone,
290      wood: self.wood - rhs.wood,
291    }
292  }
293}
294
295const impl SubAssign for Resources {
296  fn sub_assign(&mut self, rhs: Self) {
297    *self = Self {
298      food: self.food - rhs.food,
299      iron: self.iron - rhs.iron,
300      stone: self.stone - rhs.stone,
301      wood: self.wood - rhs.wood,
302    };
303  }
304}
305
306const impl Mul<u32> for Resources {
307  type Output = Resources;
308
309  fn mul(self, rhs: u32) -> Self::Output {
310    Resources {
311      food: self.food * rhs,
312      iron: self.iron * rhs,
313      stone: self.stone * rhs,
314      wood: self.wood * rhs,
315    }
316  }
317}
318
319const impl Mul<NonZeroU32> for Resources {
320  type Output = Resources;
321
322  fn mul(self, rhs: NonZeroU32) -> Self::Output {
323    self * rhs.get()
324  }
325}
326
327const impl Mul<MarketFee> for Resources {
328  type Output = Resources;
329
330  fn mul(self, rhs: MarketFee) -> Self::Output {
331    Self {
332      food: self.food * rhs,
333      iron: self.iron * rhs,
334      stone: self.stone * rhs,
335      wood: self.wood * rhs,
336    }
337  }
338}
339
340impl Sum for Resources {
341  fn sum<I>(iter: I) -> Self
342  where
343    I: Iterator<Item = Self>,
344  {
345    iter.fold(Self::default(), |acc, resources| acc + resources)
346  }
347}
348
349impl Sum<Resources> for u32 {
350  fn sum<I>(iter: I) -> Self
351  where
352    I: Iterator<Item = Resources>,
353  {
354    iter.fold(0u32, |acc, resources| acc.saturating_add(resources.sum()))
355  }
356}
357
358macro_rules! decl_resource {
359  ($($resource:ident),+ $(,)?) => {
360    paste::paste! {
361      $(
362        #[derive(Copy, Debug, Display, Deserialize, Serialize, ConstDeref, F64Math)]
363        #[derive_const(Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
364        #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
365        pub struct $resource(u32);
366
367        impl $resource {
368          pub const MIN: Self = Self::new(0);
369          pub const MAX: Self = Self::new(u32::MAX);
370
371          pub const ID: ResourceId = ResourceId::$resource;
372          pub const MARKET_PRICE: Gold = Gold::new(1);
373
374          #[inline]
375          pub const fn new(value: u32) -> Self {
376            Self(value)
377          }
378
379          #[inline]
380          pub const fn as_dyn(&self) -> &dyn Resource {
381            self
382          }
383
384          #[inline]
385          pub const fn as_dyn_mut(&mut self) -> &mut dyn Resource {
386            self
387          }
388
389          #[inline]
390          pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
391            self.0.checked_sub(rhs.0).map(Self::new)
392          }
393
394          pub const fn add_within_capacity(
395            &mut self,
396            diff: [<$resource Diff>],
397            capacity: StorageCapacity
398          ) {
399            if diff < 0i32 {
400              *self += diff;
401            } else if self.0 < *capacity {
402              let capacity = $resource::from(capacity);
403              *self = (*self + diff).min(capacity);
404            }
405          }
406        }
407
408        impl Resource for $resource {
409          fn id(&self) -> ResourceId {
410            Self::ID
411          }
412
413          fn market_price(&self) -> Gold {
414            Self::MARKET_PRICE
415          }
416
417          fn as_u32(&self) -> u32 {
418            self.0
419          }
420
421          fn set(&mut self, value: u32) {
422            *self = Self::new(value);
423          }
424        }
425
426        const impl From<u32> for $resource {
427          fn from(value: u32) -> Self {
428            Self::new(value)
429          }
430        }
431
432        const impl From<$resource> for u32 {
433          fn from(value: $resource) -> Self {
434            value.0
435          }
436        }
437
438        const impl From<f64> for $resource {
439          fn from(value: f64) -> Self {
440            debug_assert!(value.is_finite());
441            debug_assert!(value >= 0.0);
442            Self(value.trunc() as u32)
443          }
444        }
445
446        const impl From<$resource> for f64 {
447          fn from(value: $resource) -> Self {
448            f64::from(value.0)
449          }
450        }
451
452        const impl From<MineProduction> for $resource {
453          fn from(value: MineProduction) -> Self {
454            Self(*value)
455          }
456        }
457
458        const impl From<StorageCapacity> for $resource {
459          fn from(value: StorageCapacity) -> Self {
460            Self(*value)
461          }
462        }
463
464        const impl From<$resource> for Resources {
465          fn from(value: $resource) -> Self {
466            let mut resources = Resources::new();
467            resources.[<$resource:snake>] = value;
468            resources
469          }
470        }
471
472        const impl From<$resource> for Gold {
473          fn from(value: $resource) -> Self {
474            $resource::MARKET_PRICE * value.0
475          }
476        }
477
478        const impl From<Gold> for $resource {
479          fn from(value: Gold) -> Self {
480            debug_assert!($resource::MARKET_PRICE > 0u32);
481            let resource = value / $resource::MARKET_PRICE;
482            Self::new(u32::from(resource))
483          }
484        }
485
486        impl TryFrom<$resource> for i32 {
487          type Error = $crate::error::Error;
488
489          fn try_from(value: $resource) -> Result<Self> {
490            match i32::try_from(value.0) {
491              Ok(value) => Ok(value),
492              Err(_) =>  {
493                let resources = Resources::from(value);
494                Err(Error::TooManyResources(resources))
495              },
496            }
497          }
498        }
499
500        const impl PartialEq<u32> for $resource {
501          fn eq(&self, other: &u32) -> bool {
502            self.0.eq(other)
503          }
504        }
505
506        const impl PartialOrd<u32> for $resource {
507          fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
508            self.0.partial_cmp(other)
509          }
510        }
511
512        const impl Add for $resource {
513          type Output = Self;
514
515          fn add(self, rhs: Self) -> Self {
516            Self(self.0.saturating_add(rhs.0))
517          }
518        }
519
520        const impl Add<u32> for $resource {
521          type Output = Self;
522
523          fn add(self, rhs: u32) -> Self {
524            Self(self.0.saturating_add(rhs))
525          }
526        }
527
528        const impl AddAssign for $resource {
529          fn add_assign(&mut self, rhs: Self) {
530            *self = *self + rhs;
531          }
532        }
533
534        const impl Sub for $resource {
535          type Output = Self;
536
537          fn sub(self, rhs: Self) -> Self {
538            Self(self.0.saturating_sub(rhs.0))
539          }
540        }
541
542        const impl Sub<u32> for $resource {
543          type Output = Self;
544
545          fn sub(self, rhs: u32) -> Self {
546            Self(self.0.saturating_sub(rhs))
547          }
548        }
549
550        const impl SubAssign for $resource {
551          fn sub_assign(&mut self, rhs: Self) {
552            *self = *self - rhs;
553          }
554        }
555
556        const impl Mul<u32> for $resource {
557          type Output = Self;
558
559          fn mul(self, rhs: u32) -> Self::Output {
560            Self(self.0.saturating_mul(rhs))
561          }
562        }
563
564        const impl Mul<NonZeroU32> for $resource {
565          type Output = Self;
566
567          fn mul(self, rhs: NonZeroU32) -> Self::Output {
568            self * rhs.get()
569          }
570        }
571
572        const impl Mul<MarketFee> for $resource {
573          type Output = Self;
574
575          fn mul(self, rhs: MarketFee) -> Self::Output {
576            Self::from(self.mul_ceil(*rhs))
577          }
578        }
579
580        const impl Mul<Stability> for $resource {
581          type Output = $resource;
582
583          fn mul(self, rhs: Stability) -> Self::Output {
584            Self::from(self.mul_ceil(*rhs))
585          }
586        }
587
588        const impl MulAssign<u32> for $resource {
589          fn mul_assign(&mut self, rhs: u32) {
590            *self = *self * rhs;
591          }
592        }
593
594        const impl MulAssign<MarketFee> for $resource {
595          fn mul_assign(&mut self, rhs: MarketFee) {
596            *self = *self * rhs;
597          }
598        }
599
600        const impl MulAssign<Stability> for $resource {
601          fn mul_assign(&mut self, rhs: Stability) {
602            *self = *self * rhs;
603          }
604        }
605
606        impl_mul_ceil!($resource);
607      )+
608    }
609  };
610}
611
612decl_resource!(Food, Iron, Stone, Wood);
613
614pub trait Resource: Send + Sync {
615  fn id(&self) -> ResourceId;
616  fn market_price(&self) -> Gold;
617
618  fn set(&mut self, value: u32);
619  fn clamp(&mut self, min: u32, max: u32) {
620    let value = self.as_u32();
621    self.set(value.clamp(min, max));
622  }
623
624  fn as_u32(&self) -> u32;
625  fn as_f64(&self) -> f64 {
626    f64::from(self.as_u32())
627  }
628
629  fn as_resources(&self) -> Resources
630  where
631    Self: Sized,
632  {
633    Resources::with_resource(self)
634  }
635
636  fn is_silo_resource(&self) -> bool {
637    self.id().is_silo_resource()
638  }
639
640  fn is_warehouse_resource(&self) -> bool {
641    self.id().is_warehouse_resource()
642  }
643}
644
645#[subenum(SiloResourceId, WarehouseResourceId)]
646#[derive(Copy, Debug, strum::Display, EnumIs, EnumIter, Hash, Deserialize, Serialize)]
647#[derive_const(Clone, PartialEq, Eq)]
648#[serde(rename_all = "kebab-case")]
649#[strum(serialize_all = "kebab-case")]
650#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
651#[cfg_attr(feature = "typescript", ts(export))]
652pub enum ResourceId {
653  #[subenum(SiloResourceId)]
654  Food,
655
656  #[subenum(WarehouseResourceId)]
657  Iron,
658
659  #[subenum(WarehouseResourceId)]
660  Stone,
661
662  #[subenum(WarehouseResourceId)]
663  Wood,
664}
665
666impl ResourceId {
667  pub fn is_silo_resource(self) -> bool {
668    SiloResourceId::try_from(self).is_ok()
669  }
670
671  pub fn is_warehouse_resource(self) -> bool {
672    WarehouseResourceId::try_from(self).is_ok()
673  }
674}