//! Item stacks carried by a player. use crate::codec::{DecodeError, Reader, Writer}; /// Identifies a kind of item. The value zero is reserved for empty slots. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct ItemId(pub u16); /// Stone, gained by breaking rock. pub const ITEM_STONE: ItemId = ItemId(1); /// Number of slots in an inventory. pub const INVENTORY_SLOTS: usize = 24; /// Largest number of items in one slot. pub const STACK_LIMIT: u32 = 99; /// Items of one kind in one slot. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Stack { pub item: ItemId, pub count: u32, } /// Fixed number of slots, each empty or holding one stack. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Inventory { slots: [Option; INVENTORY_SLOTS], } impl Default for Inventory { fn default() -> Self { Self::new() } } impl Inventory { pub fn new() -> Self { Self { slots: [None; INVENTORY_SLOTS], } } pub fn slots(&self) -> &[Option] { &self.slots } /// Total number of `item` across all slots. pub fn count(&self, item: ItemId) -> u32 { self.slots .iter() .flatten() .filter(|stack| stack.item == item) .map(|stack| stack.count) .sum() } /// How many more of `item` fit. pub fn free_space(&self, item: ItemId) -> u32 { self.slots .iter() .map(|slot| match slot { None => STACK_LIMIT, Some(stack) if stack.item == item => STACK_LIMIT - stack.count, Some(_) => 0, }) .sum() } /// Adds up to `count` of `item`. Existing stacks are topped up before /// empty slots are used. Returns how many did not fit. /// /// # Panics /// Panics if `item` is the reserved id zero. pub fn add(&mut self, item: ItemId, count: u32) -> u32 { assert!(item.0 != 0, "item id zero is reserved for empty slots"); let mut left = count; for slot in self.slots.iter_mut() { if left == 0 { break; } let Some(stack) = slot else { continue }; if stack.item != item { continue; } let moved = left.min(STACK_LIMIT - stack.count); stack.count += moved; left -= moved; } for slot in self.slots.iter_mut() { if left == 0 { break; } if slot.is_none() { let moved = left.min(STACK_LIMIT); *slot = Some(Stack { item, count: moved }); left -= moved; } } left } /// Removes exactly `count` of `item`, emptying the last slots first. /// Returns `false` and changes nothing if fewer are present. pub fn remove(&mut self, item: ItemId, count: u32) -> bool { if self.count(item) < count { return false; } let mut left = count; for slot in self.slots.iter_mut().rev() { if left == 0 { break; } let Some(stack) = slot else { continue }; if stack.item != item { continue; } let moved = left.min(stack.count); stack.count -= moved; left -= moved; if stack.count == 0 { *slot = None; } } true } pub fn encode(&self, w: &mut Writer) { for slot in &self.slots { match slot { None => w.u16(0), Some(stack) => { w.u16(stack.item.0); w.u32(stack.count); } } } } pub fn decode(r: &mut Reader<'_>) -> Result { let mut slots = [None; INVENTORY_SLOTS]; for slot in slots.iter_mut() { let item = r.u16()?; if item == 0 { continue; } let count = r.u32()?; if count == 0 || count > STACK_LIMIT { return Err(DecodeError::InvalidValue("stack count")); } *slot = Some(Stack { item: ItemId(item), count, }); } Ok(Self { slots }) } }