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:
@ -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
3
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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
|
||||||
])
|
])
|
||||||
|
@ -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();
|
||||||
|
@ -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 }
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
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: {
|
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()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user