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