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