use std::collections::HashMap;
use crate::models::skyblock::Bazaar;
#[derive(Debug, Clone, Default)]
pub struct PriceBook {
prices: HashMap<String, f64>,
}
impl PriceBook {
pub fn new() -> Self {
Self::default()
}
pub fn with_bazaar(mut self, bazaar: &Bazaar) -> Self {
for (id, product) in &bazaar.products {
if let Some(status) = &product.quick_status {
self.prices.insert(id.to_uppercase(), status.buy_price);
}
}
self
}
pub fn with_lowest_bin(mut self, lowest_bin: &HashMap<String, i64>) -> Self {
for (id, price) in lowest_bin {
self.prices
.entry(id.to_uppercase())
.or_insert(*price as f64);
}
self
}
pub fn insert(&mut self, item_id: impl Into<String>, price: f64) {
self.prices.insert(item_id.into().to_uppercase(), price);
}
pub fn price(&self, item_id: &str) -> Option<f64> {
self.prices.get(&item_id.to_uppercase()).copied()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ItemStack {
pub item_id: String,
pub count: i64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ItemValuation {
pub item_id: String,
pub count: i64,
pub unit_price: f64,
pub total: f64,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Networth {
pub total: f64,
pub items: Vec<ItemValuation>,
pub unpriced: Vec<String>,
}
pub fn value_items(stacks: &[ItemStack], prices: &PriceBook) -> Networth {
let mut networth = Networth::default();
for stack in stacks {
match prices.price(&stack.item_id) {
Some(unit_price) => {
let total = unit_price * stack.count as f64;
networth.total += total;
networth.items.push(ItemValuation {
item_id: stack.item_id.clone(),
count: stack.count,
unit_price,
total,
});
}
None => networth.unpriced.push(stack.item_id.clone()),
}
}
networth
}
#[cfg(feature = "nbt")]
pub fn extract_item_stacks(nbt: &fastnbt::Value) -> Vec<ItemStack> {
use fastnbt::Value;
fn as_compound(v: &Value) -> Option<&std::collections::HashMap<String, Value>> {
match v {
Value::Compound(map) => Some(map),
_ => None,
}
}
let items = match nbt {
Value::Compound(root) => match root.get("i") {
Some(Value::List(list)) => list,
_ => return Vec::new(),
},
Value::List(list) => list,
_ => return Vec::new(),
};
let mut stacks = Vec::new();
for entry in items {
let Some(item) = as_compound(entry) else {
continue;
};
let count = match item.get("Count") {
Some(Value::Byte(b)) => *b as i64,
Some(Value::Short(s)) => *s as i64,
Some(Value::Int(i)) => *i as i64,
_ => 1,
};
let item_id = as_compound(item.get("tag").unwrap_or(&Value::Byte(0)))
.and_then(|tag| as_compound(tag.get("ExtraAttributes").unwrap_or(&Value::Byte(0))))
.and_then(|attrs| match attrs.get("id") {
Some(Value::String(s)) => Some(s.clone()),
_ => None,
});
if let Some(item_id) = item_id {
stacks.push(ItemStack { item_id, count });
}
}
stacks
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn values_known_items_and_tracks_unpriced() {
let mut book = PriceBook::new();
book.insert("HYPERION", 1_000_000.0);
let stacks = vec![
ItemStack {
item_id: "hyperion".into(),
count: 2,
},
ItemStack {
item_id: "MYSTERY".into(),
count: 1,
},
];
let nw = value_items(&stacks, &book);
assert_eq!(nw.total, 2_000_000.0);
assert_eq!(nw.items.len(), 1);
assert_eq!(nw.unpriced, vec!["MYSTERY".to_string()]);
}
}