basalt_api/components/
inventory.rs1use crate::components::Component;
4
5#[derive(Debug, Clone)]
17pub struct Inventory {
18 pub held_slot: u8,
20 pub slots: [basalt_types::Slot; 36],
22 pub cursor: basalt_types::Slot,
24}
25
26impl Inventory {
27 pub const HOTBAR_START: usize = 0;
29 pub const MAIN_START: usize = 9;
31
32 pub fn empty() -> Self {
34 Self {
35 held_slot: 0,
36 slots: std::array::from_fn(|_| basalt_types::Slot::empty()),
37 cursor: basalt_types::Slot::empty(),
38 }
39 }
40
41 pub fn held_item(&self) -> &basalt_types::Slot {
43 &self.slots[self.held_slot as usize]
44 }
45
46 pub fn hotbar(&self) -> &[basalt_types::Slot] {
48 &self.slots[..9]
49 }
50
51 pub fn hotbar_mut(&mut self) -> &mut [basalt_types::Slot] {
53 &mut self.slots[..9]
54 }
55
56 pub fn window_to_index(window_slot: i16) -> Option<usize> {
61 match window_slot {
62 9..=35 => Some(window_slot as usize), 36..=44 => Some((window_slot - 36) as usize), _ => None,
65 }
66 }
67
68 pub fn index_to_window(index: usize) -> Option<i16> {
70 match index {
71 0..=8 => Some(index as i16 + 36), 9..=35 => Some(index as i16), _ => None,
74 }
75 }
76
77 pub fn try_insert(&mut self, item_id: i32, count: i32) -> Option<usize> {
83 let search_order = (0..9).chain(Self::MAIN_START..36);
85 for i in search_order {
86 let slot = &mut self.slots[i];
87 if slot.item_id == Some(item_id) && slot.item_count < 64 {
88 let space = 64 - slot.item_count;
89 let to_add = count.min(space);
90 slot.item_count += to_add;
91 if to_add == count {
92 return Some(i);
93 }
94 }
95 }
96 let search_order = (0..9).chain(Self::MAIN_START..36);
98 for i in search_order {
99 if self.slots[i].is_empty() {
100 self.slots[i] = basalt_types::Slot::new(item_id, count);
101 return Some(i);
102 }
103 }
104 None
105 }
106
107 pub fn to_protocol_slots(&self) -> Vec<basalt_types::Slot> {
113 let mut protocol = vec![basalt_types::Slot::empty(); 46];
114 protocol[9..36].clone_from_slice(&self.slots[9..]);
116 protocol[36..45].clone_from_slice(&self.slots[..9]);
118 protocol
119 }
120}
121impl Component for Inventory {}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn try_insert_empty_hotbar() {
129 let mut inv = Inventory::empty();
130 let idx = inv.try_insert(1, 1);
131 assert_eq!(idx, Some(Inventory::HOTBAR_START));
132 assert_eq!(inv.slots[Inventory::HOTBAR_START].item_id, Some(1));
133 }
134
135 #[test]
136 fn try_insert_stacks() {
137 let mut inv = Inventory::empty();
138 inv.try_insert(1, 32);
139 let idx = inv.try_insert(1, 16);
140 assert_eq!(idx, Some(Inventory::HOTBAR_START));
141 assert_eq!(inv.slots[Inventory::HOTBAR_START].item_count, 48);
142 }
143
144 #[test]
145 fn try_insert_full_returns_none() {
146 let mut inv = Inventory::empty();
147 for i in 0..36 {
148 inv.slots[i] = basalt_types::Slot::new(i as i32 + 100, 64);
149 }
150 assert_eq!(inv.try_insert(999, 1), None);
151 }
152
153 #[test]
154 fn slot_conversion() {
155 assert_eq!(Inventory::window_to_index(9), Some(9));
156 assert_eq!(Inventory::window_to_index(35), Some(35));
157 assert_eq!(Inventory::window_to_index(36), Some(0));
158 assert_eq!(Inventory::window_to_index(44), Some(8));
159 assert_eq!(Inventory::window_to_index(0), None);
160 assert_eq!(Inventory::window_to_index(45), None);
161 assert_eq!(Inventory::index_to_window(0), Some(36));
162 assert_eq!(Inventory::index_to_window(9), Some(9));
163 }
164
165 #[test]
166 fn to_protocol_slots_length() {
167 let inv = Inventory::empty();
168 assert_eq!(inv.to_protocol_slots().len(), 46);
169 }
170
171 #[test]
172 fn to_protocol_slots_maps_correctly() {
173 let mut inv = Inventory::empty();
174 inv.slots[0] = basalt_types::Slot::new(1, 1);
175 inv.slots[9] = basalt_types::Slot::new(2, 2);
176 let proto = inv.to_protocol_slots();
177 assert_eq!(proto[36].item_id, Some(1));
178 assert_eq!(proto[9].item_id, Some(2));
179 }
180
181 #[test]
182 fn held_item_and_hotbar() {
183 let mut inv = Inventory::empty();
184 inv.slots[3] = basalt_types::Slot::new(5, 10);
185 inv.held_slot = 3;
186 assert_eq!(inv.held_item().item_id, Some(5));
187 assert_eq!(inv.hotbar().len(), 9);
188 assert_eq!(inv.hotbar()[3].item_count, 10);
189 }
190
191 #[test]
192 fn try_insert_main_when_hotbar_full() {
193 let mut inv = Inventory::empty();
194 for i in 0..9 {
195 inv.slots[i] = basalt_types::Slot::new(i as i32, 64);
196 }
197 let idx = inv.try_insert(999, 1);
198 assert_eq!(idx, Some(Inventory::MAIN_START));
199 assert_eq!(inv.slots[Inventory::MAIN_START].item_id, Some(999));
200 }
201
202 #[test]
203 fn window_roundtrip() {
204 for i in 0..36 {
205 let window = Inventory::index_to_window(i).unwrap();
206 let back = Inventory::window_to_index(window).unwrap();
207 assert_eq!(back, i);
208 }
209 }
210}