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; use num_traits::AsPrimitive; use serde::{Deserialize, Serialize}; use crate::ui::components::IconContent; use crate::world::travel::WorldTickEvent; pub type ItemName = String; #[derive(Clone, Debug, Default, Serialize, Deserialize, Component)] pub struct TradingState { pub items: HashMap<ItemName, usize>, pub gold: isize, } #[derive(Clone, Debug, Default, Serialize, Deserialize, Component)] 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, pub food_value: usize, pub gold_value: usize, } 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>, } 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_()); 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 } } } 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 => {} } } } } }