1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use std::io::Write;

pub use valence_generated::item::ItemKind;
use valence_nbt::Compound;

use crate::{Decode, Encode};

/// A stack of items in an inventory.
#[derive(Clone, PartialEq, Debug, Default)]
pub struct ItemStack {
    pub item: ItemKind,
    pub count: i8,
    pub nbt: Option<Compound>,
}

impl ItemStack {
    pub const EMPTY: ItemStack = ItemStack {
        item: ItemKind::Air,
        count: 0,
        nbt: None,
    };

    #[must_use]
    pub const fn new(item: ItemKind, count: i8, nbt: Option<Compound>) -> Self {
        Self { item, count, nbt }
    }

    #[must_use]
    pub const fn with_count(mut self, count: i8) -> Self {
        self.count = count;
        self
    }

    #[must_use]
    pub const fn with_item(mut self, item: ItemKind) -> Self {
        self.item = item;
        self
    }

    #[must_use]
    pub fn with_nbt(mut self, nbt: impl Into<Option<Compound>>) -> Self {
        self.nbt = nbt.into();
        self
    }

    pub const fn is_empty(&self) -> bool {
        matches!(self.item, ItemKind::Air) || self.count <= 0
    }
}

impl Encode for ItemStack {
    fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
        if self.is_empty() {
            false.encode(w)
        } else {
            true.encode(&mut w)?;
            self.item.encode(&mut w)?;
            self.count.encode(&mut w)?;
            match &self.nbt {
                Some(n) => n.encode(w),
                None => 0u8.encode(w),
            }
        }
    }
}

impl Decode<'_> for ItemStack {
    fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
        let present = bool::decode(r)?;
        if !present {
            return Ok(ItemStack::EMPTY);
        };

        let item = ItemKind::decode(r)?;
        let count = i8::decode(r)?;

        let nbt = if let [0, rest @ ..] = *r {
            *r = rest;
            None
        } else {
            Some(Compound::decode(r)?)
        };

        let stack = ItemStack { item, count, nbt };

        // Normalize empty item stacks.
        if stack.is_empty() {
            Ok(ItemStack::EMPTY)
        } else {
            Ok(stack)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty_item_stack_is_empty() {
        let air_stack = ItemStack::new(ItemKind::Air, 10, None);
        let less_then_one_stack = ItemStack::new(ItemKind::Stone, 0, None);

        assert!(air_stack.is_empty());
        assert!(less_then_one_stack.is_empty());

        assert!(ItemStack::EMPTY.is_empty());

        let not_empty_stack = ItemStack::new(ItemKind::Stone, 10, None);

        assert!(!not_empty_stack.is_empty());
    }
}