Allow trading items not sold by merchants

This commit is contained in:
Joshua Barretto 2021-04-18 20:07:00 +01:00
parent cac9ac6807
commit 352fce239e
3 changed files with 61 additions and 51 deletions

View File

@ -2,45 +2,45 @@
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
(16.0, "common.loot_tables.weapons.starter"), (16.0, true, "common.loot_tables.weapons.starter"),
(12.0, "common.loot_tables.weapons.tier-0"), (12.0, true, "common.loot_tables.weapons.tier-0"),
(6.0, "common.loot_tables.weapons.tier-1"), (6.0, true, "common.loot_tables.weapons.tier-1"),
(4.0, "common.loot_tables.weapons.tier-2"), (4.0, true, "common.loot_tables.weapons.tier-2"),
(2.0, "common.loot_tables.weapons.tier-3"), (2.0, true, "common.loot_tables.weapons.tier-3"),
//(0.25, "common.loot_tables.weapons.tier-4"), (1.0, false, "common.loot_tables.weapons.tier-4"),
//(0.125, "common.loot_tables.weapons.tier-5"), (0.5, false, "common.loot_tables.weapons.tier-5"),
//(0.05, "common.loot_tables.weapons.cultist"), (0.05, false, "common.loot_tables.weapons.cultist"),
//(0.125, "common.loot_tables.weapons.cave"), (0.125, false, "common.loot_tables.weapons.cave"),
//(0.0625, "common.loot_tables.weapons.legendary"), (0.0625, false, "common.loot_tables.weapons.legendary"),
// Armor // Armor
(20.0, "common.loot_tables.armor.cloth"), (20.0, true, "common.loot_tables.armor.cloth"),
(6.0, "common.loot_tables.armor.agile"), (6.0, true, "common.loot_tables.armor.agile"),
(3.0, "common.loot_tables.armor.swift"), (3.0, true, "common.loot_tables.armor.swift"),
(6.0, "common.loot_tables.armor.druid"), (6.0, true, "common.loot_tables.armor.druid"),
(1.0, "common.loot_tables.armor.twigs"), (1.0, true, "common.loot_tables.armor.twigs"),
(1.0, "common.loot_tables.armor.twigsflowers"), (1.0, true, "common.loot_tables.armor.twigsflowers"),
(1.0, "common.loot_tables.armor.twigsleaves"), (1.0, true, "common.loot_tables.armor.twigsleaves"),
(0.5, "common.loot_tables.armor.plate"), (0.5, true, "common.loot_tables.armor.plate"),
(0.25, "common.loot_tables.armor.steel"), (0.25, false, "common.loot_tables.armor.steel"),
//(0.075, "common.loot_tables.armor.cultist"), (0.075, false, "common.loot_tables.armor.cultist"),
// Materials // Materials
(7.5, "common.loot_tables.materials.common"), (7.5, true, "common.loot_tables.materials.common"),
(8.0, "common.loot_tables.materials.underground"), (8.0, true, "common.loot_tables.materials.underground"),
// Food // Food
(0.3, "common.loot_tables.food.farm_ingredients"), (0.3, true, "common.loot_tables.food.farm_ingredients"),
(0.4, "common.loot_tables.food.wild_ingredients"), (0.4, true, "common.loot_tables.food.wild_ingredients"),
(0.2, "common.loot_tables.food.prepared"), (0.2, true, "common.loot_tables.food.prepared"),
// TODO: Change consumables when they are split up later // TODO: Change consumables when they are split up later
(1.0, "common.loot_tables.consumables"), (1.0, true, "common.loot_tables.consumables"),
(0.5, "common.loot_tables.trading"), (0.5, false, "common.loot_tables.trading"),
], ],
// 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.0075), // common.items.consumable.potion_minor
(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.1), // common.items.weapons.staff.starter_staff (Tools, 0.05), // common.items.weapons.staff.starter_staff
(Ingredients, 0.25), // common.items.crafting_ing.leather_scraps (Ingredients, 0.25), // common.items.crafting_ing.leather_scraps
]) ])

View File

