mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'christof/econfix' into 'master'
Small economy cleanup See merge request veloren/veloren!3164
This commit is contained in:
commit
19c764dde6
@ -2,32 +2,32 @@
|
|||||||
loot_tables: [
|
loot_tables: [
|
||||||
// balance the loot tables against each other (higher= more common= smaller price)
|
// balance the loot tables against each other (higher= more common= smaller price)
|
||||||
// Weapons
|
// Weapons
|
||||||
(32.0, true, "common.loot_tables.weapons.starter"),
|
(192.0, true, "common.loot_tables.weapons.starter"),
|
||||||
(0.025, false, "common.loot_tables.weapons.cultist"),
|
(0.075, false, "common.loot_tables.weapons.cultist"),
|
||||||
(0.025, false, "common.loot_tables.weapons.cave"),
|
(0.075, false, "common.loot_tables.weapons.cave"),
|
||||||
(0.02, false, "common.loot_tables.weapons.legendary"),
|
(0.04, false, "common.loot_tables.weapons.legendary"),
|
||||||
|
|
||||||
// Weapons sets
|
// Weapons sets
|
||||||
(16.0, true, "common.loot_tables.weapons.tier-0"),
|
(80.0, true, "common.loot_tables.weapons.tier-0"),
|
||||||
(8.0, true, "common.loot_tables.weapons.tier-1"),
|
(48.0, true, "common.loot_tables.weapons.tier-1"),
|
||||||
(1.0, true, "common.loot_tables.weapons.tier-2"),
|
(6.0, true, "common.loot_tables.weapons.tier-2"),
|
||||||
(0.125, true, "common.loot_tables.weapons.tier-3"),
|
(0.75, true, "common.loot_tables.weapons.tier-3"),
|
||||||
(0.0625, false, "common.loot_tables.weapons.tier-4"),
|
(0.375, false, "common.loot_tables.weapons.tier-4"),
|
||||||
(0.03, false, "common.loot_tables.weapons.tier-5"),
|
(0.18, false, "common.loot_tables.weapons.tier-5"),
|
||||||
|
|
||||||
// Non-craftable Armor
|
// Non-craftable Armor
|
||||||
(20.0, true, "common.loot_tables.armor.cloth"),
|
(640, true, "common.loot_tables.armor.cloth"),
|
||||||
(1.0, true, "common.loot_tables.armor.twigs"),
|
(6.0, true, "common.loot_tables.armor.twigs"),
|
||||||
(1.0, true, "common.loot_tables.armor.twigsflowers"),
|
(6.0, true, "common.loot_tables.armor.twigsflowers"),
|
||||||
(1.0, true, "common.loot_tables.armor.twigsleaves"),
|
(6.0, true, "common.loot_tables.armor.twigsleaves"),
|
||||||
(0.01, false, "common.trading.jewellery"),
|
(0.02, false, "common.trading.jewellery"),
|
||||||
|
|
||||||
// Ingredients
|
// Ingredients
|
||||||
(1.0, true, "common.trading.sellable_materials"),
|
(72.5, true, "common.trading.sellable_materials"),
|
||||||
(1.0, false, "common.trading.unsellable_materials"),
|
(1.7205, false, "common.trading.unsellable_materials"),
|
||||||
|
|
||||||
// Food Ingredients
|
// Food Ingredients
|
||||||
(1.0, true, "common.trading.food"),
|
(20.375, true, "common.trading.food"),
|
||||||
|
|
||||||
// Potions
|
// Potions
|
||||||
//
|
//
|
||||||
@ -39,9 +39,9 @@ loot_tables: [
|
|||||||
// and economy.
|
// and economy.
|
||||||
//
|
//
|
||||||
// Collections
|
// Collections
|
||||||
(0.00001, false, "common.trading.collection"),
|
(0.00026, false, "common.trading.collection"),
|
||||||
// Manual balance
|
// Manual balance
|
||||||
(1.0, false, "common.trading.balance"),
|
(81.0, false, "common.trading.balance"),
|
||||||
],
|
],
|
||||||
|
|
||||||
// this is the amount of that good the most common item represents
|
// this is the amount of that good the most common item represents
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
#![warn(clippy::pedantic)]
|
|
||||||
//#![warn(clippy::nursery)]
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
assets::{self, AssetExt},
|
assets::{self, AssetExt},
|
||||||
lottery::{LootSpec, Lottery},
|
lottery::LootSpec,
|
||||||
recipe::{default_recipe_book, RecipeInput},
|
recipe::{default_recipe_book, RecipeInput},
|
||||||
trade::Good,
|
trade::Good,
|
||||||
};
|
};
|
||||||
@ -11,6 +8,7 @@ use assets::AssetGuard;
|
|||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::cmp::Ordering;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
const PRICING_DEBUG: bool = false;
|
const PRICING_DEBUG: bool = false;
|
||||||
@ -27,7 +25,6 @@ pub struct TradePricing {
|
|||||||
|
|
||||||
// good_scaling of coins
|
// good_scaling of coins
|
||||||
coin_scale: f32,
|
coin_scale: f32,
|
||||||
// rng: ChaChaRng,
|
|
||||||
|
|
||||||
// get amount of material per item
|
// get amount of material per item
|
||||||
material_cache: HashMap<String, (Good, f32)>,
|
material_cache: HashMap<String, (Good, f32)>,
|
||||||
@ -81,8 +78,11 @@ lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ProbabilityFile {
|
/// A collection of items with probabilty (normalized to one), created
|
||||||
pub content: Vec<(f32, String)>,
|
/// hierarchically from `LootSpec`s
|
||||||
|
/// (probability, item id, average amount)
|
||||||
|
pub struct ProbabilityFile {
|
||||||
|
pub content: Vec<(f32, String, f32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl assets::Asset for ProbabilityFile {
|
impl assets::Asset for ProbabilityFile {
|
||||||
@ -94,22 +94,25 @@ impl assets::Asset for ProbabilityFile {
|
|||||||
impl From<Vec<(f32, LootSpec<String>)>> for ProbabilityFile {
|
impl From<Vec<(f32, LootSpec<String>)>> for ProbabilityFile {
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
fn from(content: Vec<(f32, LootSpec<String>)>) -> Self {
|
fn from(content: Vec<(f32, LootSpec<String>)>) -> Self {
|
||||||
|
let rescale = if content.is_empty() {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
1.0 / content.iter().map(|e| e.0).sum::<f32>()
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
content: content
|
content: content
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|(p0, loot)| match loot {
|
.flat_map(|(p0, loot)| match loot {
|
||||||
LootSpec::Item(asset) => vec![(p0, asset)].into_iter(),
|
LootSpec::Item(asset) => vec![(p0 * rescale, asset, 1.0)].into_iter(),
|
||||||
LootSpec::ItemQuantity(asset, a, b) => {
|
LootSpec::ItemQuantity(asset, a, b) => {
|
||||||
vec![(p0 * (a + b) as f32 / 2.0, asset)].into_iter()
|
vec![(p0 * rescale, asset, (a + b) as f32 * 0.5)].into_iter()
|
||||||
},
|
},
|
||||||
LootSpec::LootTable(table_asset) => {
|
LootSpec::LootTable(table_asset) => {
|
||||||
let total = Lottery::<LootSpec<String>>::load_expect(&table_asset)
|
let unscaled = &Self::load_expect(&table_asset).read().content;
|
||||||
.read()
|
let scale = p0 * rescale;
|
||||||
.total();
|
unscaled
|
||||||
Self::load_expect_cloned(&table_asset)
|
.iter()
|
||||||
.content
|
.map(|(p1, asset, amount)| (*p1 * scale, asset.clone(), *amount))
|
||||||
.into_iter()
|
|
||||||
.map(|(p1, asset)| (p0 * p1 / total, asset))
|
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
},
|
},
|
||||||
@ -171,7 +174,7 @@ impl assets::Compound for EqualitySet {
|
|||||||
EqualitySpec::LootTable(table) => {
|
EqualitySpec::LootTable(table) => {
|
||||||
let acc = &ProbabilityFile::load_expect(table).read().content;
|
let acc = &ProbabilityFile::load_expect(table).read().content;
|
||||||
|
|
||||||
acc.iter().map(|(_p, item)| item).cloned().collect()
|
acc.iter().map(|(_p, item, _)| item).cloned().collect()
|
||||||
},
|
},
|
||||||
EqualitySpec::Set(xs) => xs.clone(),
|
EqualitySpec::Set(xs) => xs.clone(),
|
||||||
};
|
};
|
||||||
@ -200,7 +203,11 @@ struct RememberedRecipe {
|
|||||||
|
|
||||||
fn sort_and_normalize(entryvec: &mut [Entry], scale: f32) {
|
fn sort_and_normalize(entryvec: &mut [Entry], scale: f32) {
|
||||||
if !entryvec.is_empty() {
|
if !entryvec.is_empty() {
|
||||||
entryvec.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
|
entryvec.sort_by(|a, b| {
|
||||||
|
a.1.partial_cmp(&b.1)
|
||||||
|
.unwrap_or(Ordering::Equal)
|
||||||
|
.then_with(|| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal))
|
||||||
|
});
|
||||||
if let Some((_, max_scale, _)) = entryvec.last() {
|
if let Some((_, max_scale, _)) = entryvec.last() {
|
||||||
// most common item has frequency max_scale. avoid NaN
|
// most common item has frequency max_scale. avoid NaN
|
||||||
let rescale = scale / max_scale;
|
let rescale = scale / max_scale;
|
||||||
@ -359,11 +366,11 @@ impl TradePricing {
|
|||||||
}
|
}
|
||||||
let (frequency, can_sell, asset_path) = table;
|
let (frequency, can_sell, asset_path) = table;
|
||||||
let loot = ProbabilityFile::load_expect(asset_path);
|
let loot = ProbabilityFile::load_expect(asset_path);
|
||||||
for (p, item_asset) in &loot.read().content {
|
for (p, item_asset, amount) in &loot.read().content {
|
||||||
result.get_list_by_path_mut(item_asset).add(
|
result.get_list_by_path_mut(item_asset).add(
|
||||||
&eqset,
|
&eqset,
|
||||||
item_asset,
|
item_asset,
|
||||||
frequency * p,
|
frequency * p * *amount,
|
||||||
*can_sell,
|
*can_sell,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -475,7 +482,6 @@ impl TradePricing {
|
|||||||
loop {
|
loop {
|
||||||
let index =
|
let index =
|
||||||
(rand::random::<f32>() * ((upper - lower) as f32)).floor() as usize + lower;
|
(rand::random::<f32>() * ((upper - lower) as f32)).floor() as usize + lower;
|
||||||
//.gen_range(lower..upper);
|
|
||||||
if table.get(index).map_or(false, |i| !selling || i.2) {
|
if table.get(index).map_or(false, |i| !selling || i.2) {
|
||||||
break table.get(index).map(|i| i.0.clone());
|
break table.get(index).map(|i| i.0.clone());
|
||||||
}
|
}
|
||||||
@ -510,124 +516,225 @@ impl TradePricing {
|
|||||||
use crate::comp::item::{armor, tool, Item, ItemKind};
|
use crate::comp::item::{armor, tool, Item, ItemKind};
|
||||||
|
|
||||||
// we pass the item and the inverse of the price to the closure
|
// we pass the item and the inverse of the price to the closure
|
||||||
fn printvec<F>(good_kind: &str, entries: &[(String, f32, bool)], f: F)
|
fn printvec<F>(good_kind: &str, entries: &[(String, f32, bool)], f: F, unit: &str)
|
||||||
where
|
where
|
||||||
F: Fn(&Item, f32) -> String,
|
F: Fn(&Item, f32) -> String,
|
||||||
{
|
{
|
||||||
println!("\n======{:^15}======", good_kind);
|
|
||||||
for (item_id, p, can_sell) in entries.iter() {
|
for (item_id, p, can_sell) in entries.iter() {
|
||||||
let it = Item::new_from_asset_expect(item_id);
|
let it = Item::new_from_asset_expect(item_id);
|
||||||
let price = 1.0 / p;
|
let price = 1.0 / p;
|
||||||
println!(
|
println!(
|
||||||
"<{}> {}\n{:>4.2} {:?} {}",
|
"{}, {}, {:>4.2}, {}, {:?}, {}, {},",
|
||||||
item_id,
|
item_id,
|
||||||
if *can_sell { "+" } else { "-" },
|
if *can_sell { "yes" } else { "no" },
|
||||||
price,
|
price,
|
||||||
|
good_kind,
|
||||||
it.quality,
|
it.quality,
|
||||||
f(&it, *p)
|
f(&it, *p),
|
||||||
|
unit,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printvec("Armor", &self.armor.entries, |i, p| {
|
println!("Item, ForSale, Amount, Good, Quality, Deal, Unit,");
|
||||||
if let ItemKind::Armor(a) = &i.kind {
|
|
||||||
match a.protection() {
|
printvec(
|
||||||
Some(armor::Protection::Invincible) => "Invincible".into(),
|
"Armor",
|
||||||
Some(armor::Protection::Normal(x)) => format!("{:.4} prot/val", x * p),
|
&self.armor.entries,
|
||||||
None => "0.0 prot/val".into(),
|
|i, p| {
|
||||||
|
if let ItemKind::Armor(a) = &i.kind {
|
||||||
|
match a.protection() {
|
||||||
|
Some(armor::Protection::Invincible) => "Invincible".into(),
|
||||||
|
Some(armor::Protection::Normal(x)) => format!("{:.4}", x * p),
|
||||||
|
None => "0.0".into(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("{:?}", i.kind)
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
format!("{:?}", i.kind)
|
"prot/val",
|
||||||
}
|
);
|
||||||
});
|
printvec(
|
||||||
printvec("Tools", &self.tools.entries, |i, p| {
|
"Tools",
|
||||||
if let ItemKind::Tool(t) = &i.kind {
|
&self.tools.entries,
|
||||||
match &t.stats {
|
|i, p| {
|
||||||
tool::StatKind::Direct(d) => {
|
if let ItemKind::Tool(t) = &i.kind {
|
||||||
format!("{:.4} dps/val", d.power * d.speed * p)
|
match &t.stats {
|
||||||
},
|
tool::StatKind::Direct(d) => {
|
||||||
tool::StatKind::Modular => "Modular".into(),
|
format!("{:.4}", d.power * d.speed * p)
|
||||||
|
},
|
||||||
|
tool::StatKind::Modular => "Modular".into(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("{:?}", i.kind)
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
format!("{:?}", i.kind)
|
"dps/val",
|
||||||
}
|
);
|
||||||
});
|
printvec(
|
||||||
printvec("Potions", &self.potions.entries, |i, p| {
|
"Potions",
|
||||||
if let ItemKind::Consumable { kind: _, effects } = &i.kind {
|
&self.potions.entries,
|
||||||
effects
|
|i, p| {
|
||||||
.iter()
|
if let ItemKind::Consumable { kind: _, effects } = &i.kind {
|
||||||
.map(|e| {
|
effects
|
||||||
if let crate::effect::Effect::Buff(b) = e {
|
.iter()
|
||||||
format!("{:.2} str/val", b.data.strength * p)
|
.map(|e| {
|
||||||
} else {
|
if let crate::effect::Effect::Buff(b) = e {
|
||||||
format!("{:?}", e)
|
format!("{:.2}", b.data.strength * p)
|
||||||
}
|
} else {
|
||||||
})
|
format!("{:?}", e)
|
||||||
.collect::<Vec<String>>()
|
}
|
||||||
.join(" ")
|
})
|
||||||
} else {
|
.collect::<Vec<String>>()
|
||||||
format!("{:?}", i.kind)
|
.join(" ")
|
||||||
}
|
} else {
|
||||||
});
|
format!("{:?}", i.kind)
|
||||||
printvec("Food", &self.food.entries, |i, p| {
|
}
|
||||||
if let ItemKind::Consumable { kind: _, effects } = &i.kind {
|
},
|
||||||
effects
|
"str/val",
|
||||||
.iter()
|
);
|
||||||
.map(|e| {
|
printvec(
|
||||||
if let crate::effect::Effect::Buff(b) = e {
|
"Food",
|
||||||
format!("{:.2} str/val", b.data.strength * p)
|
&self.food.entries,
|
||||||
} else {
|
|i, p| {
|
||||||
format!("{:?}", e)
|
if let ItemKind::Consumable { kind: _, effects } = &i.kind {
|
||||||
}
|
effects
|
||||||
})
|
.iter()
|
||||||
.collect::<Vec<String>>()
|
.map(|e| {
|
||||||
.join(" ")
|
if let crate::effect::Effect::Buff(b) = e {
|
||||||
} else {
|
format!("{:.2}", b.data.strength * p)
|
||||||
format!("{:?}", i.kind)
|
} else {
|
||||||
}
|
format!("{:?}", e)
|
||||||
});
|
}
|
||||||
printvec("Ingredients", &self.ingredients.entries, |i, _p| {
|
})
|
||||||
if let ItemKind::Ingredient { kind } = &i.kind {
|
.collect::<Vec<String>>()
|
||||||
kind.clone()
|
.join(" ")
|
||||||
} else {
|
} else {
|
||||||
format!("{:?}", i.kind)
|
format!("{:?}", i.kind)
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
printvec("Other", &self.other.entries, |i, _p| {
|
"str/val",
|
||||||
format!("{:?}", i.kind)
|
);
|
||||||
});
|
printvec(
|
||||||
println!("<{}>\n{}", Self::COIN_ITEM, self.coin_scale);
|
"Ingredients",
|
||||||
|
&self.ingredients.entries,
|
||||||
|
|_i, _p| String::new(),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
printvec("Other", &self.other.entries, |_i, _p| String::new(), "");
|
||||||
|
println!("{}, yes, {}, Coin, ,,,", Self::COIN_ITEM, self.coin_scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// hierarchically combine and scale this loot table
|
||||||
|
#[must_use]
|
||||||
|
pub fn expand_loot_table(loot_table: &str) -> Vec<(f32, String, f32)> {
|
||||||
|
ProbabilityFile::from(vec![(1.0, LootSpec::LootTable(loot_table.into()))]).content
|
||||||
|
}
|
||||||
|
|
||||||
// if you want to take a look at the calculated values run:
|
// if you want to take a look at the calculated values run:
|
||||||
// cd common && cargo test trade_pricing -- --nocapture
|
// cd common && cargo test trade_pricing -- --nocapture
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{comp::inventory::trade_pricing::TradePricing, trade::Good};
|
use crate::{
|
||||||
use tracing::{info, Level};
|
comp::inventory::trade_pricing::{expand_loot_table, ProbabilityFile, TradePricing},
|
||||||
use tracing_subscriber::{
|
lottery::LootSpec,
|
||||||
filter::{EnvFilter, LevelFilter},
|
trade::Good,
|
||||||
FmtSubscriber,
|
|
||||||
};
|
};
|
||||||
|
use tracing::{info, Level};
|
||||||
|
use tracing_subscriber::{filter::EnvFilter, FmtSubscriber};
|
||||||
|
|
||||||
fn init() {
|
fn init() {
|
||||||
FmtSubscriber::builder()
|
FmtSubscriber::builder()
|
||||||
.with_max_level(Level::ERROR)
|
.with_max_level(Level::ERROR)
|
||||||
.with_env_filter(EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into()))
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
.init();
|
.try_init()
|
||||||
|
.unwrap_or(());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_prices() {
|
fn test_loot_table() {
|
||||||
|
init();
|
||||||
|
info!("init");
|
||||||
|
|
||||||
|
let loot = expand_loot_table("common.loot_tables.creature.quad_medium.gentle");
|
||||||
|
let lootsum = loot.iter().fold(0.0, |s, i| s + i.0);
|
||||||
|
assert!((lootsum - 1.0).abs() < 1e-3);
|
||||||
|
// hierarchical
|
||||||
|
let loot2 = expand_loot_table("common.loot_tables.creature.quad_medium.catoblepas");
|
||||||
|
let lootsum2 = loot2.iter().fold(0.0, |s, i| s + i.0);
|
||||||
|
assert!((lootsum2 - 1.0).abs() < 1e-4);
|
||||||
|
|
||||||
|
// highly nested
|
||||||
|
let loot3 = expand_loot_table("common.loot_tables.creature.biped_large.wendigo");
|
||||||
|
let lootsum3 = loot3.iter().fold(0.0, |s, i| s + i.0);
|
||||||
|
assert!((lootsum3 - 1.0).abs() < 1e-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prices1() {
|
||||||
init();
|
init();
|
||||||
info!("init");
|
info!("init");
|
||||||
|
|
||||||
TradePricing::instance().print_sorted();
|
TradePricing::instance().print_sorted();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prices2() {
|
||||||
|
init();
|
||||||
|
info!("init");
|
||||||
|
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
if let Some(item_id) = TradePricing::random_item(Good::Armor, 5.0, false) {
|
if let Some(item_id) = TradePricing::random_item(Good::Armor, 5.0, false) {
|
||||||
info!("Armor 5 {}", item_id);
|
info!("Armor 5 {}", item_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalized(probability: &ProbabilityFile) -> bool {
|
||||||
|
let sum = probability.content.iter().map(|(p, _, _)| p).sum::<f32>();
|
||||||
|
(dbg!(sum) - 1.0).abs() < 1e-3
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normalizing_table1() {
|
||||||
|
let item = |asset: &str| LootSpec::Item(asset.to_owned());
|
||||||
|
let loot_table = vec![(1.0, item("wow")), (1.0, item("nice"))];
|
||||||
|
|
||||||
|
let probability: ProbabilityFile = loot_table.into();
|
||||||
|
assert!(normalized(&probability));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normalizing_table2() {
|
||||||
|
let table = |asset: &str| LootSpec::LootTable(asset.to_owned());
|
||||||
|
let loot_table = vec![(
|
||||||
|
1.0,
|
||||||
|
table("common.loot_tables.creature.quad_medium.catoblepas"),
|
||||||
|
)];
|
||||||
|
let probability: ProbabilityFile = loot_table.into();
|
||||||
|
assert!(normalized(&probability));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normalizing_table3() {
|
||||||
|
let table = |asset: &str| LootSpec::LootTable(asset.to_owned());
|
||||||
|
let loot_table = vec![
|
||||||
|
(
|
||||||
|
1.0,
|
||||||
|
table("common.loot_tables.creature.quad_medium.catoblepas"),
|
||||||
|
),
|
||||||
|
(1.0, table("common.loot_tables.creature.quad_medium.gentle")),
|
||||||
|
];
|
||||||
|
let probability: ProbabilityFile = loot_table.into();
|
||||||
|
assert!(normalized(&probability));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normalizing_table4() {
|
||||||
|
let quantity = |asset: &str, a, b| LootSpec::ItemQuantity(asset.to_owned(), a, b);
|
||||||
|
let loot_table = vec![(1.0, quantity("such", 3, 5)), (1.0, quantity("much", 5, 9))];
|
||||||
|
let probability: ProbabilityFile = loot_table.into();
|
||||||
|
assert!(normalized(&probability));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user