Merge branch 'aweinstock/economy-20210528' into 'master'

Fix economy data not properly being used to price trades, resulting in default...

See merge request veloren/veloren!2353
This commit is contained in:
Marcel
2021-05-28 22:05:24 +00:00
9 changed files with 218 additions and 20 deletions

View File

@ -141,6 +141,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Mindflayer AI now correctly summons husks at certain HP thresholds. - Mindflayer AI now correctly summons husks at certain HP thresholds.
- Far away NPCs respond to being damaged by a projectile - Far away NPCs respond to being damaged by a projectile
- Fixed terrain clipping with glider - Fixed terrain clipping with glider
- Fixed an issue where prices weren't properly making their way from econsim to the actual trade values.
## [0.9.0] - 2021-03-20 ## [0.9.0] - 2021-03-20

3
Cargo.lock generated
View File

@ -5900,6 +5900,7 @@ dependencies = [
"bincode", "bincode",
"bitvec", "bitvec",
"criterion", "criterion",
"csv",
"deflate 0.9.1", "deflate 0.9.1",
"enum-iterator", "enum-iterator",
"flate2", "flate2",
@ -5919,8 +5920,10 @@ dependencies = [
"rand_chacha 0.3.0", "rand_chacha 0.3.0",
"rayon", "rayon",
"ron", "ron",
"rusqlite",
"serde", "serde",
"structopt", "structopt",
"strum",
"svg_fmt", "svg_fmt",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",

View File

@ -41,6 +41,6 @@ good_scaling: [
(Food, 0.1), // common.items.food.mushroom (Food, 0.1), // common.items.food.mushroom
(Coin, 1.0), // common.items.utility.coins (Coin, 1.0), // common.items.utility.coins
(Armor, 0.05), // common.items.armor.misc.pants.worker_blue (Armor, 0.05), // common.items.armor.misc.pants.worker_blue
(Tools, 0.05), // common.items.weapons.staff.starter_staff (Tools, 0.10), // common.items.weapons.staff.starter_staff
(Ingredients, 0.25), // common.items.crafting_ing.leather_scraps (Ingredients, 0.15), // common.items.crafting_ing.leather_scraps
]) ])

View File

