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