diff --git a/assets/common/item_price_equality.ron b/assets/common/item_price_equality.ron deleted file mode 100644 index 76dd365a0d..0000000000 --- a/assets/common/item_price_equality.ron +++ /dev/null @@ -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" ], -] diff --git a/assets/common/loot_tables/trading/buying.ron b/assets/common/loot_tables/trading/buying.ron deleted file mode 100644 index 31bfd830c9..0000000000 --- a/assets/common/loot_tables/trading/buying.ron +++ /dev/null @@ -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 -[ -] diff --git a/assets/common/recipe_book.ron b/assets/common/recipe_book.ron index cb6cb55f72..5a1f2a8515 100644 --- a/assets/common/recipe_book.ron +++ b/assets/common/recipe_book.ron @@ -531,7 +531,6 @@ inputs: [ (Item("common.items.crafting_ing.leather.leather_strips"), 4), (Item("common.items.crafting_ing.twigs"), 10), - (Item("common.items.mineral.ore.veloritefrag"), 1), ], craft_sprite: Some(Anvil), is_recycling: false, diff --git a/assets/common/trading/balance.ron b/assets/common/trading/balance.ron new file mode 100644 index 0000000000..02ad4990b4 --- /dev/null +++ b/assets/common/trading/balance.ron @@ -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")), +] diff --git a/assets/common/loot_tables/trading/collection.ron b/assets/common/trading/collection.ron similarity index 100% rename from assets/common/loot_tables/trading/collection.ron rename to assets/common/trading/collection.ron diff --git a/assets/common/loot_tables/trading/food.ron b/assets/common/trading/food.ron similarity index 79% rename from assets/common/loot_tables/trading/food.ron rename to assets/common/trading/food.ron index e98d52abf0..a220506326 100644 --- a/assets/common/loot_tables/trading/food.ron +++ b/assets/common/trading/food.ron @@ -7,16 +7,16 @@ // Please keep it sorted by rarity so it's easier to reason about things [ // 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")), (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.tough_raw")), (4.0, Item("common.items.food.meat.beast_small_raw")), // Gatherables - (0.025, Item("common.items.food.coconut")), - (0.25, Item("common.items.food.cheese")), - (0.25, Item("common.items.food.apple")), + (0.25, Item("common.items.food.coconut")), + (0.5, Item("common.items.food.cheese")), + (1.0, Item("common.items.food.apple")), (2.0, Item("common.items.food.carrot")), (2.0, Item("common.items.food.lettuce")), (2.0, Item("common.items.food.tomato")), diff --git a/assets/common/item_price_calculation.ron b/assets/common/trading/item_price_calculation.ron similarity index 72% rename from assets/common/item_price_calculation.ron rename to assets/common/trading/item_price_calculation.ron index 24fded7293..91a49da8da 100644 --- a/assets/common/item_price_calculation.ron +++ b/assets/common/trading/item_price_calculation.ron @@ -3,29 +3,31 @@ loot_tables: [ // balance the loot tables against each other (higher= more common= smaller price) // Weapons (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.cave"), (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 (20.0, true, "common.loot_tables.armor.cloth"), (1.0, true, "common.loot_tables.armor.twigs"), (1.0, true, "common.loot_tables.armor.twigsflowers"), (1.0, true, "common.loot_tables.armor.twigsleaves"), - (0.01, false, "common.loot_tables.trading.jewellery"), + (0.01, false, "common.trading.jewellery"), // Ingredients - (1.0, true, "common.loot_tables.trading.sellable_materials"), - (1.0, false, "common.loot_tables.trading.unsellable_materials"), + (1.0, true, "common.trading.sellable_materials"), + (1.0, false, "common.trading.unsellable_materials"), // Food Ingredients - (1.0, true, "common.loot_tables.trading.food"), + (1.0, true, "common.trading.food"), // Potions // @@ -39,14 +41,16 @@ loot_tables: [ // Misc (0.1, true, "common.loot_tables.consumable.throwable"), // 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 // so basically this table balances the goods against each other (higher=less valuable) good_scaling: [ (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 (Armor, 0.5), // common.items.armor.misc.pants.worker_blue (Tools, 0.5), // common.items.weapons.staff.starter_staff diff --git a/assets/common/trading/item_price_equality.ron b/assets/common/trading/item_price_equality.ron new file mode 100644 index 0000000000..6b1a027090 --- /dev/null +++ b/assets/common/trading/item_price_equality.ron @@ -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", + ]), +] diff --git a/assets/common/loot_tables/trading/jewellery.ron b/assets/common/trading/jewellery.ron similarity index 100% rename from assets/common/loot_tables/trading/jewellery.ron rename to assets/common/trading/jewellery.ron diff --git a/assets/common/loot_tables/trading/sellable_materials.ron b/assets/common/trading/sellable_materials.ron similarity index 92% rename from assets/common/loot_tables/trading/sellable_materials.ron rename to assets/common/trading/sellable_materials.ron index c486d78e58..0736ccaaf9 100644 --- a/assets/common/loot_tables/trading/sellable_materials.ron +++ b/assets/common/trading/sellable_materials.ron @@ -30,9 +30,8 @@ // Mob Drops (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")), - (1.0, Item("common.items.crafting_ing.animal_misc.icy_fang")), (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.sharp_fang")), @@ -48,10 +47,10 @@ (0.1, Item("common.items.crafting_ing.seashells")), (0.2, Item("common.items.crafting_ing.honey")), (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.flowers.pyrebloom")), (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.wild_flax")), (9.0, Item("common.items.flowers.sunflower")), diff --git a/assets/common/loot_tables/trading/unsellable_materials.ron b/assets/common/trading/unsellable_materials.ron similarity index 95% rename from assets/common/loot_tables/trading/unsellable_materials.ron rename to assets/common/trading/unsellable_materials.ron index e08cf09ed7..5c333cc8b2 100644 --- a/assets/common/loot_tables/trading/unsellable_materials.ron +++ b/assets/common/trading/unsellable_materials.ron @@ -25,6 +25,7 @@ (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.lively_vine")), + (0.15, Item("common.items.crafting_ing.animal_misc.lively_vine")), (0.225, Item("common.items.crafting_ing.sticky_thread")), // Ultra Rare drops diff --git a/common/src/comp/inventory/trade_pricing.rs b/common/src/comp/inventory/trade_pricing.rs index aa339f7eab..9ca594da8f 100644 --- a/common/src/comp/inventory/trade_pricing.rs +++ b/common/src/comp/inventory/trade_pricing.rs @@ -44,10 +44,7 @@ struct Entries { impl Entries { fn add(&mut self, eqset: &EqualitySet, item_name: &str, probability: f32, can_sell: bool) { - let canonical_itemname = eqset - .equivalence_class - .get(item_name) - .map_or(item_name, |i| &**i); + let canonical_itemname = eqset.canonical(item_name); let old = self .entries @@ -66,10 +63,15 @@ impl Entries { } self.entries .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, } +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 { fn load( cache: &assets::AssetCache, id: &str, ) -> Result { - let manifest = cache.load::>>>(id)?; - let mut ret = Self { + #[derive(Debug, Deserialize)] + enum EqualitySpec { + LootTable(String), + Set(Vec), + } + + let mut eqset = Self { equivalence_class: HashMap::new(), }; - for set in &manifest.read().0 { - let mut iter = set.iter(); + + let manifest = &cache.load::>>(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() { 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 { - ret.equivalence_class + eqset + .equivalence_class .insert(item.to_string(), first.clone()); } } } - Ok(ret) + Ok(eqset) } } @@ -278,10 +307,7 @@ impl TradePricing { // look up price (inverse frequency) of an item fn price_lookup(&self, eqset: &EqualitySet, requested_name: &str) -> f32 { - let canonical_name = eqset - .equivalence_class - .get(requested_name) - .map_or(requested_name, |name| &**name); + let canonical_name = eqset.canonical(requested_name); let goods = self.get_list_by_path(canonical_name); // even if we multiply by INVEST_FACTOR we need to remain @@ -322,8 +348,9 @@ impl TradePricing { #[allow(clippy::cast_precision_loss)] fn read() -> Self { let mut result = Self::default(); - let price_config = TradingPriceFile::load_expect("common.item_price_calculation").read(); - let eqset = EqualitySet::load_expect("common.item_price_equality").read(); + let price_config = + 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(); for table in &price_config.loot_tables { if PRICING_DEBUG { @@ -382,7 +409,7 @@ impl TradePricing { let actual_cost = result.calculate_material_cost(recipe, &eqset); let output_tradeable = recipe.input.iter().all(|(input, _)| { result - .get_list_by_path(&input) + .get_list_by_path(input) .iter() .find(|(item, _, _)| item == input) .map_or(false, |(_, _, tradeable)| *tradeable) @@ -468,11 +495,7 @@ impl TradePricing { if item == Self::COIN_ITEM { (Good::Coin, 1.0) } else { - let item = TRADE_PRICING - .equality_set - .equivalence_class - .get(item) - .map_or(item, |i| &**i); + let item = TRADE_PRICING.equality_set.canonical(item); TRADE_PRICING.material_cache.get(item).copied().map_or( (Good::Terrain(crate::terrain::BiomeKind::Void), 0.0), @@ -489,20 +512,21 @@ impl TradePricing { use crate::comp::item::{armor, tool, Item, ItemKind}; // we pass the item and the inverse of the price to the closure - fn printvec(x: &str, e: &[(String, f32, bool)], f: F) + fn printvec(good_kind: &str, entries: &[(String, f32, bool)], f: F) where F: Fn(&Item, f32) -> String, { - println!("\n======{:^15}======", x); - for i in e.iter() { - let it = Item::new_from_asset_expect(&i.0); - let price = 1.0 / i.1; + println!("\n======{:^15}======", good_kind); + for (item_id, p, can_sell) in entries.iter() { + let it = Item::new_from_asset_expect(item_id); + let price = 1.0 / p; println!( - "<{}>\n{:>4.2} {:?} {}", - i.0, + "<{}> {}\n{:>4.2} {:?} {}", + item_id, + if *can_sell { "+" } else { "-" }, price, it.quality, - f(&it, i.1) + f(&it, *p) ); } }