Skip to main content

steel_registry/data_components/components/
pot_decorations.rs

1//! Vanilla `minecraft:pot_decorations` item component.
2
3use std::io::{Cursor, Error, Result, Write};
4use std::str::FromStr;
5
6use simdnbt::owned::{NbtList, NbtTag};
7use simdnbt::{FromNbtTag, ToNbtTag};
8use steel_utils::Identifier;
9use steel_utils::codec::VarInt;
10use steel_utils::hash::{ComponentHasher, HashComponent};
11use steel_utils::serial::{ReadFrom, WriteTo};
12
13use crate::items::ItemRef;
14use crate::{REGISTRY, RegistryEntry, RegistryExt, vanilla_items};
15
16/// The back, left, right, and front decorations of a decorated pot.
17#[derive(Debug, Clone, PartialEq)]
18pub struct PotDecorations {
19    back: Option<ItemRef>,
20    left: Option<ItemRef>,
21    right: Option<ItemRef>,
22    front: Option<ItemRef>,
23}
24
25impl PotDecorations {
26    pub const MAX_DECORATIONS: usize = 4;
27    pub const EMPTY: Self = Self {
28        back: None,
29        left: None,
30        right: None,
31        front: None,
32    };
33
34    /// Constructs the component from Vanilla's ordered, at-most-four item list.
35    pub fn from_ordered(items: &[ItemRef]) -> Result<Self> {
36        if items.len() > Self::MAX_DECORATIONS {
37            return Err(Error::other(format!(
38                "Got {} pot decorations, but maximum is {}",
39                items.len(),
40                Self::MAX_DECORATIONS
41            )));
42        }
43        Ok(Self {
44            back: decoration(items.first().copied()),
45            left: decoration(items.get(1).copied()),
46            right: decoration(items.get(2).copied()),
47            front: decoration(items.get(3).copied()),
48        })
49    }
50
51    #[must_use]
52    pub const fn back(&self) -> Option<ItemRef> {
53        self.back
54    }
55
56    #[must_use]
57    pub const fn left(&self) -> Option<ItemRef> {
58        self.left
59    }
60
61    #[must_use]
62    pub const fn right(&self) -> Option<ItemRef> {
63        self.right
64    }
65
66    #[must_use]
67    pub const fn front(&self) -> Option<ItemRef> {
68        self.front
69    }
70
71    #[must_use]
72    pub fn ordered(&self) -> [ItemRef; Self::MAX_DECORATIONS] {
73        [
74            self.back.unwrap_or(&vanilla_items::BRICK),
75            self.left.unwrap_or(&vanilla_items::BRICK),
76            self.right.unwrap_or(&vanilla_items::BRICK),
77            self.front.unwrap_or(&vanilla_items::BRICK),
78        ]
79    }
80}
81
82fn decoration(item: Option<ItemRef>) -> Option<ItemRef> {
83    item.filter(|item| *item != &*vanilla_items::BRICK)
84}
85
86impl WriteTo for PotDecorations {
87    fn write(&self, writer: &mut impl Write) -> Result<()> {
88        VarInt(Self::MAX_DECORATIONS as i32).write(writer)?;
89        for item in self.ordered() {
90            let id = i32::try_from(item.id())
91                .map_err(|_| Error::other(format!("Item id is too large: {}", item.id())))?;
92            VarInt(id).write(writer)?;
93        }
94        Ok(())
95    }
96}
97
98impl ReadFrom for PotDecorations {
99    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
100        let count = VarInt::read(data)?.0;
101        let count =
102            usize::try_from(count).map_err(|_| Error::other("Negative pot decoration count"))?;
103        if count > Self::MAX_DECORATIONS {
104            return Err(Error::other(format!(
105                "Got {count} pot decorations, but maximum is {}",
106                Self::MAX_DECORATIONS
107            )));
108        }
109        let mut items = Vec::with_capacity(count);
110        for _ in 0..count {
111            let id = VarInt::read(data)?.0;
112            let id =
113                usize::try_from(id).map_err(|_| Error::other(format!("Negative item id: {id}")))?;
114            let item = REGISTRY
115                .items
116                .by_id(id)
117                .ok_or_else(|| Error::other(format!("Unknown item id: {id}")))?;
118            items.push(item);
119        }
120        Self::from_ordered(&items)
121    }
122}
123
124impl ToNbtTag for PotDecorations {
125    fn to_nbt_tag(self) -> NbtTag {
126        NbtTag::List(NbtList::String(
127            self.ordered()
128                .into_iter()
129                .map(|item| item.key.to_string().into())
130                .collect(),
131        ))
132    }
133}
134
135impl FromNbtTag for PotDecorations {
136    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
137        let values = tag.list()?.to_owned().as_nbt_tags();
138        if values.len() > Self::MAX_DECORATIONS {
139            return None;
140        }
141        let items = values
142            .iter()
143            .map(|value| {
144                let key = Identifier::from_str(&value.string()?.to_string()).ok()?;
145                REGISTRY.items.by_key(&key)
146            })
147            .collect::<Option<Vec<_>>>()?;
148        Self::from_ordered(&items).ok()
149    }
150}
151
152impl HashComponent for PotDecorations {
153    fn hash_component(&self, hasher: &mut ComponentHasher) {
154        hasher.start_list();
155        for item in self.ordered() {
156            hasher.put_component_hash(&item.key.to_string());
157        }
158        hasher.end_list();
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use std::io::Cursor;
165
166    use simdnbt::borrow::read_tag;
167    use simdnbt::{FromNbtTag as _, ToNbtTag as _};
168    use steel_utils::hash::HashComponent as _;
169    use steel_utils::serial::{ReadFrom as _, WriteTo as _};
170
171    use super::PotDecorations;
172    use crate::data_components::vanilla_components::POT_DECORATIONS;
173    use crate::init_vanilla_registry;
174    use crate::vanilla_items;
175
176    fn parse(tag: simdnbt::owned::NbtTag) -> Option<PotDecorations> {
177        let mut bytes = Vec::new();
178        tag.write(&mut bytes);
179        let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
180        PotDecorations::from_nbt_tag(borrowed.as_tag())
181    }
182
183    #[test]
184    fn ordered_sides_round_trip_both_codecs_and_hash_as_four_items() {
185        init_vanilla_registry();
186        let decorations = PotDecorations::from_ordered(&[
187            &vanilla_items::ANGLER_POTTERY_SHERD,
188            &vanilla_items::BRICK,
189            &vanilla_items::ARCHER_POTTERY_SHERD,
190        ])
191        .expect("three decorations should fit");
192        assert_eq!(
193            decorations.back(),
194            Some(&*vanilla_items::ANGLER_POTTERY_SHERD)
195        );
196        assert_eq!(decorations.left(), None);
197        assert_eq!(decorations.front(), None);
198
199        let nbt = decorations.clone().to_nbt_tag();
200        assert_eq!(parse(nbt.clone()), Some(decorations.clone()));
201        assert_eq!(decorations.compute_hash(), nbt.compute_hash());
202
203        let mut network = Vec::new();
204        decorations
205            .write(&mut network)
206            .expect("decorations should encode");
207        assert_eq!(
208            PotDecorations::read(&mut Cursor::new(network.as_slice()))
209                .expect("decorations should decode"),
210            decorations
211        );
212    }
213
214    #[test]
215    fn extracted_decorated_pot_uses_four_bricks_as_empty_sides() {
216        init_vanilla_registry();
217        assert_eq!(
218            vanilla_items::DECORATED_POT.components.get(POT_DECORATIONS),
219            Some(PotDecorations::EMPTY)
220        );
221    }
222
223    #[test]
224    fn both_codecs_reject_more_than_four_items() {
225        init_vanilla_registry();
226        let items = [&*vanilla_items::BRICK; 5];
227        assert!(PotDecorations::from_ordered(&items).is_err());
228
229        let mut network = vec![5];
230        network.extend([0; 5]);
231        assert!(PotDecorations::read(&mut Cursor::new(network.as_slice())).is_err());
232    }
233}