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
commit f8b642ce86
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.
- Far away NPCs respond to being damaged by a projectile
- 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

3
Cargo.lock generated
View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
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 {
Void,
Lake,
@ -13,3 +14,7 @@ pub enum BiomeKind {
Jungle,
Forest,
}
impl Default for BiomeKind {
fn default() -> BiomeKind { BiomeKind::Void }
}

View File

@ -5,6 +5,7 @@ use crate::{
};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use strum_macros::EnumIter;
use tracing::{trace, warn};
#[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
// directly related to trade between entities, but between sites (more abstract)
// economical information
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, EnumIter)]
pub enum Good {
Territory(BiomeKind),
Flour,

View File

@ -7,7 +7,7 @@ edition = "2018"
[features]
tracy = ["common/tracy", "common-net/tracy"]
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"]
@ -43,15 +43,18 @@ lz-fear = { version = "0.1.1", optional = true }
deflate = { version = "0.9.1", optional = true }
flate2 = { version = "1.0.20", optional = true }
num-traits = { version = "0.2", optional = true }
common-frontend = { package = "veloren-common-frontend", path = "../common/frontend", optional = true }
[dev-dependencies]
common-frontend = { package = "veloren-common-frontend", path = "../common/frontend" }
criterion = "0.3"
csv = "1.1.3"
tracing-subscriber = { version = "0.2.15", default-features = false, features = ["fmt", "chrono", "ansi", "smallvec", "env-filter"] }
minifb = "0.19.1"
rusqlite = { version = "0.24.2", features = ["array", "vtab", "bundled", "trace"] }
svg_fmt = "0.4"
structopt = "0.3"
strum = "0.20"
[[bench]]
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: {
let labor_values = normalize(self.labor_values.clone());
// Use labor values as prices. Not correct (doesn't care about exchange value)
let prices = normalize(self.values.clone())
.map(|good, value| Some((labor_values[good]? + value?) * 0.5));
prices
.iter()
.map(|(g, v)| (g, v.unwrap_or(Economy::MINIMUM_PRICE)))
.collect()
let prices = normalize(self.values.clone()).map(|good, value| {
(labor_values[good].unwrap_or(Economy::MINIMUM_PRICE)
+ value.unwrap_or(Economy::MINIMUM_PRICE))
* 0.5
});
prices.iter().map(|(g, v)| (g, *v)).collect()
},
}
}