Make ItemEquality work

+ Create own directory for trade_pricing `assets/common/trading`
+ Move fictive loot tables there
+ Mark sellable/non-sellable items in trade_pricing tests
This commit is contained in:
juliancoffee 2021-07-03 15:19:21 +03:00
parent 28c9ea029b
commit 0640902b7f
12 changed files with 159 additions and 69 deletions

View File

@ -1,4 +0,0 @@
[
["common.items.weapons.sceptre.caduceus", "common.items.weapons.sceptre.root_evil"],
["common.items.weapons.staff.laevateinn", "common.items.weapons.staff.phoenix" ],
]

View File

@ -1,10 +0,0 @@
// Loot table that exists purely for price rationalisation
// Put here anything that doesn't fit in anything else.
//
// This loot table should be marked as un-sellable.
// If you want to add something that merchants can buy and sell,
// add another loot table
//
// Please keep it sorted by rarity so it's easier to reason about things
[
]

View File

@ -531,7 +531,6 @@
inputs: [ inputs: [
(Item("common.items.crafting_ing.leather.leather_strips"), 4), (Item("common.items.crafting_ing.leather.leather_strips"), 4),
(Item("common.items.crafting_ing.twigs"), 10), (Item("common.items.crafting_ing.twigs"), 10),
(Item("common.items.mineral.ore.veloritefrag"), 1),
], ],
craft_sprite: Some(Anvil), craft_sprite: Some(Anvil),
is_recycling: false, is_recycling: false,

View File

@ -0,0 +1,19 @@
// Loot table that exists purely for price rationalisation
//
// NOTE: it shouln't be considered source of resources.
// Place here things which have greater prices than intended.
//
// This loot table should be marked as un-sellable.
// If you want to add something that merchants can buy and sell,
// add another loot table
//
// Please keep it sorted by rarity so it's easier to reason about things
[
// Mostly tools here because crafting turn rarity from input ingredients,
// and move it to output, which isn't desired
(1.0, Item("common.items.tool.pickaxe_steel")),
(20.0, Item("common.items.tool.craftsman_hammer")),
(20.0, Item("common.items.tool.pickaxe_stone")),
(20.0, Item("common.items.weapons.hammer.burnt_drumstick")),
(20.0, Item("common.items.weapons.sword.wood-2")),
]

View File

