Skip to main content

basalt_types/
slot.rs

1use crate::{Decode, Encode, EncodedSize, Result, VarInt};
2
3/// A Minecraft item stack, used in inventories, entity equipment, and trade offers.
4///
5/// The Slot type represents an item stack in the Minecraft protocol. It encodes
6/// the item count, item ID, and optional component data. An empty slot has
7/// `item_count = 0` and no other data. A non-empty slot includes the item ID
8/// and an optional list of item components (like damage, enchantments, custom name).
9///
10/// Wire format:
11/// - VarInt `item_count` (0 = empty slot, > 0 = slot with item)
12/// - If `item_count > 0`:
13///   - VarInt `item_id`
14///   - VarInt `num_components_to_add`
15///   - VarInt `num_components_to_remove`
16///   - Component data (opaque bytes for now)
17#[derive(Debug, Clone, Default, PartialEq)]
18pub struct Slot {
19    /// The number of items in this stack. 0 means the slot is empty.
20    pub item_count: i32,
21    /// The registry ID of the item, if the slot is not empty.
22    pub item_id: Option<i32>,
23    /// Raw component data bytes. Full component parsing is deferred
24    /// to a future implementation — for now, the components are stored
25    /// as opaque bytes to allow roundtrip encoding.
26    pub component_data: Vec<u8>,
27}
28
29impl Slot {
30    /// Creates an empty slot (no item).
31    pub fn empty() -> Self {
32        Self {
33            item_count: 0,
34            item_id: None,
35            component_data: Vec::new(),
36        }
37    }
38
39    /// Creates a simple slot with an item ID and count, no components.
40    pub fn new(item_id: i32, count: i32) -> Self {
41        Self {
42            item_count: count,
43            item_id: Some(item_id),
44            component_data: Vec::new(),
45        }
46    }
47
48    /// Returns true if the slot is empty (no item).
49    pub fn is_empty(&self) -> bool {
50        self.item_count == 0
51    }
52}
53
54/// Encodes a Slot in the Minecraft protocol format.
55///
56/// Empty slots encode as a single VarInt(0). Non-empty slots encode the
57/// item count, item ID, then component data. If no components are present,
58/// explicit zero counts are written (VarInt(0) + VarInt(0)).
59impl Encode for Slot {
60    /// Writes the slot to the buffer.
61    fn encode(&self, buf: &mut Vec<u8>) -> Result<()> {
62        VarInt(self.item_count).encode(buf)?;
63        if self.item_count > 0 {
64            if let Some(item_id) = self.item_id {
65                VarInt(item_id).encode(buf)?;
66            }
67            if self.component_data.is_empty() {
68                // No components: write explicit zero counts
69                VarInt(0).encode(buf)?; // components to add
70                VarInt(0).encode(buf)?; // components to remove
71            } else {
72                buf.extend_from_slice(&self.component_data);
73            }
74        }
75        Ok(())
76    }
77}
78
79/// Decodes a Slot from the Minecraft protocol format.
80///
81/// Reads the item count. If zero, returns an empty slot. Otherwise reads
82/// the item ID and component counts. Items with zero components decode
83/// cleanly, allowing correct `Vec<Slot>` support. Items with components
84/// store the raw component data as opaque bytes until a full component
85/// parser is implemented.
86impl Decode for Slot {
87    /// Reads a slot from the buffer.
88    fn decode(buf: &mut &[u8]) -> Result<Self> {
89        let item_count = VarInt::decode(buf)?.0;
90        if item_count <= 0 {
91            return Ok(Self::empty());
92        }
93
94        let item_id = VarInt::decode(buf)?.0;
95
96        // Read component counts to properly advance the cursor
97        let num_add = VarInt::decode(buf)?.0;
98        let num_remove = VarInt::decode(buf)?.0;
99
100        if num_add == 0 && num_remove == 0 {
101            // No components — cursor correctly positioned for next slot
102            return Ok(Self {
103                item_count,
104                item_id: Some(item_id),
105                component_data: Vec::new(),
106            });
107        }
108
109        // Components present — re-encode counts + data as opaque for roundtrip
110        let mut component_data = Vec::new();
111        VarInt(num_add).encode(&mut component_data)?;
112        VarInt(num_remove).encode(&mut component_data)?;
113
114        // Components-to-remove are single VarInt type IDs
115        for _ in 0..num_remove {
116            let id = VarInt::decode(buf)?;
117            id.encode(&mut component_data)?;
118        }
119
120        if num_add > 0 {
121            // Components-to-add are variable-length and type-dependent.
122            // Without a full component parser, consume remaining bytes.
123            // TODO: implement component parser for full multi-slot support
124            component_data.extend_from_slice(buf);
125            *buf = &buf[buf.len()..];
126        }
127
128        Ok(Self {
129            item_count,
130            item_id: Some(item_id),
131            component_data,
132        })
133    }
134}
135
136/// Computes the wire size of a Slot.
137impl EncodedSize for Slot {
138    fn encoded_size(&self) -> usize {
139        let count_size = VarInt(self.item_count).encoded_size();
140        if self.item_count == 0 {
141            count_size
142        } else {
143            count_size
144                + self.item_id.map_or(0, |id| VarInt(id).encoded_size())
145                + if self.component_data.is_empty() {
146                    2 // VarInt(0) + VarInt(0) for zero component counts
147                } else {
148                    self.component_data.len()
149                }
150        }
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn empty_slot_roundtrip() {
160        let slot = Slot::empty();
161        let mut buf = Vec::new();
162        slot.encode(&mut buf).unwrap();
163
164        let mut cursor = buf.as_slice();
165        let decoded = Slot::decode(&mut cursor).unwrap();
166        assert!(decoded.is_empty());
167        assert_eq!(decoded.item_count, 0);
168    }
169
170    #[test]
171    fn empty_slot_encodes_as_zero() {
172        let slot = Slot::empty();
173        let mut buf = Vec::new();
174        slot.encode(&mut buf).unwrap();
175        assert_eq!(buf, [0x00]);
176    }
177
178    #[test]
179    fn simple_slot() {
180        let slot = Slot::new(1, 64);
181        assert!(!slot.is_empty());
182        assert_eq!(slot.item_count, 64);
183        assert_eq!(slot.item_id, Some(1));
184    }
185
186    #[test]
187    fn encoded_size_empty() {
188        let slot = Slot::empty();
189        assert_eq!(slot.encoded_size(), 1);
190    }
191
192    #[test]
193    fn default_is_empty() {
194        let slot = Slot::default();
195        assert!(slot.is_empty());
196    }
197
198    #[test]
199    fn non_empty_slot_encode_decode() {
200        let slot = Slot::new(42, 10);
201        let mut buf = Vec::with_capacity(slot.encoded_size());
202        slot.encode(&mut buf).unwrap();
203        assert_eq!(buf.len(), slot.encoded_size());
204
205        // Decode reads item_count + item_id + consumes rest as components
206        let mut cursor = buf.as_slice();
207        let decoded = Slot::decode(&mut cursor).unwrap();
208        assert_eq!(decoded.item_count, 10);
209        assert_eq!(decoded.item_id, Some(42));
210    }
211
212    #[test]
213    fn encoded_size_non_empty() {
214        let slot = Slot::new(1, 1);
215        // VarInt(1) = 1 byte for count + VarInt(1) = 1 byte for id
216        // + VarInt(0) + VarInt(0) = 2 bytes for zero component counts = 4
217        assert_eq!(slot.encoded_size(), 4);
218    }
219
220    #[test]
221    fn consecutive_slot_roundtrip() {
222        // Two simple slots encoded back-to-back must decode independently
223        let slot1 = Slot::new(1, 10);
224        let slot2 = Slot::new(2, 20);
225        let mut buf = Vec::new();
226        slot1.encode(&mut buf).unwrap();
227        slot2.encode(&mut buf).unwrap();
228
229        let mut cursor = buf.as_slice();
230        let d1 = Slot::decode(&mut cursor).unwrap();
231        let d2 = Slot::decode(&mut cursor).unwrap();
232        assert!(cursor.is_empty());
233        assert_eq!(d1.item_count, 10);
234        assert_eq!(d1.item_id, Some(1));
235        assert_eq!(d2.item_count, 20);
236        assert_eq!(d2.item_id, Some(2));
237    }
238
239    #[test]
240    fn slot_with_remove_components_roundtrip() {
241        // Build a slot with components-to-remove (0 to add, 2 to remove)
242        let mut component_data = Vec::new();
243        VarInt(0).encode(&mut component_data).unwrap(); // 0 to add
244        VarInt(2).encode(&mut component_data).unwrap(); // 2 to remove
245        VarInt(5).encode(&mut component_data).unwrap(); // remove type 5
246        VarInt(10).encode(&mut component_data).unwrap(); // remove type 10
247
248        let slot = Slot {
249            item_count: 1,
250            item_id: Some(42),
251            component_data,
252        };
253        let mut buf = Vec::with_capacity(slot.encoded_size());
254        slot.encode(&mut buf).unwrap();
255        assert_eq!(buf.len(), slot.encoded_size());
256
257        let mut cursor = buf.as_slice();
258        let decoded = Slot::decode(&mut cursor).unwrap();
259        assert!(cursor.is_empty());
260        assert_eq!(decoded.item_count, 1);
261        assert_eq!(decoded.item_id, Some(42));
262        assert_eq!(decoded.component_data, slot.component_data);
263    }
264
265    #[test]
266    fn slot_with_add_components_consumes_remaining() {
267        // Build a slot with components-to-add (opaque data after counts)
268        let mut component_data = Vec::new();
269        VarInt(1).encode(&mut component_data).unwrap(); // 1 to add
270        VarInt(0).encode(&mut component_data).unwrap(); // 0 to remove
271        component_data.extend_from_slice(&[0xAA, 0xBB, 0xCC]); // opaque
272
273        let slot = Slot {
274            item_count: 1,
275            item_id: Some(7),
276            component_data,
277        };
278        let mut buf = Vec::with_capacity(slot.encoded_size());
279        slot.encode(&mut buf).unwrap();
280
281        let mut cursor = buf.as_slice();
282        let decoded = Slot::decode(&mut cursor).unwrap();
283        assert!(cursor.is_empty());
284        assert_eq!(decoded.item_count, 1);
285        assert_eq!(decoded.item_id, Some(7));
286        assert_eq!(decoded.component_data, slot.component_data);
287    }
288
289    #[test]
290    fn empty_and_nonempty_slots_interleaved() {
291        let slots = vec![
292            Slot::empty(),
293            Slot::new(1, 5),
294            Slot::empty(),
295            Slot::new(2, 3),
296        ];
297        let mut buf = Vec::new();
298        for s in &slots {
299            s.encode(&mut buf).unwrap();
300        }
301
302        let mut cursor = buf.as_slice();
303        for expected in &slots {
304            let decoded = Slot::decode(&mut cursor).unwrap();
305            assert_eq!(decoded.item_count, expected.item_count);
306            assert_eq!(decoded.item_id, expected.item_id);
307        }
308        assert!(cursor.is_empty());
309    }
310}