Skip to main content

steel_registry/data_components/components/
pot_decorations.rs

1//! Vanilla `minecraft:pot_decorations` item component.
2
3use std::io::{Cursor, Result, Write};
4
5use simdnbt::owned::{NbtCompound, NbtTag};
6use simdnbt::{FromNbtTag, ToNbtTag};
7use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
8use steel_utils::serial::{ReadFrom, WriteTo};
9
10use crate::ItemStackTemplate;
11
12/// The back, left, right, and front decorations of a decorated pot.
13#[derive(Debug, Clone, PartialEq)]
14pub struct PotDecorations {
15    back: Option<ItemStackTemplate>,
16    left: Option<ItemStackTemplate>,
17    right: Option<ItemStackTemplate>,
18    front: Option<ItemStackTemplate>,
19}
20
21impl PotDecorations {
22    pub const EMPTY: Self = Self {
23        back: None,
24        left: None,
25        right: None,
26        front: None,
27    };
28
29    #[must_use]
30    pub const fn new(
31        back: Option<ItemStackTemplate>,
32        left: Option<ItemStackTemplate>,
33        right: Option<ItemStackTemplate>,
34        front: Option<ItemStackTemplate>,
35    ) -> Self {
36        Self {
37            back,
38            left,
39            right,
40            front,
41        }
42    }
43
44    #[must_use]
45    pub const fn back(&self) -> Option<&ItemStackTemplate> {
46        self.back.as_ref()
47    }
48
49    #[must_use]
50    pub const fn left(&self) -> Option<&ItemStackTemplate> {
51        self.left.as_ref()
52    }
53
54    #[must_use]
55    pub const fn right(&self) -> Option<&ItemStackTemplate> {
56        self.right.as_ref()
57    }
58
59    #[must_use]
60    pub const fn front(&self) -> Option<&ItemStackTemplate> {
61        self.front.as_ref()
62    }
63}
64
65impl WriteTo for PotDecorations {
66    fn write(&self, writer: &mut impl Write) -> Result<()> {
67        self.back.write(writer)?;
68        self.left.write(writer)?;
69        self.right.write(writer)?;
70        self.front.write(writer)
71    }
72}
73
74impl ReadFrom for PotDecorations {
75    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
76        Ok(Self {
77            back: Option::<ItemStackTemplate>::read(data)?,
78            left: Option::<ItemStackTemplate>::read(data)?,
79            right: Option::<ItemStackTemplate>::read(data)?,
80            front: Option::<ItemStackTemplate>::read(data)?,
81        })
82    }
83}
84
85impl ToNbtTag for PotDecorations {
86    fn to_nbt_tag(self) -> NbtTag {
87        let mut compound = NbtCompound::new();
88        if let Some(back) = self.back {
89            compound.insert("back", back.to_nbt_tag());
90        }
91        if let Some(left) = self.left {
92            compound.insert("left", left.to_nbt_tag());
93        }
94        if let Some(right) = self.right {
95            compound.insert("right", right.to_nbt_tag());
96        }
97        if let Some(front) = self.front {
98            compound.insert("front", front.to_nbt_tag());
99        }
100        NbtTag::Compound(compound)
101    }
102}
103
104impl FromNbtTag for PotDecorations {
105    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
106        let compound = tag.compound()?;
107        Some(Self {
108            back: optional_side(compound.get("back"))?,
109            left: optional_side(compound.get("left"))?,
110            right: optional_side(compound.get("right"))?,
111            front: optional_side(compound.get("front"))?,
112        })
113    }
114}
115
116#[expect(
117    clippy::option_option,
118    reason = "the outer option reports codec failure while the inner option represents an absent side"
119)]
120fn optional_side(
121    tag: Option<simdnbt::borrow::NbtTag<'_, '_>>,
122) -> Option<Option<ItemStackTemplate>> {
123    match tag {
124        Some(tag) => Some(Some(ItemStackTemplate::from_nbt_tag(tag)?)),
125        None => Some(None),
126    }
127}
128
129impl HashComponent for PotDecorations {
130    fn hash_component(&self, hasher: &mut ComponentHasher) {
131        let mut entries = Vec::with_capacity(4);
132        if let Some(back) = &self.back {
133            push_hash_entry(&mut entries, "back", back);
134        }
135        if let Some(left) = &self.left {
136            push_hash_entry(&mut entries, "left", left);
137        }
138        if let Some(right) = &self.right {
139            push_hash_entry(&mut entries, "right", right);
140        }
141        if let Some(front) = &self.front {
142            push_hash_entry(&mut entries, "front", front);
143        }
144        sort_map_entries(&mut entries);
145        hasher.start_map();
146        for entry in entries {
147            hasher.put_raw_bytes(&entry.key_bytes);
148            hasher.put_raw_bytes(&entry.value_bytes);
149        }
150        hasher.end_map();
151    }
152}
153
154fn push_hash_entry<T: HashComponent + ?Sized>(entries: &mut Vec<HashEntry>, key: &str, value: &T) {
155    let mut key_hasher = ComponentHasher::new();
156    key_hasher.put_string(key);
157    let mut value_hasher = ComponentHasher::new();
158    value.hash_component(&mut value_hasher);
159    entries.push(HashEntry::new(key_hasher, value_hasher));
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::{ItemStackTemplate, 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 named_sides_round_trip_both_codecs_and_hash() {
185        init_vanilla_registry();
186        let decorations = PotDecorations::new(
187            Some(ItemStackTemplate::new(&vanilla_items::ANGLER_POTTERY_SHERD)),
188            None,
189            Some(ItemStackTemplate::new(&vanilla_items::ARCHER_POTTERY_SHERD)),
190            None,
191        );
192        assert!(decorations.back().is_some());
193        assert!(decorations.left().is_none());
194
195        let nbt = decorations.clone().to_nbt_tag();
196        assert_eq!(parse(nbt.clone()), Some(decorations.clone()));
197        assert_eq!(decorations.compute_hash(), nbt.compute_hash());
198
199        let mut network = Vec::new();
200        decorations
201            .write(&mut network)
202            .expect("decorations should encode");
203        assert_eq!(
204            PotDecorations::read(&mut Cursor::new(network.as_slice()))
205                .expect("decorations should decode"),
206            decorations
207        );
208    }
209
210    #[test]
211    fn extracted_decorated_pot_defaults_to_brick_on_every_side() {
212        init_vanilla_registry();
213        let decorations = vanilla_items::DECORATED_POT
214            .components
215            .get(POT_DECORATIONS)
216            .expect("decorated pot should have a pot_decorations component");
217        assert!(decorations.back().is_some());
218        assert!(decorations.left().is_some());
219        assert!(decorations.right().is_some());
220        assert!(decorations.front().is_some());
221    }
222}