@ -7,16 +7,16 @@
// Please keep it sorted by rarity so it's easier to reason about things // Please keep it sorted by rarity so it's easier to reason about things
[ [
// Meats // Meats
(0.0125, Item("common.items.food.meat.bird_large_raw")), (0.125, Item("common.items.food.meat.bird_large_raw")),
(0.5, Item("common.items.food.meat.beast_large_raw")), (0.5, Item("common.items.food.meat.beast_large_raw")),
(2.0, Item("common.items.food.meat.bird_raw")), (2.0, Item("common.items.food.meat.bird_raw")),
(2.0, Item("common.items.food.meat.fish_raw")), (2.0, Item("common.items.food.meat.fish_raw")),
(2.0, Item("common.items.food.meat.tough_raw")), (2.0, Item("common.items.food.meat.tough_raw")),
(4.0, Item("common.items.food.meat.beast_small_raw")), (4.0, Item("common.items.food.meat.beast_small_raw")),
// Gatherables // Gatherables
(0.025, Item("common.items.food.coconut")), (0.25, Item("common.items.food.coconut")),
(0.25, Item("common.items.food.cheese")), (0.5, Item("common.items.food.cheese")),
(0.25, Item("common.items.food.apple")), (1.0, Item("common.items.food.apple")),
(2.0, Item("common.items.food.carrot")), (2.0, Item("common.items.food.carrot")),
(2.0, Item("common.items.food.lettuce")), (2.0, Item("common.items.food.lettuce")),
(2.0, Item("common.items.food.tomato")), (2.0, Item("common.items.food.tomato")),

View File

@ -3,29 +3,31 @@ 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"), (32.0, true, "common.loot_tables.weapons.starter"),
(16.0, true, "common.loot_tables.weapons.tier-0"),
(8.0, true, "common.loot_tables.weapons.tier-1"),
(4.0, true, "common.loot_tables.weapons.tier-2"),
(2.0, true, "common.loot_tables.weapons.tier-3"),
(1.0, false, "common.loot_tables.weapons.tier-4"),
(0.5, false, "common.loot_tables.weapons.tier-5"),
(0.025, false, "common.loot_tables.weapons.cultist"), (0.025, false, "common.loot_tables.weapons.cultist"),
(0.025, false, "common.loot_tables.weapons.cave"), (0.025, false, "common.loot_tables.weapons.cave"),
(0.02, false, "common.loot_tables.weapons.legendary"), (0.02, false, "common.loot_tables.weapons.legendary"),
// Weapons sets
(16.0, true, "common.loot_tables.weapons.tier-0"),
(8.0, true, "common.loot_tables.weapons.tier-1"),
(1.0, true, "common.loot_tables.weapons.tier-2"),
(0.125, true, "common.loot_tables.weapons.tier-3"),
(0.0625, false, "common.loot_tables.weapons.tier-4"),
(0.03, false, "common.loot_tables.weapons.tier-5"),
// Non-craftable Armor // Non-craftable Armor
(20.0, true, "common.loot_tables.armor.cloth"), (20.0, true, "common.loot_tables.armor.cloth"),
(1.0, true, "common.loot_tables.armor.twigs"), (1.0, true, "common.loot_tables.armor.twigs"),
(1.0, true, "common.loot_tables.armor.twigsflowers"), (1.0, true, "common.loot_tables.armor.twigsflowers"),
(1.0, true, "common.loot_tables.armor.twigsleaves"), (1.0, true, "common.loot_tables.armor.twigsleaves"),
(0.01, false, "common.loot_tables.trading.jewellery"), (0.01, false, "common.trading.jewellery"),
// Ingredients // Ingredients
(1.0, true, "common.loot_tables.trading.sellable_materials"), (1.0, true, "common.trading.sellable_materials"),
(1.0, false, "common.loot_tables.trading.unsellable_materials"), (1.0, false, "common.trading.unsellable_materials"),
// Food Ingredients // Food Ingredients
(1.0, true, "common.loot_tables.trading.food"), (1.0, true, "common.trading.food"),
// Potions // Potions
// //
@ -39,14 +41,16 @@ loot_tables: [
// Misc // Misc
(0.1, true, "common.loot_tables.consumable.throwable"), (0.1, true, "common.loot_tables.consumable.throwable"),
// Collections // Collections
(0.00001, false, "common.loot_tables.trading.collection"), (0.00001, false, "common.trading.collection"),
// Manual balance
(1.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
// so basically this table balances the goods against each other (higher=less valuable) // so basically this table balances the goods against each other (higher=less valuable)
good_scaling: [ good_scaling: [
(Potions, 0.005), // common.items.consumable.potion_minor (Potions, 0.005), // common.items.consumable.potion_minor
(Food, 0.5), // common.items.food.mushroom (Food, 0.15), // common.items.food.mushroom
(Coin, 1.0), // common.items.utility.coins (Coin, 1.0), // common.items.utility.coins
(Armor, 0.5), // common.items.armor.misc.pants.worker_blue (Armor, 0.5), // common.items.armor.misc.pants.worker_blue
(Tools, 0.5), // common.items.weapons.staff.starter_staff (Tools, 0.5), // common.items.weapons.staff.starter_staff

View File

@ -0,0 +1,58 @@
[
// Tier-0 weapons
LootTable("common.loot_tables.weapons.sword.stone"),
LootTable("common.loot_tables.weapons.axe.stone"),
LootTable("common.loot_tables.weapons.hammer.stone"),
LootTable("common.loot_tables.weapons.bow.wood"),
LootTable("common.loot_tables.weapons.staff.wood"),
// Tier-1 weapons
LootTable("common.loot_tables.weapons.sword.bronze"),
LootTable("common.loot_tables.weapons.axe.bronze"),
LootTable("common.loot_tables.weapons.hammer.bronze"),
LootTable("common.loot_tables.weapons.bow.bone"),
LootTable("common.loot_tables.weapons.staff.bamboo"),
LootTable("common.loot_tables.weapons.sceptre.bamboo"),
// Tier-2 weapons
LootTable("common.loot_tables.weapons.sword.iron"),
LootTable("common.loot_tables.weapons.axe.iron"),
LootTable("common.loot_tables.weapons.hammer.iron"),
LootTable("common.loot_tables.weapons.bow.hardwood"),
LootTable("common.loot_tables.weapons.staff.hardwood"),
LootTable("common.loot_tables.weapons.sceptre.hardwood"),
// Tier-3 weapons
LootTable("common.loot_tables.weapons.sword.steel"),
LootTable("common.loot_tables.weapons.axe.steel"),
LootTable("common.loot_tables.weapons.hammer.steel"),
LootTable("common.loot_tables.weapons.bow.metal"),
LootTable("common.loot_tables.weapons.staff.ironwood"),
LootTable("common.loot_tables.weapons.sceptre.ironwood"),
// Tier-4 weapons
LootTable("common.loot_tables.weapons.sword.cobalt"),
LootTable("common.loot_tables.weapons.axe.cobalt"),
LootTable("common.loot_tables.weapons.hammer.cobalt"),
LootTable("common.loot_tables.weapons.bow.frostwood"),
LootTable("common.loot_tables.weapons.staff.frostwood"),
LootTable("common.loot_tables.weapons.sceptre.frostwood"),
// Tier-5 weapons
LootTable("common.loot_tables.weapons.sword.bloodsteel"),
LootTable("common.loot_tables.weapons.axe.bloodsteel"),
LootTable("common.loot_tables.weapons.hammer.bloodsteel"),
LootTable("common.loot_tables.weapons.bow.eldwood"),
LootTable("common.loot_tables.weapons.staff.eldwood"),
LootTable("common.loot_tables.weapons.sceptre.eldwood"),
// Legendary staves and sceptres
Set([
"common.items.weapons.staff.phoenix",
"common.items.weapons.staff.laevateinn",
]),
Set([
"common.items.weapons.sceptre.root_evil",
"common.items.weapons.sceptre.caduceus",
]),
]

View File

@ -30,9 +30,8 @@
// Mob Drops // Mob Drops
(0.15, Item("common.items.crafting_ing.animal_misc.grim_eyeball")), (0.15, Item("common.items.crafting_ing.animal_misc.grim_eyeball")),
(0.15, Item("common.items.crafting_ing.animal_misc.lively_vine")), (0.15, Item("common.items.crafting_ing.animal_misc.icy_fang")),
(0.5, Item("common.items.crafting_ing.animal_misc.raptor_feather")), (0.5, Item("common.items.crafting_ing.animal_misc.raptor_feather")),
(1.0, Item("common.items.crafting_ing.animal_misc.icy_fang")),
(1.2, Item("common.items.crafting_ing.animal_misc.claw")), (1.2, Item("common.items.crafting_ing.animal_misc.claw")),
(2.5, Item("common.items.crafting_ing.animal_misc.fur")), (2.5, Item("common.items.crafting_ing.animal_misc.fur")),
(2.5, Item("common.items.crafting_ing.animal_misc.sharp_fang")), (2.5, Item("common.items.crafting_ing.animal_misc.sharp_fang")),
@ -48,10 +47,10 @@
(0.1, Item("common.items.crafting_ing.seashells")), (0.1, Item("common.items.crafting_ing.seashells")),
(0.2, Item("common.items.crafting_ing.honey")), (0.2, Item("common.items.crafting_ing.honey")),
(0.2, Item("common.items.flowers.moonbell")), (0.2, Item("common.items.flowers.moonbell")),
(1.0, Item("common.items.crafting_ing.cactus")),
(1.0, Item("common.items.crafting_ing.cotton_boll")), (1.0, Item("common.items.crafting_ing.cotton_boll")),
(1.0, Item("common.items.flowers.pyrebloom")), (1.0, Item("common.items.flowers.pyrebloom")),
(3.0, Item("common.items.crafting_ing.twigs")), (3.0, Item("common.items.crafting_ing.twigs")),
(4.0, Item("common.items.crafting_ing.cactus")),
(4.0, Item("common.items.flowers.red")), (4.0, Item("common.items.flowers.red")),
(4.0, Item("common.items.flowers.wild_flax")), (4.0, Item("common.items.flowers.wild_flax")),
(9.0, Item("common.items.flowers.sunflower")), (9.0, Item("common.items.flowers.sunflower")),

View File

@ -25,6 +25,7 @@
(0.005, Item("common.items.crafting_ing.animal_misc.phoenix_feather")), (0.005, Item("common.items.crafting_ing.animal_misc.phoenix_feather")),
(0.08, Item("common.items.crafting_ing.animal_misc.large_horn")), (0.08, Item("common.items.crafting_ing.animal_misc.large_horn")),
(0.08, Item("common.items.crafting_ing.animal_misc.lively_vine")), (0.08, Item("common.items.crafting_ing.animal_misc.lively_vine")),
(0.15, Item("common.items.crafting_ing.animal_misc.lively_vine")),
(0.225, Item("common.items.crafting_ing.sticky_thread")), (0.225, Item("common.items.crafting_ing.sticky_thread")),
// Ultra Rare drops // Ultra Rare drops

View File

@ -44,10 +44,7 @@ struct Entries {
impl Entries { impl Entries {
fn add(&mut self, eqset: &EqualitySet, item_name: &str, probability: f32, can_sell: bool) { fn add(&mut self, eqset: &EqualitySet, item_name: &str, probability: f32, can_sell: bool) {
let canonical_itemname = eqset let canonical_itemname = eqset.canonical(item_name);
.equivalence_class
.get(item_name)
.map_or(item_name, |i| &**i);
let old = self let old = self
.entries .entries
@ -66,10 +63,15 @@ impl Entries {
} }
self.entries self.entries
.push((canonical_itemname.to_owned(), probability, can_sell)); .push((canonical_itemname.to_owned(), probability, can_sell));
if canonical_itemname != item_name {
// Add the non-canonical item so that it'll show up in merchant inventories
self.entries.push((item_name.to_owned(), 0.0, can_sell));
} }
// Add the non-canonical item so that it'll show up in merchant inventories
// It will have infinity as its price, but it's fine,
// because we determine all prices based on canonical value
if canonical_itemname != item_name
&& !self.entries.iter().any(|(name, _, _)| name == item_name)
{
self.entries.push((item_name.to_owned(), 0.0, can_sell));
} }
} }
} }
@ -136,27 +138,54 @@ struct EqualitySet {
equivalence_class: HashMap<String, String>, equivalence_class: HashMap<String, String>,
} }
impl EqualitySet {
fn canonical<'a>(&'a self, item_name: &'a str) -> &'a str {
let canonical_itemname = self
.equivalence_class
.get(item_name)
.map_or(item_name, |i| &**i);
canonical_itemname
}
}
impl assets::Compound for EqualitySet { impl assets::Compound for EqualitySet {
fn load<S: assets::source::Source>( fn load<S: assets::source::Source>(
cache: &assets::AssetCache<S>, cache: &assets::AssetCache<S>,
id: &str, id: &str,
) -> Result<Self, assets::Error> { ) -> Result<Self, assets::Error> {
let manifest = cache.load::<assets::Ron<Vec<Vec<String>>>>(id)?; #[derive(Debug, Deserialize)]
let mut ret = Self { enum EqualitySpec {
LootTable(String),
Set(Vec<String>),
}
let mut eqset = Self {
equivalence_class: HashMap::new(), equivalence_class: HashMap::new(),
}; };
for set in &manifest.read().0 {
let mut iter = set.iter(); let manifest = &cache.load::<assets::Ron<Vec<EqualitySpec>>>(id)?.read().0;
for set in manifest {
let items = match set {
EqualitySpec::LootTable(table) => {
let acc = &ProbabilityFile::load_expect(table).read().content;
acc.iter().map(|(_p, item)| item).cloned().collect()
},
EqualitySpec::Set(xs) => xs.clone(),
};
let mut iter = items.iter();
if let Some(first) = iter.next() { if let Some(first) = iter.next() {
let first = first.to_string(); let first = first.to_string();
ret.equivalence_class.insert(first.clone(), first.clone()); eqset.equivalence_class.insert(first.clone(), first.clone());
for item in iter { for item in iter {
ret.equivalence_class eqset
.equivalence_class
.insert(item.to_string(), first.clone()); .insert(item.to_string(), first.clone());
} }
} }
} }
Ok(ret) Ok(eqset)
} }
} }
@ -278,10 +307,7 @@ impl TradePricing {
// look up price (inverse frequency) of an item // look up price (inverse frequency) of an item
fn price_lookup(&self, eqset: &EqualitySet, requested_name: &str) -> f32 { fn price_lookup(&self, eqset: &EqualitySet, requested_name: &str) -> f32 {
let canonical_name = eqset let canonical_name = eqset.canonical(requested_name);
.equivalence_class
.get(requested_name)
.map_or(requested_name, |name| &**name);
let goods = self.get_list_by_path(canonical_name); let goods = self.get_list_by_path(canonical_name);
// even if we multiply by INVEST_FACTOR we need to remain // even if we multiply by INVEST_FACTOR we need to remain
@ -322,8 +348,9 @@ impl TradePricing {
#[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_precision_loss)]
fn read() -> Self { fn read() -> Self {
let mut result = Self::default(); let mut result = Self::default();
let price_config = TradingPriceFile::load_expect("common.item_price_calculation").read(); let price_config =
let eqset = EqualitySet::load_expect("common.item_price_equality").read(); TradingPriceFile::load_expect("common.trading.item_price_calculation").read();
let eqset = EqualitySet::load_expect("common.trading.item_price_equality").read();
result.equality_set = eqset.clone(); result.equality_set = eqset.clone();
for table in &price_config.loot_tables { for table in &price_config.loot_tables {
if PRICING_DEBUG { if PRICING_DEBUG {
@ -382,7 +409,7 @@ impl TradePricing {
let actual_cost = result.calculate_material_cost(recipe, &eqset); let actual_cost = result.calculate_material_cost(recipe, &eqset);
let output_tradeable = recipe.input.iter().all(|(input, _)| { let output_tradeable = recipe.input.iter().all(|(input, _)| {
result result
.get_list_by_path(&input) .get_list_by_path(input)
.iter() .iter()
.find(|(item, _, _)| item == input) .find(|(item, _, _)| item == input)
.map_or(false, |(_, _, tradeable)| *tradeable) .map_or(false, |(_, _, tradeable)| *tradeable)
@ -468,11 +495,7 @@ impl TradePricing {
if item == Self::COIN_ITEM { if item == Self::COIN_ITEM {
(Good::Coin, 1.0) (Good::Coin, 1.0)
} else { } else {
let item = TRADE_PRICING let item = TRADE_PRICING.equality_set.canonical(item);
.equality_set
.equivalence_class
.get(item)
.map_or(item, |i| &**i);
TRADE_PRICING.material_cache.get(item).copied().map_or( TRADE_PRICING.material_cache.get(item).copied().map_or(
(Good::Terrain(crate::terrain::BiomeKind::Void), 0.0), (Good::Terrain(crate::terrain::BiomeKind::Void), 0.0),
@ -489,20 +512,21 @@ 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>(x: &str, e: &[(String, f32, bool)], f: F) fn printvec<F>(good_kind: &str, entries: &[(String, f32, bool)], f: F)
where where
F: Fn(&Item, f32) -> String, F: Fn(&Item, f32) -> String,
{ {
println!("\n======{:^15}======", x); println!("\n======{:^15}======", good_kind);
for i in e.iter() { for (item_id, p, can_sell) in entries.iter() {
let it = Item::new_from_asset_expect(&i.0); let it = Item::new_from_asset_expect(item_id);
let price = 1.0 / i.1; let price = 1.0 / p;
println!( println!(
"<{}>\n{:>4.2} {:?} {}", "<{}> {}\n{:>4.2} {:?} {}",
i.0, item_id,
if *can_sell { "+" } else { "-" },
price, price,
it.quality, it.quality,
f(&it, i.1) f(&it, *p)
); );
} }
} }