use crate::{ assets::{self, AssetExt, AssetHandle}, sim::SimChunk, site::Site, util::{DHashMap, MapVec}, }; use common::{ store::Id, terrain::BiomeKind, trade::{Good, SitePrices}, }; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use std::{fmt, marker::PhantomData, sync::Once}; use Good::*; #[derive(Debug, Serialize, Deserialize)] pub struct Profession { pub name: String, pub orders: Vec<(Good, f32)>, pub products: Vec<(Good, f32)>, } // reference to profession #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct Labor(u8, PhantomData); #[derive(Debug)] pub struct AreaResources { pub resource_sum: MapVec, pub resource_chunks: MapVec, pub chunks: u32, } impl Default for AreaResources { fn default() -> Self { Self { resource_sum: MapVec::default(), resource_chunks: MapVec::default(), chunks: 0, } } } #[derive(Debug)] pub struct NaturalResources { // resources per distance, we should increase labor cost for far resources pub per_area: Vec, // computation simplifying cached values pub chunks_per_resource: MapVec, pub average_yield_per_chunk: MapVec, } impl Default for NaturalResources { fn default() -> Self { Self { per_area: Vec::new(), chunks_per_resource: MapVec::default(), average_yield_per_chunk: MapVec::default(), } } } #[derive(Debug, Deserialize)] pub struct RawProfessions(Vec); impl assets::Asset for RawProfessions { type Loader = assets::RonLoader; const EXTENSION: &'static str = "ron"; } pub fn default_professions() -> AssetHandle { RawProfessions::load_expect("common.professions") } lazy_static! { static ref LABOR: AssetHandle = default_professions(); // used to define resources needed by every person static ref DUMMY_LABOR: Labor = Labor( LABOR .read() .0 .iter() .position(|a| a.name == "_") .unwrap_or(0) as u8, PhantomData ); } impl fmt::Debug for Labor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if (self.0 as usize) < LABOR.read().0.len() { f.write_str(&LABOR.read().0[self.0 as usize].name) } else { f.write_str("?") } } } #[derive(Debug)] pub struct TradeOrder { pub customer: Id, pub amount: MapVec, // positive for orders, negative for exchange } #[derive(Debug)] pub struct TradeDelivery { pub supplier: Id, pub amount: MapVec, // positive for orders, negative for exchange pub prices: MapVec, // at the time of interaction pub supply: MapVec, // maximum amount available, at the time of interaction } #[derive(Debug)] pub struct TradeInformation { pub orders: DHashMap, Vec>, // per provider pub deliveries: DHashMap, Vec>, // per receiver } impl Default for TradeInformation { fn default() -> Self { Self { orders: Default::default(), deliveries: Default::default(), } } } #[derive(Debug)] pub struct NeighborInformation { pub id: Id, pub travel_distance: usize, // remembered from last interaction pub last_values: MapVec, pub last_supplies: MapVec, } #[derive(Debug)] pub struct Economy { // Population pub pop: f32, /// Total available amount of each good pub stocks: MapVec, /// Surplus stock compared to demand orders pub surplus: MapVec, /// change rate (derivative) of stock in the current situation pub marginal_surplus: MapVec, /// amount of wares not needed by the economy (helps with trade planning) pub unconsumed_stock: MapVec, // For some goods, such a goods without any supply, it doesn't make sense to talk about value pub values: MapVec>, pub last_exports: MapVec, pub active_exports: MapVec, // unfinished trade (amount unconfirmed) //pub export_targets: MapVec, pub labor_values: MapVec>, pub material_costs: MapVec, // Proportion of individuals dedicated to an industry pub labors: MapVec, // Per worker, per year, of their output good pub yields: MapVec, pub productivity: MapVec, pub natural_resources: NaturalResources, // usize is distance pub neighbors: Vec, } static INIT: Once = Once::new(); impl Default for Economy { fn default() -> Self { INIT.call_once(|| { LABOR.read(); }); Self { pop: 32.0, stocks: MapVec::from_list(&[(Coin, Economy::STARTING_COIN)], 100.0), surplus: Default::default(), marginal_surplus: Default::default(), values: MapVec::from_list(&[(Coin, Some(2.0))], None), last_exports: Default::default(), active_exports: Default::default(), labor_values: Default::default(), material_costs: Default::default(), labors: MapVec::from_default(0.01), yields: MapVec::from_default(1.0), productivity: MapVec::from_default(1.0), natural_resources: Default::default(), neighbors: Default::default(), unconsumed_stock: Default::default(), } } } impl Economy { pub const MINIMUM_PRICE: f32 = 0.1; pub const STARTING_COIN: f32 = 1000.0; const _NATURAL_RESOURCE_SCALE: f32 = 1.0 / 9.0; pub fn cache_economy(&mut self) { for &g in good_list() { let amount: f32 = self .natural_resources .per_area .iter() .map(|a| a.resource_sum[g]) .sum(); let chunks = self .natural_resources .per_area .iter() .map(|a| a.resource_chunks[g]) .sum(); if chunks > 0.001 { self.natural_resources.chunks_per_resource[g] = chunks; self.natural_resources.average_yield_per_chunk[g] = amount / chunks; } } } pub fn get_orders(&self) -> DHashMap, Vec<(Good, f32)>> { LABOR .read() .0 .iter() .enumerate() .map(|(i, p)| { ( if p.name == "_" { None } else { Some(Labor(i as u8, PhantomData)) }, p.orders.clone(), ) }) .collect() } pub fn get_productivity(&self) -> MapVec> { let products: MapVec> = MapVec::from_iter( LABOR .read() .0 .iter() .enumerate() .filter(|(_, p)| !p.products.is_empty()) .map(|(i, p)| (Labor(i as u8, PhantomData), p.products.clone())), vec![(Good::Terrain(BiomeKind::Void), 0.0)], ); products.map(|l, vec| { let labor_ratio = self.labors[l]; let total_workers = labor_ratio * self.pop; // apply economy of scale (workers get more productive in numbers) let relative_scale = 1.0 + labor_ratio; let absolute_scale = (1.0 + total_workers / 100.0).min(3.0); let scale = relative_scale * absolute_scale; vec.iter() .map(|(good, amount)| (*good, amount * scale)) .collect() }) } pub fn replenish(&mut self, _time: f32) { for (good, &ch) in self.natural_resources.chunks_per_resource.iter() { let per_year = self.natural_resources.average_yield_per_chunk[good] * ch; self.stocks[good] = self.stocks[good].max(per_year); } // info!("resources {:?}", self.stocks); } pub fn add_chunk(&mut self, ch: &SimChunk, distance_squared: i64) { // let biome = ch.get_biome(); // we don't scale by pi, although that would be correct let distance_bin = (distance_squared >> 16).min(64) as usize; if self.natural_resources.per_area.len() <= distance_bin { self.natural_resources .per_area .resize_with(distance_bin + 1, Default::default); } self.natural_resources.per_area[distance_bin].chunks += 1; // self.natural_resources.per_area[distance_bin].resource_sum[Terrain(biome)] += // 1.0; self.natural_resources.per_area[distance_bin]. // resource_chunks[Terrain(biome)] += 1.0; TODO: Scale resources by // rockiness or tree_density? let mut add_biome = |biome, amount| { self.natural_resources.per_area[distance_bin].resource_sum[Terrain(biome)] += amount; self.natural_resources.per_area[distance_bin].resource_chunks[Terrain(biome)] += amount; }; if ch.river.is_ocean() { add_biome(BiomeKind::Ocean, 1.0); } else if ch.river.is_lake() { add_biome(BiomeKind::Lake, 1.0); } else { add_biome(BiomeKind::Forest, 0.5 + ch.tree_density); add_biome(BiomeKind::Grassland, 0.5 + ch.humidity); add_biome(BiomeKind::Jungle, 0.5 + ch.humidity * ch.temp.max(0.0)); add_biome(BiomeKind::Mountain, 0.5 + (ch.alt / 4000.0).max(0.0)); add_biome( BiomeKind::Desert, 0.5 + (1.0 - ch.humidity) * ch.temp.max(0.0), ); add_biome(BiomeKind::Snowland, 0.5 + (-ch.temp).max(0.0)); } } pub fn add_neighbor(&mut self, id: Id, distance: usize) { self.neighbors.push(NeighborInformation { id, travel_distance: distance, last_values: MapVec::from_default(Economy::MINIMUM_PRICE), last_supplies: Default::default(), }); } pub fn get_site_prices(&self) -> SitePrices { SitePrices { values: { // Use labor values as prices. Not correct (doesn't care about exchange value) let prices = self.labor_values.clone(); // Normalized values. Note: not correct, but better than nothing let sum = prices .iter() .map(|(_, price)| (*price).unwrap_or(0.0)) .sum::() .max(0.001); prices .iter() .map(|(g, v)| (g, v.map(|v| v / sum).unwrap_or(Economy::MINIMUM_PRICE))) .collect() }, } } } pub fn good_list() -> &'static [Good] { static GOODS: [Good; 23] = [ // controlled resources Territory(BiomeKind::Grassland), Territory(BiomeKind::Forest), Territory(BiomeKind::Lake), Territory(BiomeKind::Ocean), Territory(BiomeKind::Mountain), RoadSecurity, Ingredients, // produced goods Flour, Meat, Wood, Stone, Food, Tools, Armor, Potions, Transportation, // exchange currency Coin, // uncontrolled resources Terrain(BiomeKind::Lake), Terrain(BiomeKind::Mountain), Terrain(BiomeKind::Grassland), Terrain(BiomeKind::Forest), Terrain(BiomeKind::Desert), Terrain(BiomeKind::Ocean), ]; &GOODS } pub fn transportation_effort(g: Good) -> f32 { match g { Terrain(_) | Territory(_) | RoadSecurity => 0.0, Coin => 0.01, Potions => 0.1, Armor => 2.0, Stone => 4.0, _ => 1.0, } } pub fn decay_rate(g: Good) -> f32 { match g { Food => 0.2, Flour => 0.1, Meat => 0.25, Ingredients => 0.1, _ => 0.0, } } /** you can't accumulate or save these options/resources for later */ pub fn direct_use_goods() -> &'static [Good] { static DIRECT_USE: [Good; 13] = [ Transportation, Territory(BiomeKind::Grassland), Territory(BiomeKind::Forest), Territory(BiomeKind::Lake), Territory(BiomeKind::Ocean), Territory(BiomeKind::Mountain), RoadSecurity, Terrain(BiomeKind::Grassland), Terrain(BiomeKind::Forest), Terrain(BiomeKind::Lake), Terrain(BiomeKind::Ocean), Terrain(BiomeKind::Mountain), Terrain(BiomeKind::Desert), ]; &DIRECT_USE } impl Labor { pub fn list() -> impl Iterator { (0..LABOR.read().0.len()) .filter(|&i| i != (DUMMY_LABOR.0 as usize)) .map(|i| Self(i as u8, PhantomData)) } }