azalea_inventory/
slot.rs

1use std::{
2    any::Any,
3    fmt,
4    io::{self, Cursor, Write},
5};
6
7use azalea_buf::{AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
8use azalea_registry::DataComponentKind;
9use indexmap::IndexMap;
10
11use crate::components::{self};
12
13/// Either an item in an inventory or nothing.
14#[derive(Debug, Clone, Default, PartialEq)]
15pub enum ItemStack {
16    #[default]
17    Empty,
18    Present(ItemStackData),
19}
20
21impl ItemStack {
22    /// Check if the slot is ItemStack::Empty, if the count is <= 0, or if the
23    /// item is air.
24    ///
25    /// This is the opposite of [`ItemStack::is_present`].
26    pub fn is_empty(&self) -> bool {
27        match self {
28            ItemStack::Empty => true,
29            ItemStack::Present(item) => item.is_empty(),
30        }
31    }
32    /// Check if the slot is not ItemStack::Empty, if the count is > 0, and if
33    /// the item is not air.
34    ///
35    /// This is the opposite of [`ItemStack::is_empty`].
36    pub fn is_present(&self) -> bool {
37        !self.is_empty()
38    }
39
40    /// Return the amount of the item in the slot, or 0 if the slot is empty.
41    ///
42    /// Note that it's possible for the count to be zero or negative when the
43    /// slot is present.
44    pub fn count(&self) -> i32 {
45        match self {
46            ItemStack::Empty => 0,
47            ItemStack::Present(i) => i.count,
48        }
49    }
50
51    /// Remove `count` items from this slot, returning the removed items.
52    pub fn split(&mut self, count: u32) -> ItemStack {
53        match self {
54            ItemStack::Empty => ItemStack::Empty,
55            ItemStack::Present(i) => {
56                let returning = i.split(count);
57                if i.is_empty() {
58                    *self = ItemStack::Empty;
59                }
60                ItemStack::Present(returning)
61            }
62        }
63    }
64
65    /// Get the `kind` of the item in this slot, or
66    /// [`azalea_registry::Item::Air`]
67    pub fn kind(&self) -> azalea_registry::Item {
68        match self {
69            ItemStack::Empty => azalea_registry::Item::Air,
70            ItemStack::Present(i) => i.kind,
71        }
72    }
73
74    /// Update whether this slot is empty, based on the count.
75    pub fn update_empty(&mut self) {
76        if let ItemStack::Present(i) = self
77            && i.is_empty()
78        {
79            *self = ItemStack::Empty;
80        }
81    }
82
83    /// Convert this slot into an [`ItemStackData`], if it's present.
84    pub fn as_present(&self) -> Option<&ItemStackData> {
85        match self {
86            ItemStack::Empty => None,
87            ItemStack::Present(i) => Some(i),
88        }
89    }
90
91    pub fn as_present_mut(&mut self) -> Option<&mut ItemStackData> {
92        match self {
93            ItemStack::Empty => None,
94            ItemStack::Present(i) => Some(i),
95        }
96    }
97}
98
99/// An item in an inventory, with a count and NBT. Usually you want
100/// [`ItemStack`] or [`azalea_registry::Item`] instead.
101#[derive(Debug, Clone, PartialEq)]
102pub struct ItemStackData {
103    /// The amount of the item in this slot.
104    ///
105    /// The count can be zero or negative, but this is rare.
106    pub count: i32,
107    pub kind: azalea_registry::Item,
108    pub components: DataComponentPatch,
109}
110
111impl ItemStackData {
112    /// Remove `count` items from this slot, returning the removed items.
113    pub fn split(&mut self, count: u32) -> ItemStackData {
114        let returning_count = i32::min(count as i32, self.count);
115        let mut returning = self.clone();
116        returning.count = returning_count;
117        self.count -= returning_count;
118        returning
119    }
120
121    /// Check if the count of the item is <= 0 or if the item is air.
122    pub fn is_empty(&self) -> bool {
123        self.count <= 0 || self.kind == azalea_registry::Item::Air
124    }
125
126    /// Whether this item is the same as another item, ignoring the count.
127    ///
128    /// ```
129    /// # use azalea_inventory::ItemStackData;
130    /// # use azalea_registry::Item;
131    /// let mut a = ItemStackData {
132    ///     kind: Item::Stone,
133    ///     count: 1,
134    ///     components: Default::default(),
135    /// };
136    /// let mut b = ItemStackData {
137    ///     kind: Item::Stone,
138    ///     count: 2,
139    ///     components: Default::default(),
140    /// };
141    /// assert!(a.is_same_item_and_components(&b));
142    ///
143    /// b.kind = Item::Dirt;
144    /// assert!(!a.is_same_item_and_components(&b));
145    /// ```
146    pub fn is_same_item_and_components(&self, other: &ItemStackData) -> bool {
147        self.kind == other.kind && self.components == other.components
148    }
149}
150
151impl AzaleaRead for ItemStack {
152    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
153        let count = i32::azalea_read_var(buf)?;
154        if count <= 0 {
155            Ok(ItemStack::Empty)
156        } else {
157            let kind = azalea_registry::Item::azalea_read(buf)?;
158            let components = DataComponentPatch::azalea_read(buf)?;
159            Ok(ItemStack::Present(ItemStackData {
160                count,
161                kind,
162                components,
163            }))
164        }
165    }
166}
167
168impl AzaleaWrite for ItemStack {
169    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
170        match self {
171            ItemStack::Empty => 0_i32.azalea_write_var(buf)?,
172            ItemStack::Present(i) => {
173                i.count.azalea_write_var(buf)?;
174                i.kind.azalea_write(buf)?;
175                i.components.azalea_write(buf)?;
176            }
177        };
178        Ok(())
179    }
180}
181
182impl From<ItemStackData> for ItemStack {
183    fn from(item: ItemStackData) -> Self {
184        if item.is_empty() {
185            ItemStack::Empty
186        } else {
187            ItemStack::Present(item)
188        }
189    }
190}
191
192/// An update to an item's data components.
193///
194/// Note that in vanilla items come with their own set of default components,
195/// and Azalea does not implement that yet.
196#[derive(Default)]
197pub struct DataComponentPatch {
198    pub components:
199        IndexMap<DataComponentKind, Option<Box<dyn components::EncodableDataComponent>>>,
200}
201
202impl DataComponentPatch {
203    /// Returns the value of the component in the generic argument for this
204    /// item.
205    ///
206    /// ```
207    /// # use azalea_inventory::{ItemStackData, DataComponentPatch, components};
208    /// # use azalea_registry::Item;
209    /// # fn example(item: &ItemStackData) -> Option<()> {
210    /// let item_nutrition = item.components.get::<components::Food>()?.nutrition;
211    /// # Some(())
212    /// # }
213    /// ```
214    pub fn get<T: components::DataComponent>(&self) -> Option<&T> {
215        let component = self.components.get(&T::KIND).and_then(|c| c.as_deref())?;
216        let component_any = component as &dyn Any;
217        component_any.downcast_ref::<T>()
218    }
219
220    pub fn get_kind(
221        &self,
222        kind: DataComponentKind,
223    ) -> Option<&dyn components::EncodableDataComponent> {
224        self.components.get(&kind).and_then(|c| c.as_deref())
225    }
226
227    /// Returns whether the component in the generic argument is present for
228    /// this item.
229    ///
230    /// ```
231    /// # use azalea_inventory::{ItemStackData, DataComponentPatch, components};
232    /// # use azalea_registry::Item;
233    /// # let item = ItemStackData {
234    /// #     kind: Item::Stone,
235    /// #     count: 1,
236    /// #     components: Default::default(),
237    /// # };
238    /// let is_edible = item.components.has::<components::Food>();
239    /// # assert!(!is_edible);
240    /// ```
241    pub fn has<T: components::DataComponent>(&self) -> bool {
242        self.has_kind(T::KIND)
243    }
244
245    pub fn has_kind(&self, kind: DataComponentKind) -> bool {
246        self.get_kind(kind).is_some()
247    }
248}
249
250impl AzaleaRead for DataComponentPatch {
251    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
252        let components_with_data_count = u32::azalea_read_var(buf)?;
253        let components_without_data_count = u32::azalea_read_var(buf)?;
254
255        if components_without_data_count == 0 && components_with_data_count == 0 {
256            return Ok(DataComponentPatch::default());
257        }
258
259        let mut components = IndexMap::new();
260        for _ in 0..components_with_data_count {
261            let component_kind = DataComponentKind::azalea_read(buf)?;
262            let component_data = components::from_kind(component_kind, buf)?;
263            components.insert(component_kind, Some(component_data));
264        }
265
266        for _ in 0..components_without_data_count {
267            let component_kind = DataComponentKind::azalea_read(buf)?;
268            components.insert(component_kind, None);
269        }
270
271        Ok(DataComponentPatch { components })
272    }
273}
274
275impl AzaleaWrite for DataComponentPatch {
276    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
277        let mut components_with_data_count: u32 = 0;
278        let mut components_without_data_count: u32 = 0;
279        for component in self.components.values() {
280            if component.is_some() {
281                components_with_data_count += 1;
282            } else {
283                components_without_data_count += 1;
284            }
285        }
286
287        components_with_data_count.azalea_write_var(buf)?;
288        components_without_data_count.azalea_write_var(buf)?;
289
290        let mut component_buf = Vec::new();
291        for (kind, component) in &self.components {
292            if let Some(component) = component {
293                kind.azalea_write(buf)?;
294
295                component_buf.clear();
296                component.encode(&mut component_buf)?;
297                buf.write_all(&component_buf)?;
298            }
299        }
300
301        for (kind, component) in &self.components {
302            if component.is_none() {
303                kind.azalea_write(buf)?;
304            }
305        }
306
307        Ok(())
308    }
309}
310
311impl Clone for DataComponentPatch {
312    fn clone(&self) -> Self {
313        let mut components = IndexMap::with_capacity(self.components.len());
314        for (kind, component) in &self.components {
315            components.insert(*kind, component.as_ref().map(|c| (*c).clone()));
316        }
317        DataComponentPatch { components }
318    }
319}
320impl fmt::Debug for DataComponentPatch {
321    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322        f.debug_set().entries(self.components.keys()).finish()
323    }
324}
325impl PartialEq for DataComponentPatch {
326    fn eq(&self, other: &Self) -> bool {
327        if self.components.len() != other.components.len() {
328            return false;
329        }
330        for (kind, component) in &self.components {
331            let Some(other_component) = other.components.get(kind) else {
332                return false;
333            };
334            // we can't use PartialEq, but we can use our own eq method
335            if let Some(component) = component {
336                let Some(other_component) = other_component else {
337                    return false;
338                };
339                if !component.eq((*other_component).clone()) {
340                    return false;
341                }
342            } else if other_component.is_some() {
343                return false;
344            }
345        }
346        true
347    }
348}