Skip to main content

ghast_protocol/types/
slot.rs

1//! Inventory slots (item stacks) in their per-version wire formats.
2//!
3//! NBT is kept raw ([`RawNbt`](crate::nbt::RawNbt)): decoding a `window_items` packet only walks the NBT to
4//! find its end; building a tree is up to the caller ([`RawNbt::parse`](crate::nbt::RawNbt::parse)).
5
6/// Slot format of 1.8 – 1.12.2: `i16` item id (`-1` = empty), `i8` count, `i16` damage,
7/// optional NBT (`0` byte = none).
8pub mod legacy {
9    use bytes::{BufMut, BytesMut};
10
11    use crate::codec::{Decode, DecodeResult, Encode, EncodeResult, Reader};
12    use crate::error::EncodeError;
13    use crate::nbt::RawNbt;
14
15    /// A non-empty item stack.
16    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
17    pub struct ItemStack {
18        /// Numeric item id. Never `-1` (that encodes an empty slot).
19        pub id: i16,
20        /// Stack size.
21        pub count: i8,
22        /// Damage / metadata value.
23        pub damage: i16,
24        /// Item NBT, if any.
25        pub nbt: Option<RawNbt>,
26    }
27
28    /// One slot: empty or an item stack.
29    #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
30    pub struct Slot(pub Option<ItemStack>);
31
32    impl Slot {
33        /// The empty slot.
34        pub const EMPTY: Slot = Slot(None);
35
36        /// Whether the slot is empty.
37        #[inline]
38        pub fn is_empty(&self) -> bool {
39            self.0.is_none()
40        }
41
42        /// The item stack, if any.
43        #[inline]
44        pub fn item(&self) -> Option<&ItemStack> {
45            self.0.as_ref()
46        }
47    }
48
49    impl Decode for Slot {
50        #[inline]
51        fn decode(r: &mut Reader<'_>) -> DecodeResult<Self> {
52            let id = r.read_i16()?;
53            if id == -1 {
54                return Ok(Slot(None));
55            }
56            Ok(Slot(Some(ItemStack {
57                id,
58                count: r.read_i8()?,
59                damage: r.read_i16()?,
60                nbt: RawNbt::read_optional(r)?,
61            })))
62        }
63    }
64
65    impl Encode for Slot {
66        #[inline]
67        fn encode(&self, w: &mut BytesMut) -> EncodeResult {
68            match &self.0 {
69                None => w.put_i16(-1),
70                Some(it) => {
71                    if it.id == -1 {
72                        return Err(EncodeError::Invalid(
73                            "item id -1 is reserved for empty slots",
74                        ));
75                    }
76                    w.put_i16(it.id);
77                    w.put_i8(it.count);
78                    w.put_i16(it.damage);
79                    RawNbt::write_optional(&it.nbt, w);
80                }
81            }
82            Ok(())
83        }
84    }
85}
86
87/// Slot format of 1.13.2 – 1.20.4: `bool` present, then VarInt item id, `i8` count,
88/// optional NBT.
89pub mod modern {
90    use bytes::{BufMut, BytesMut};
91
92    use crate::codec::{Decode, DecodeResult, Encode, EncodeResult, McWrite, Reader};
93    use crate::nbt::RawNbt;
94
95    /// A non-empty item stack.
96    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
97    pub struct ItemStack {
98        /// Registry item id.
99        pub id: i32,
100        /// Stack size.
101        pub count: i8,
102        /// Item NBT, if any.
103        pub nbt: Option<RawNbt>,
104    }
105
106    /// One slot: empty or an item stack.
107    #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
108    pub struct Slot(pub Option<ItemStack>);
109
110    impl Slot {
111        /// The empty slot.
112        pub const EMPTY: Slot = Slot(None);
113
114        /// Whether the slot is empty.
115        #[inline]
116        pub fn is_empty(&self) -> bool {
117            self.0.is_none()
118        }
119
120        /// The item stack, if any.
121        #[inline]
122        pub fn item(&self) -> Option<&ItemStack> {
123            self.0.as_ref()
124        }
125    }
126
127    impl Decode for Slot {
128        #[inline]
129        fn decode(r: &mut Reader<'_>) -> DecodeResult<Self> {
130            if !r.read_bool()? {
131                return Ok(Slot(None));
132            }
133            Ok(Slot(Some(ItemStack {
134                id: r.read_varint()?,
135                count: r.read_i8()?,
136                nbt: RawNbt::read_optional(r)?,
137            })))
138        }
139    }
140
141    impl Encode for Slot {
142        #[inline]
143        fn encode(&self, w: &mut BytesMut) -> EncodeResult {
144            match &self.0 {
145                None => w.put_bool(false),
146                Some(it) => {
147                    w.put_bool(true);
148                    w.put_varint(it.id);
149                    w.put_i8(it.count);
150                    RawNbt::write_optional(&it.nbt, w);
151                }
152            }
153            Ok(())
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::codec::{Encode, decode_exact};
162    use bytes::{Bytes, BytesMut};
163
164    #[test]
165    fn legacy_known_answers() {
166        // Empty slot.
167        let b = Bytes::from_static(&[0xff, 0xff]);
168        assert_eq!(decode_exact::<legacy::Slot>(&b), Ok(legacy::Slot::EMPTY));
169        // 64 x stone (id 1), damage 0, no NBT.
170        let b = Bytes::from_static(&[0x00, 0x01, 0x40, 0x00, 0x00, 0x00]);
171        let s = decode_exact::<legacy::Slot>(&b).unwrap();
172        let it = s.item().unwrap();
173        assert_eq!(
174            (it.id, it.count, it.damage, it.nbt.is_none()),
175            (1, 64, 0, true)
176        );
177        let mut w = BytesMut::new();
178        s.encode(&mut w).unwrap();
179        assert_eq!(&w[..], &b[..]);
180        // With an empty-named empty compound as NBT.
181        let b = Bytes::from_static(&[0x01, 0x14, 0x01, 0x00, 0x05, 0x0a, 0x00, 0x00, 0x00]);
182        let s = decode_exact::<legacy::Slot>(&b).unwrap();
183        assert_eq!(s.item().unwrap().nbt.as_ref().unwrap().as_bytes().len(), 4);
184    }
185
186    #[test]
187    fn modern_known_answers() {
188        let b = Bytes::from_static(&[0x00]);
189        assert_eq!(decode_exact::<modern::Slot>(&b), Ok(modern::Slot::EMPTY));
190        let b = Bytes::from_static(&[0x01, 0xac, 0x02, 0x10, 0x00]);
191        let s = decode_exact::<modern::Slot>(&b).unwrap();
192        let it = s.item().unwrap();
193        assert_eq!((it.id, it.count), (300, 16));
194    }
195}