Skip to main content

nil_core/market/
mod.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4pub mod fee;
5pub mod vault;
6
7use crate::market::fee::MarketFee;
8use crate::market::vault::MarketVault;
9use crate::resources::gold::Gold;
10use crate::resources::{Food, Iron, Resources, Stone, Wood};
11use serde::{Deserialize, Serialize};
12use std::marker::PhantomData;
13
14#[derive(Clone, Debug, Deserialize, Serialize)]
15#[derive_const(Default)]
16#[serde(rename_all = "camelCase")]
17#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
18pub struct Market {
19  vault: MarketVault,
20  fee: MarketFee,
21
22  // We don't really need to include the market price here,
23  // since it can be derived from the resources themselves.
24  // However, including it simplifies things for downstream consumers,
25  // as they don't need to query the resources separately to obtain it.
26  #[cfg_attr(feature = "typescript", ts(as = "MarketPriceTable"))]
27  #[cfg_attr(
28    not(feature = "typescript"),
29    serde(skip_deserializing, serialize_with = "serialize_market_price_table")
30  )]
31  price_table: PhantomData<MarketPriceTable>,
32}
33
34impl Market {
35  pub const fn new(fee: MarketFee) -> Self {
36    Self {
37      vault: MarketVault::default(),
38      fee: fee.clamped(),
39      price_table: PhantomData,
40    }
41  }
42
43  #[inline]
44  pub const fn vault(&self) -> &MarketVault {
45    &self.vault
46  }
47
48  pub(crate) const fn vault_mut(&mut self) -> &mut MarketVault {
49    &mut self.vault
50  }
51
52  #[inline]
53  pub const fn fee(&self) -> MarketFee {
54    self.fee
55  }
56
57  /// Sets the market fee, clamping it to the valid range.
58  ///
59  /// As the fee is not expected to be changed throughout the game,
60  /// this should only be used to execute cheats or for testing purposes.
61  pub(crate) const fn set_fee(&mut self, fee: MarketFee) {
62    self.fee = fee.clamped();
63  }
64
65  #[inline]
66  pub const fn price_table(&self) -> MarketPriceTable {
67    MarketPriceTable::default()
68  }
69
70  #[inline]
71  pub const fn price_of(&self, op: MarketOperation, resources: Resources) -> Gold {
72    match op {
73      MarketOperation::Buy => Gold::from(resources + (resources * self.fee())),
74      MarketOperation::Sell => Gold::from(resources),
75    }
76  }
77
78  /// Maximum amount of a resource that can be bought with the given amount of gold.
79  pub fn buyable_amount(&self, market_price: Gold, gold: Gold) -> u32 {
80    let fee = f64::from(self.fee());
81    let market_price = f64::from(market_price);
82    let gold = f64::from(gold);
83    let resource = gold / (market_price * (1.0 + fee));
84    resource.floor().max(0.0) as u32
85  }
86}
87
88#[derive(Copy, Debug, strum::Display, Hash, Deserialize, Serialize)]
89#[derive_const(Clone, PartialEq, Eq)]
90#[serde(rename_all = "kebab-case")]
91#[strum(serialize_all = "kebab-case")]
92#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
93pub enum MarketOperation {
94  Buy,
95  Sell,
96}
97
98#[derive(Copy, Debug, Deserialize, Serialize)]
99#[derive_const(Clone, PartialEq, Eq)]
100#[serde(rename_all = "camelCase")]
101#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
102pub struct MarketPriceTable {
103  food: Gold,
104  iron: Gold,
105  stone: Gold,
106  wood: Gold,
107}
108
109impl MarketPriceTable {
110  pub const fn new() -> Self {
111    Self::default()
112  }
113}
114
115const impl Default for MarketPriceTable {
116  fn default() -> Self {
117    Self {
118      food: Food::MARKET_PRICE,
119      iron: Iron::MARKET_PRICE,
120      stone: Stone::MARKET_PRICE,
121      wood: Wood::MARKET_PRICE,
122    }
123  }
124}
125
126#[cfg(not(feature = "typescript"))]
127#[allow(clippy::trivially_copy_pass_by_ref)]
128fn serialize_market_price_table<S>(
129  _: &PhantomData<MarketPriceTable>,
130  serializer: S,
131) -> Result<S::Ok, S::Error>
132where
133  S: serde::Serializer,
134{
135  MarketPriceTable::default().serialize(serializer)
136}