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