steel_registry/data_components/components/
pot_decorations.rs1use 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#[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}