1pub 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#[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 pub const MIN: Self = Self {
54 food: Food::MIN,
55 iron: Iron::MIN,
56 stone: Stone::MIN,
57 wood: Wood::MIN,
58 };
59
60 pub const MAX: Self = Self {
62 food: Food::MAX,
63 iron: Iron::MAX,
64 stone: Stone::MAX,
65 wood: Wood::MAX,
66 };
67
68 pub const PLAYER: Self = Self::splat(800);
70
71 pub const BOT: Self = Self::splat(2500);
73
74 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 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 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 #[inline]
229 pub fn sum(&self) -> u32 {
230 self.iter().map(Resource::as_u32).sum()
231 }
232
233 #[inline]
235 pub fn sum_silo(&self) -> u32 {
236 self.silo().sum()
237 }
238
239 #[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}