1use std::collections::HashMap;
2
3use crate::models::skyblock::Bazaar;
4
5#[derive(Debug, Clone, Default)]
7pub struct PriceBook {
8 prices: HashMap<String, f64>,
9}
10
11impl PriceBook {
12 pub fn new() -> Self {
14 Self::default()
15 }
16
17 pub fn with_bazaar(mut self, bazaar: &Bazaar) -> Self {
20 for (id, product) in &bazaar.products {
21 if let Some(status) = &product.quick_status {
22 self.prices.insert(id.to_uppercase(), status.buy_price);
23 }
24 }
25 self
26 }
27
28 pub fn with_lowest_bin(mut self, lowest_bin: &HashMap<String, i64>) -> Self {
31 for (id, price) in lowest_bin {
32 self.prices
33 .entry(id.to_uppercase())
34 .or_insert(*price as f64);
35 }
36 self
37 }
38
39 pub fn insert(&mut self, item_id: impl Into<String>, price: f64) {
41 self.prices.insert(item_id.into().to_uppercase(), price);
42 }
43
44 pub fn price(&self, item_id: &str) -> Option<f64> {
46 self.prices.get(&item_id.to_uppercase()).copied()
47 }
48}
49
50#[derive(Debug, Clone, PartialEq)]
52pub struct ItemStack {
53 pub item_id: String,
55 pub count: i64,
57}
58
59#[derive(Debug, Clone, PartialEq)]
61pub struct ItemValuation {
62 pub item_id: String,
63 pub count: i64,
64 pub unit_price: f64,
65 pub total: f64,
66}
67
68#[derive(Debug, Clone, Default, PartialEq)]
70pub struct Networth {
71 pub total: f64,
73 pub items: Vec<ItemValuation>,
75 pub unpriced: Vec<String>,
77}
78
79pub fn value_items(stacks: &[ItemStack], prices: &PriceBook) -> Networth {
81 let mut networth = Networth::default();
82 for stack in stacks {
83 match prices.price(&stack.item_id) {
84 Some(unit_price) => {
85 let total = unit_price * stack.count as f64;
86 networth.total += total;
87 networth.items.push(ItemValuation {
88 item_id: stack.item_id.clone(),
89 count: stack.count,
90 unit_price,
91 total,
92 });
93 }
94 None => networth.unpriced.push(stack.item_id.clone()),
95 }
96 }
97 networth
98}
99
100#[cfg(feature = "nbt")]
105pub fn extract_item_stacks(nbt: &fastnbt::Value) -> Vec<ItemStack> {
106 use fastnbt::Value;
107
108 fn as_compound(v: &Value) -> Option<&std::collections::HashMap<String, Value>> {
109 match v {
110 Value::Compound(map) => Some(map),
111 _ => None,
112 }
113 }
114
115 let items = match nbt {
116 Value::Compound(root) => match root.get("i") {
117 Some(Value::List(list)) => list,
118 _ => return Vec::new(),
119 },
120 Value::List(list) => list,
121 _ => return Vec::new(),
122 };
123
124 let mut stacks = Vec::new();
125 for entry in items {
126 let Some(item) = as_compound(entry) else {
127 continue;
128 };
129 let count = match item.get("Count") {
130 Some(Value::Byte(b)) => *b as i64,
131 Some(Value::Short(s)) => *s as i64,
132 Some(Value::Int(i)) => *i as i64,
133 _ => 1,
134 };
135 let item_id = as_compound(item.get("tag").unwrap_or(&Value::Byte(0)))
136 .and_then(|tag| as_compound(tag.get("ExtraAttributes").unwrap_or(&Value::Byte(0))))
137 .and_then(|attrs| match attrs.get("id") {
138 Some(Value::String(s)) => Some(s.clone()),
139 _ => None,
140 });
141 if let Some(item_id) = item_id {
142 stacks.push(ItemStack { item_id, count });
143 }
144 }
145 stacks
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn values_known_items_and_tracks_unpriced() {
154 let mut book = PriceBook::new();
155 book.insert("HYPERION", 1_000_000.0);
156 let stacks = vec![
157 ItemStack {
158 item_id: "hyperion".into(),
159 count: 2,
160 },
161 ItemStack {
162 item_id: "MYSTERY".into(),
163 count: 1,
164 },
165 ];
166 let nw = value_items(&stacks, &book);
167 assert_eq!(nw.total, 2_000_000.0);
168 assert_eq!(nw.items.len(), 1);
169 assert_eq!(nw.unpriced, vec!["MYSTERY".to_string()]);
170 }
171}