Skip to content
Snippets Groups Projects
trading.rs 6.03 KiB
Newer Older
Louis's avatar
Louis committed
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};

use bevy::prelude::{Assets, Component, EventReader, Handle, Query, Res, Resource};
use bevy::reflect::TypeUuid;
Louis's avatar
Louis committed
use num_traits::AsPrimitive;
use serde::{Deserialize, Serialize};

use crate::ui::components::IconContent;
Louis's avatar
Louis committed
use crate::world::travel::WorldTickEvent;
Louis's avatar
Louis committed

pub type ItemName = String;

#[derive(Clone, Debug, Default, Serialize, Deserialize, Component)]
Louis's avatar
Louis committed
pub struct TradingState {
	pub items: HashMap<ItemName, usize>,
	pub gold: isize,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize, Component)]
Louis's avatar
Louis committed
pub struct HungerState {
	pub sustenance: usize,
	pub starvation_ticks: f32,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct TradeGood {
	pub name: String,
	pub icon: IconContent,
Louis's avatar
Louis committed
	pub food_value: usize,
	pub gold_value: usize,
Louis's avatar
Louis committed
}

impl PartialEq for TradeGood {
	fn eq(&self, other: &Self) -> bool {
		self.name == other.name
	}
}
impl PartialEq<String> for TradeGood {
	fn eq(&self, other: &String) -> bool {
		&self.name == other
	}
}
impl Eq for TradeGood {}
impl Hash for TradeGood {
	fn hash<H: Hasher>(&self, state: &mut H) {
		self.name.hash(state)
	}
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RosterSupplyTrend {
	#[serde(alias = "converge")]
	#[default]
	Converge,
	#[serde(alias = "consume")]
	Consume,
	#[serde(alias = "produce")]
	Produce,
	#[serde(alias = "halt")]
	Halt,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RosterEntry {
	pub tick_decay: f32,
	pub natural_limit: usize,
	pub cost_multipliers: Vec<(usize, f32)>,
	#[serde(default)]
	pub undersupply_trend: RosterSupplyTrend,
	#[serde(default)]
	pub oversupply_trend: RosterSupplyTrend,
}

#[derive(Debug, Clone, PartialEq, Default, TypeUuid, Serialize, Deserialize)]
#[serde(from = "HashMap<String, RosterEntry>")]
#[uuid = "7e395a74-7745-11ed-8b4e-ef01db57341b"]
pub struct TradeManifest(HashMap<String, RosterEntry>);
impl From<HashMap<String, RosterEntry>> for TradeManifest {
	fn from(value: HashMap<String, RosterEntry>) -> Self {
		Self(value)
	}
}
impl Deref for TradeManifest {
	type Target = HashMap<String, RosterEntry>;
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}
impl DerefMut for TradeManifest {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.0
	}
}

#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, Component)]
pub struct TradeManifestTickState(HashMap<String, f32>);
impl From<HashMap<String, f32>> for TradeManifestTickState {
	fn from(value: HashMap<String, f32>) -> Self {
		Self(value)
	}
}
impl Deref for TradeManifestTickState {
	type Target = HashMap<String, f32>;
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}
impl DerefMut for TradeManifestTickState {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.0
	}
}

#[derive(Debug, Clone, PartialEq, Component)]
pub struct TradeRoster {
	pub goods_relations: HashMap<TradeGood, RosterEntry>,
	pub production_ticks: HashMap<TradeGood, f32>,
Louis's avatar
Louis committed
}

impl TradingState {
	pub fn spend_gold(&mut self, amount: impl AsPrimitive<isize>) -> bool {
		if self.gold < amount.as_() {
			false
		} else {
			self.adjust_gold(-amount.as_());
Louis's avatar
Louis committed
			true
		}
	}

	pub fn adjust_gold(&mut self, adjustment: impl AsPrimitive<isize>) {
		self.gold += adjustment.as_();
	}

	pub fn remove_items(
		&mut self,
		identifier: impl ToString,
		amount: impl AsPrimitive<usize>,
	) -> bool {
		match self.items.entry(identifier.to_string()) {
			Entry::Occupied(mut e) => {
				let amount = amount.as_();
				if e.get() >= &amount {
					*e.get_mut() -= amount;
					if e.get() == &0 {
						e.remove();
					}
					true
				} else {
					false
				}
			}
			Entry::Vacant(_) => false,
		}
	}

	pub fn add_items(&mut self, identifier: impl ToString, amount: impl AsPrimitive<usize>) {
		*self.items.entry(identifier.to_string()).or_insert(0) += amount.as_()
	}

	pub fn try_buy_items(
		&mut self,
		cost: impl AsPrimitive<isize>,
		identifier: impl ToString,
		amount: impl AsPrimitive<usize>,
	) -> bool {
		if self.spend_gold(cost) {
			self.add_items(identifier, amount);
			true
		} else {
			false
		}
	}

	pub fn try_sell_items(
		&mut self,
		value: impl AsPrimitive<isize>,
		identifier: impl ToString,
		amount: impl AsPrimitive<usize>,
	) -> bool {
		if self.remove_items(identifier, amount) {
			self.adjust_gold(value);
			true
		} else {
			false
		}
	}
}
Louis's avatar
Louis committed

pub fn tick_manifests(
	events: EventReader<WorldTickEvent>,
	mut town_query: Query<(
		&mut TradingState,
		&Handle<TradeManifest>,
		&mut TradeManifestTickState,
	)>,
	manifests: Res<Assets<TradeManifest>>,
) {
	if events.is_empty() {
		return;
	}
	log::info!("TICK MANIFESTS");

	for (mut state, manifest, mut tick_state) in &mut town_query {
		if let Some(manifest) = manifests.get(manifest) {
			for (good, entry) in manifest.iter() {
				let quantity = *state.items.get(good).to_owned().unwrap_or(&0);

				let ts = tick_state.entry(good.clone()).or_default();

				match if quantity >= entry.natural_limit {
					&entry.oversupply_trend
				} else {
					&entry.undersupply_trend
				} {
					RosterSupplyTrend::Converge => {
						if quantity != entry.natural_limit {
							*ts += entry.tick_decay;
							while ts > &mut 1.0 {
								*ts -= 1.0;
								if quantity > entry.natural_limit {
									let mut item = state.items.entry(good.clone()).or_insert(0);
									*item = item.saturating_sub(1);
								} else {
									let mut item = state.items.entry(good.clone()).or_insert(0);
									*item = item.saturating_add(1);
								}
							}
						}
					}
					RosterSupplyTrend::Consume => {
						*ts += entry.tick_decay;
						while ts > &mut 1.0 {
							*ts -= 1.0;
							let mut item = state.items.entry(good.clone()).or_insert(0);
							*item = item.saturating_sub(1);
						}
					}
					RosterSupplyTrend::Produce => {
						*ts += entry.tick_decay;
						while ts > &mut 1.0 {
							*ts -= 1.0;
							let mut item = state.items.entry(good.clone()).or_insert(0);
							*item = item.saturating_add(1);
						}
					}
					RosterSupplyTrend::Halt => {}
				}
			}
		}
	}
}