@ -551,10 +551,12 @@ impl LoadoutBuilder {
.expect("coins should be stackable"); .expect("coins should be stackable");
*s = Some(coin_item); *s = Some(coin_item);
coins = 0; coins = 0;
} else if let Some(item_id) = } else if armor > 0.0 {
TradePricing::random_item(Good::Armor, armor, true) if let Some(item_id) =
{ TradePricing::random_item(Good::Armor, armor, true)
*s = Some(Item::new_from_asset_expect(&item_id)); {
*s = Some(Item::new_from_asset_expect(&item_id));
}
} }
} }
let mut bag1 = Item::new_from_asset_expect( let mut bag1 = Item::new_from_asset_expect(
@ -566,10 +568,13 @@ impl LoadoutBuilder {
.copied() .copied()
.unwrap_or_default() .unwrap_or_default()
/ 10.0; / 10.0;
for i in bag1.slots_mut() { if weapon > 0.0 {
if let Some(item_id) = TradePricing::random_item(Good::Tools, weapon, true) for i in bag1.slots_mut() {
{ if let Some(item_id) =
*i = Some(Item::new_from_asset_expect(&item_id)); TradePricing::random_item(Good::Tools, weapon, true)
{
*i = Some(Item::new_from_asset_expect(&item_id));
}
} }
} }
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();

View File

@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum_macros::EnumIter;
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, EnumIter)]
pub enum BiomeKind { pub enum BiomeKind {
Void, Void,
Lake, Lake,
@ -13,3 +14,7 @@ pub enum BiomeKind {
Jungle, Jungle,
Forest, Forest,
} }
impl Default for BiomeKind {
fn default() -> BiomeKind { BiomeKind::Void }
}

View File

@ -5,6 +5,7 @@ use crate::{
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum_macros::EnumIter;
use tracing::{trace, warn}; use tracing::{trace, warn};
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -296,7 +297,7 @@ impl Default for Trades {
// we need this declaration in common for Merchant loadout creation, it is not // we need this declaration in common for Merchant loadout creation, it is not
// directly related to trade between entities, but between sites (more abstract) // directly related to trade between entities, but between sites (more abstract)
// economical information // economical information
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, EnumIter)]
pub enum Good { pub enum Good {
Territory(BiomeKind), Territory(BiomeKind),
Flour, Flour,

View File

@ -7,7 +7,7 @@ edition = "2018"
[features] [features]
tracy = ["common/tracy", "common-net/tracy"] tracy = ["common/tracy", "common-net/tracy"]
simd = ["vek/platform_intrinsics"] simd = ["vek/platform_intrinsics"]
bin_compression = ["lz-fear", "deflate", "flate2", "common-frontend", "image/jpeg", "num-traits"] bin_compression = ["lz-fear", "deflate", "flate2", "image/jpeg", "num-traits"]
default = ["simd"] default = ["simd"]
@ -43,15 +43,18 @@ lz-fear = { version = "0.1.1", optional = true }
deflate = { version = "0.9.1", optional = true } deflate = { version = "0.9.1", optional = true }
flate2 = { version = "1.0.20", optional = true } flate2 = { version = "1.0.20", optional = true }
num-traits = { version = "0.2", optional = true } num-traits = { version = "0.2", optional = true }
common-frontend = { package = "veloren-common-frontend", path = "../common/frontend", optional = true }
[dev-dependencies] [dev-dependencies]
common-frontend = { package = "veloren-common-frontend", path = "../common/frontend" }
criterion = "0.3" criterion = "0.3"
csv = "1.1.3"
tracing-subscriber = { version = "0.2.15", default-features = false, features = ["fmt", "chrono", "ansi", "smallvec", "env-filter"] } tracing-subscriber = { version = "0.2.15", default-features = false, features = ["fmt", "chrono", "ansi", "smallvec", "env-filter"] }
minifb = "0.19.1" minifb = "0.19.1"
rusqlite = { version = "0.24.2", features = ["array", "vtab", "bundled", "trace"] }
svg_fmt = "0.4" svg_fmt = "0.4"
structopt = "0.3" structopt = "0.3"
strum = "0.20"
[[bench]] [[bench]]
harness = false harness = false

View File

@ -0,0 +1,180 @@
use common::{
terrain::BiomeKind,
trade::{Good, SitePrices},
};
use rayon::ThreadPoolBuilder;
use rusqlite::{Connection, ToSql};
use std::error::Error;
use strum::IntoEnumIterator;
use vek::Vec2;
use veloren_world::{
index::Index,
sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP},
World,
};
fn good_pricing_csv(world: &World, index: &Index) -> Result<(), Box<dyn Error>> {
let mut csv = csv::Writer::from_path("good_pricing.csv")?;
csv.write_record(&[
"Site",
"XCoord",
"YCoord",
"Flour",
"Meat",
"Transportation",
"Food",
"Wood",
"Stone",
"Tools",
"Armor",
"Ingredients",
"Potions",
"Coin",
"RoadSecurity",
])?;
for civsite in world.civs().sites() {
if let Some(site_id) = civsite.site_tmp {
let site = index.sites.get(site_id);
if site.do_economic_simulation() {
let prices = site.economy.get_site_prices();
//println!("{:?}: {:?} {:?}", site.name(), civsite.center, prices);
csv.write_record(&[
site.name(),
&format!("{}", civsite.center.x),
&format!("{}", civsite.center.y),
&format!("{}", prices.values.get(&Good::Flour).unwrap_or(&0.0)),
&format!("{}", prices.values.get(&Good::Meat).unwrap_or(&0.0)),
&format!(
"{}",
prices.values.get(&Good::Transportation).unwrap_or(&0.0)
),
&format!("{}", prices.values.get(&Good::Food).unwrap_or(&0.0)),
&format!("{}", prices.values.get(&Good::Wood).unwrap_or(&0.0)),
&format!("{}", prices.values.get(&Good::Stone).unwrap_or(&0.0)),
&format!("{}", prices.values.get(&Good::Tools).unwrap_or(&0.0)),
&format!("{}", prices.values.get(&Good::Armor).unwrap_or(&0.0)),
&format!("{}", prices.values.get(&Good::Ingredients).unwrap_or(&0.0)),
&format!("{}", prices.values.get(&Good::Potions).unwrap_or(&0.0)),
&format!("{}", prices.values.get(&Good::Coin).unwrap_or(&0.0)),
&format!("{}", prices.values.get(&Good::RoadSecurity).unwrap_or(&0.0)),
])?;
}
}
}
Ok(())
}
fn economy_sqlite(world: &World, index: &Index) -> Result<(), Box<dyn Error>> {
let conn = Connection::open("economy.sqlite")?;
#[rustfmt::skip]
conn.execute_batch("
DROP TABLE IF EXISTS site;
CREATE TABLE site (
xcoord INTEGER NOT NULL,
ycoord INTEGER NUT NULL,
name TEXT NOT NULL
);
CREATE UNIQUE INDEX site_position ON site(xcoord, ycoord);
DROP TABLE IF EXISTS site_price;
CREATE TABLE site_price (
xcoord INTEGER NOT NULL,
ycoord INTEGER NOT NULL,
good TEXT NOT NULL,
price REAL NOT NULL
);
CREATE UNIQUE INDEX site_good on site_price(xcoord, ycoord, good);
")?;
let mut all_goods = Vec::new();
for good in Good::iter() {
match good {
Good::Territory(_) => {
for biome in BiomeKind::iter() {
all_goods.push(Good::Territory(biome));
}
},
Good::Terrain(_) => {
for biome in BiomeKind::iter() {
all_goods.push(Good::Terrain(biome));
}
},
_ => {
all_goods.push(good);
},
}
}
let mut good_columns = String::new();
let mut good_exprs = String::new();
for good in all_goods.iter() {
good_columns += &format!(", '{:?}'", good);
good_exprs += &format!(
", MAX(CASE WHEN site_price.good = '{:?}' THEN site_price.price END)",
good
);
}
#[rustfmt::skip]
let create_view = format!("
DROP VIEW IF EXISTS site_price_tr;
CREATE VIEW site_price_tr (xcoord, ycoord {})
AS SELECT xcoord, ycoord {}
FROM site NATURAL JOIN site_price
GROUP BY xcoord, ycoord;
", good_columns, good_exprs);
conn.execute_batch(&create_view)?;
let mut insert_price_stmt = conn
.prepare("REPLACE INTO site_price (xcoord, ycoord, good, price) VALUES (?1, ?2, ?3, ?4)")?;
let mut insert_price = move |center: Vec2<i32>, good: Good, prices: &SitePrices| {
let price = prices.values.get(&good).unwrap_or(&0.0);
insert_price_stmt.execute(&[
&center.x as &dyn ToSql,
&center.y,
&format!("{:?}", good),
&(*price as f64),
])
};
for civsite in world.civs().sites() {
if let Some(site_id) = civsite.site_tmp {
let site = index.sites.get(site_id);
if site.do_economic_simulation() {
let prices = site.economy.get_site_prices();
conn.execute(
"REPLACE INTO site (xcoord, ycoord, name) VALUES (?1, ?2, ?3)",
&[
&civsite.center.x as &dyn ToSql,
&civsite.center.y,
&site.name(),
],
)?;
for good in all_goods.iter() {
insert_price(civsite.center, *good, &prices)?;
}
}
}
}
Ok(())
}
fn main() {
common_frontend::init_stdout(None);
println!("Loading world");
let pool = ThreadPoolBuilder::new().build().unwrap();
let (world, index) = World::generate(
59686,
WorldOpts {
seed_elements: true,
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
},
&pool,
);
println!("Loaded world");
if let Err(e) = good_pricing_csv(&world, &index) {
println!("Error generating goodpricing csv: {:?}", e);
}
if let Err(e) = economy_sqlite(&world, &index) {
println!("Error generating economy sqlite db: {:?}", e);
}
}

View File

@ -339,12 +339,12 @@ impl Economy {
values: { values: {
let labor_values = normalize(self.labor_values.clone()); let labor_values = normalize(self.labor_values.clone());
// Use labor values as prices. Not correct (doesn't care about exchange value) // Use labor values as prices. Not correct (doesn't care about exchange value)
let prices = normalize(self.values.clone()) let prices = normalize(self.values.clone()).map(|good, value| {
.map(|good, value| Some((labor_values[good]? + value?) * 0.5)); (labor_values[good].unwrap_or(Economy::MINIMUM_PRICE)
prices + value.unwrap_or(Economy::MINIMUM_PRICE))
.iter() * 0.5
.map(|(g, v)| (g, v.unwrap_or(Economy::MINIMUM_PRICE))) });
.collect() prices.iter().map(|(g, v)| (g, *v)).collect()
}, },
} }
} }