mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
commit
f8b642ce86
@ -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
3
Cargo.lock
generated
@ -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",
|
||||
|
@ -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
|
||||
])
|
||||
|
@ -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();
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
180
world/examples/pricing_csv.rs
Normal file
180
world/examples/pricing_csv.rs
Normal 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(&[
|
||||
¢er.x as &dyn ToSql,
|
||||
¢er.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);
|
||||
}
|
||||
}
|
@ -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()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user