@ -590,7 +590,8 @@ 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) = TradePricing::random_item(Good::Armor, armor) } else 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));
} }
@ -605,7 +606,8 @@ impl LoadoutBuilder {
.unwrap_or_default() .unwrap_or_default()
/ 10.0; / 10.0;
for i in bag1.slots_mut() { for i in bag1.slots_mut() {
if let Some(item_id) = TradePricing::random_item(Good::Tools, weapon) { if let Some(item_id) = TradePricing::random_item(Good::Tools, weapon, true)
{
*i = Some(Item::new_from_asset_expect(&item_id)); *i = Some(Item::new_from_asset_expect(&item_id));
} }
} }
@ -638,7 +640,7 @@ impl LoadoutBuilder {
/ 10.0; / 10.0;
for i in bag2.slots_mut() { for i in bag2.slots_mut() {
if let Some(item_id) = if let Some(item_id) =
TradePricing::random_item(Good::Ingredients, ingredients) TradePricing::random_item(Good::Ingredients, ingredients, true)
{ {
*i = item_with_amount(&item_id, &mut ingredients); *i = item_with_amount(&item_id, &mut ingredients);
} }
@ -658,7 +660,7 @@ impl LoadoutBuilder {
.max(10000.0) .max(10000.0)
/ 10.0; / 10.0;
for i in bag3.slots_mut() { for i in bag3.slots_mut() {
if let Some(item_id) = TradePricing::random_item(Good::Food, food) { if let Some(item_id) = TradePricing::random_item(Good::Food, food, true) {
*i = item_with_amount(&item_id, &mut food); *i = item_with_amount(&item_id, &mut food);
} }
} }
@ -672,7 +674,9 @@ impl LoadoutBuilder {
.unwrap_or_default() .unwrap_or_default()
/ 10.0; / 10.0;
for i in bag4.slots_mut() { for i in bag4.slots_mut() {
if let Some(item_id) = TradePricing::random_item(Good::Potions, potions) { if let Some(item_id) =
TradePricing::random_item(Good::Potions, potions, true)
{
*i = item_with_amount(&item_id, &mut potions); *i = item_with_amount(&item_id, &mut potions);
} }
} }

View File

@ -10,7 +10,7 @@ use lazy_static::lazy_static;
use serde::Deserialize; use serde::Deserialize;
use tracing::{info, warn}; use tracing::{info, warn};
type Entry = (String, f32); type Entry = (String, f32, bool);
type Entries = Vec<Entry>; type Entries = Vec<Entry>;
const PRICING_DEBUG: bool = false; const PRICING_DEBUG: bool = false;
@ -72,7 +72,7 @@ impl From<Vec<(f32, LootSpec)>> for ProbabilityFile {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct TradingPriceFile { struct TradingPriceFile {
pub loot_tables: Vec<(f32, String)>, pub loot_tables: Vec<(f32, bool, String)>,
pub good_scaling: Vec<(Good, f32)>, // the amount of Good equivalent to the most common item pub good_scaling: Vec<(Good, f32)>, // the amount of Good equivalent to the most common item
} }
@ -168,7 +168,7 @@ impl TradePricing {
} }
fn read() -> Self { fn read() -> Self {
fn add(entryvec: &mut Entries, itemname: &str, probability: f32) { fn add(entryvec: &mut Entries, itemname: &str, probability: f32, can_sell: bool) {
let val = entryvec.iter_mut().find(|j| *j.0 == *itemname); let val = entryvec.iter_mut().find(|j| *j.0 == *itemname);
if let Some(r) = val { if let Some(r) = val {
if PRICING_DEBUG { if PRICING_DEBUG {
@ -179,13 +179,13 @@ impl TradePricing {
if PRICING_DEBUG { if PRICING_DEBUG {
info!("New {} {}", itemname, probability); info!("New {} {}", itemname, probability);
} }
entryvec.push((itemname.to_string(), probability)); entryvec.push((itemname.to_string(), probability, can_sell));
} }
} }
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());
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;
for i in entryvec.iter_mut() { for i in entryvec.iter_mut() {
@ -210,9 +210,9 @@ impl TradePricing {
if PRICING_DEBUG { if PRICING_DEBUG {
info!(?i); info!(?i);
} }
let loot = ProbabilityFile::load_expect(&i.1); let loot = ProbabilityFile::load_expect(&i.2);
for j in loot.read().content.iter() { for j in loot.read().content.iter() {
add(&mut result.get_list_by_path_mut(&j.1), &j.1, i.0 * j.0); add(&mut result.get_list_by_path_mut(&j.1), &j.1, i.0 * j.0, i.1);
} }
} }
@ -241,8 +241,8 @@ impl TradePricing {
fn price_lookup(s: &TradePricing, name: &str) -> f32 { fn price_lookup(s: &TradePricing, name: &str) -> f32 {
let vec = s.get_list_by_path(name); let vec = s.get_list_by_path(name);
vec.iter() vec.iter()
.find(|(n, _)| n == name) .find(|(n, _, _)| n == name)
.map(|(_, freq)| 1.0 / freq) .map(|(_, freq, _)| 1.0 / freq)
// even if we multiply by INVEST_FACTOR we need to remain above UNAVAILABLE_PRICE (add 1.0 to compensate rounding errors) // even if we multiply by INVEST_FACTOR we need to remain above UNAVAILABLE_PRICE (add 1.0 to compensate rounding errors)
.unwrap_or(TradePricing::UNAVAILABLE_PRICE/TradePricing::INVEST_FACTOR+1.0) .unwrap_or(TradePricing::UNAVAILABLE_PRICE/TradePricing::INVEST_FACTOR+1.0)
} }
@ -276,6 +276,7 @@ impl TradePricing {
&mut result.get_list_by_path_mut(&e.output), &mut result.get_list_by_path_mut(&e.output),
&e.output, &e.output,
(e.amount as f32) / actual_cost * TradePricing::CRAFTING_FACTOR, (e.amount as f32) / actual_cost * TradePricing::CRAFTING_FACTOR,
true,
); );
false false
} else { } else {
@ -305,7 +306,7 @@ impl TradePricing {
result result
} }
fn random_item_impl(&self, good: Good, amount: f32) -> Option<String> { fn random_item_impl(&self, good: Good, amount: f32, selling: bool) -> Option<String> {
if good == Good::Coin { if good == Good::Coin {
Some(TradePricing::COIN_ITEM.into()) Some(TradePricing::COIN_ITEM.into())
} else { } else {
@ -321,14 +322,19 @@ impl TradePricing {
.find(|i| i.1.1 * amount >= 1.0) .find(|i| i.1.1 * amount >= 1.0)
.map(|i| i.0) .map(|i| i.0)
.unwrap_or(upper - 1); .unwrap_or(upper - 1);
let index = (rand::random::<f32>() * ((upper - lower) as f32)).floor() as usize + lower; loop {
let index =
(rand::random::<f32>() * ((upper - lower) as f32)).floor() as usize + lower;
//.gen_range(lower..upper); //.gen_range(lower..upper);
table.get(index).map(|i| i.0.clone()) if table.get(index).map_or(false, |i| !selling || i.2) {
break table.get(index).map(|i| i.0.clone());
}
}
} }
} }
pub fn random_item(good: Good, amount: f32) -> Option<String> { pub fn random_item(good: Good, amount: f32, selling: bool) -> Option<String> {
TRADE_PRICING.random_item_impl(good, amount) TRADE_PRICING.random_item_impl(good, amount, selling)
} }
pub fn get_material(item: &str) -> (Good, f32) { pub fn get_material(item: &str) -> (Good, f32) {
@ -350,7 +356,7 @@ 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)], f: F) fn printvec<F>(x: &str, e: &[(String, f32, bool)], f: F)
where where
F: Fn(&Item, f32) -> String, F: Fn(&Item, f32) -> String,
{ {
@ -437,7 +443,7 @@ mod tests {
TradePricing::instance().print_sorted(); TradePricing::instance().print_sorted();
for _ in 0..5 { for _ in 0..5 {
if let Some(item_id) = TradePricing::random_item(Good::Armor, 5.0) { if let Some(item_id) = TradePricing::random_item(Good::Armor, 5.0, false) {
info!("Armor 5 {}", item_id); info!("Armor 5 {}", item_id);
} }
} }