use crate::components::Component;
#[derive(Debug, Clone)]
pub struct Inventory {
pub held_slot: u8,
pub slots: [basalt_types::Slot; 36],
pub cursor: basalt_types::Slot,
}
impl Inventory {
pub const HOTBAR_START: usize = 0;
pub const MAIN_START: usize = 9;
pub fn empty() -> Self {
Self {
held_slot: 0,
slots: std::array::from_fn(|_| basalt_types::Slot::empty()),
cursor: basalt_types::Slot::empty(),
}
}
pub fn held_item(&self) -> &basalt_types::Slot {
&self.slots[self.held_slot as usize]
}
pub fn hotbar(&self) -> &[basalt_types::Slot] {
&self.slots[..9]
}
pub fn hotbar_mut(&mut self) -> &mut [basalt_types::Slot] {
&mut self.slots[..9]
}
pub fn window_to_index(window_slot: i16) -> Option<usize> {
match window_slot {
9..=35 => Some(window_slot as usize), 36..=44 => Some((window_slot - 36) as usize), _ => None,
}
}
pub fn index_to_window(index: usize) -> Option<i16> {
match index {
0..=8 => Some(index as i16 + 36), 9..=35 => Some(index as i16), _ => None,
}
}
pub fn try_insert(&mut self, item_id: i32, count: i32) -> Option<usize> {
let search_order = (0..9).chain(Self::MAIN_START..36);
for i in search_order {
let slot = &mut self.slots[i];
if slot.item_id == Some(item_id) && slot.item_count < 64 {
let space = 64 - slot.item_count;
let to_add = count.min(space);
slot.item_count += to_add;
if to_add == count {
return Some(i);
}
}
}
let search_order = (0..9).chain(Self::MAIN_START..36);
for i in search_order {
if self.slots[i].is_empty() {
self.slots[i] = basalt_types::Slot::new(item_id, count);
return Some(i);
}
}
None
}
pub fn to_protocol_slots(&self) -> Vec<basalt_types::Slot> {
let mut protocol = vec![basalt_types::Slot::empty(); 46];
protocol[9..36].clone_from_slice(&self.slots[9..]);
protocol[36..45].clone_from_slice(&self.slots[..9]);
protocol
}
}
impl Component for Inventory {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn try_insert_empty_hotbar() {
let mut inv = Inventory::empty();
let idx = inv.try_insert(1, 1);
assert_eq!(idx, Some(Inventory::HOTBAR_START));
assert_eq!(inv.slots[Inventory::HOTBAR_START].item_id, Some(1));
}
#[test]
fn try_insert_stacks() {
let mut inv = Inventory::empty();
inv.try_insert(1, 32);
let idx = inv.try_insert(1, 16);
assert_eq!(idx, Some(Inventory::HOTBAR_START));
assert_eq!(inv.slots[Inventory::HOTBAR_START].item_count, 48);
}
#[test]
fn try_insert_full_returns_none() {
let mut inv = Inventory::empty();
for i in 0..36 {
inv.slots[i] = basalt_types::Slot::new(i as i32 + 100, 64);
}
assert_eq!(inv.try_insert(999, 1), None);
}
#[test]
fn slot_conversion() {
assert_eq!(Inventory::window_to_index(9), Some(9));
assert_eq!(Inventory::window_to_index(35), Some(35));
assert_eq!(Inventory::window_to_index(36), Some(0));
assert_eq!(Inventory::window_to_index(44), Some(8));
assert_eq!(Inventory::window_to_index(0), None);
assert_eq!(Inventory::window_to_index(45), None);
assert_eq!(Inventory::index_to_window(0), Some(36));
assert_eq!(Inventory::index_to_window(9), Some(9));
}
#[test]
fn to_protocol_slots_length() {
let inv = Inventory::empty();
assert_eq!(inv.to_protocol_slots().len(), 46);
}
#[test]
fn to_protocol_slots_maps_correctly() {
let mut inv = Inventory::empty();
inv.slots[0] = basalt_types::Slot::new(1, 1);
inv.slots[9] = basalt_types::Slot::new(2, 2);
let proto = inv.to_protocol_slots();
assert_eq!(proto[36].item_id, Some(1));
assert_eq!(proto[9].item_id, Some(2));
}
#[test]
fn held_item_and_hotbar() {
let mut inv = Inventory::empty();
inv.slots[3] = basalt_types::Slot::new(5, 10);
inv.held_slot = 3;
assert_eq!(inv.held_item().item_id, Some(5));
assert_eq!(inv.hotbar().len(), 9);
assert_eq!(inv.hotbar()[3].item_count, 10);
}
#[test]
fn try_insert_main_when_hotbar_full() {
let mut inv = Inventory::empty();
for i in 0..9 {
inv.slots[i] = basalt_types::Slot::new(i as i32, 64);
}
let idx = inv.try_insert(999, 1);
assert_eq!(idx, Some(Inventory::MAIN_START));
assert_eq!(inv.slots[Inventory::MAIN_START].item_id, Some(999));
}
#[test]
fn window_roundtrip() {
for i in 0..36 {
let window = Inventory::index_to_window(i).unwrap();
let back = Inventory::window_to_index(window).unwrap();
assert_eq!(back, i);
}
}
}