steel_registry/data_components/components/
banner_patterns.rs1use std::io::{Cursor, Error, Result, Write};
4
5use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
6use simdnbt::{FromNbtTag, ToNbtTag};
7use steel_utils::codec::VarInt;
8use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
9use steel_utils::serial::{ReadFrom, WriteTo};
10
11use crate::banner_pattern::BannerPattern;
12use crate::{DyeColor, RegistryHolder};
13
14#[derive(Debug, Clone, PartialEq)]
16pub struct BannerPatternLayer {
17 pattern: RegistryHolder<BannerPattern>,
18 color: DyeColor,
19}
20
21impl BannerPatternLayer {
22 #[must_use]
23 pub const fn new(pattern: RegistryHolder<BannerPattern>, color: DyeColor) -> Self {
24 Self { pattern, color }
25 }
26
27 #[must_use]
28 pub const fn pattern(&self) -> &RegistryHolder<BannerPattern> {
29 &self.pattern
30 }
31
32 #[must_use]
33 pub const fn color(&self) -> DyeColor {
34 self.color
35 }
36
37 fn to_nbt_tag_ref(&self) -> NbtTag {
38 let mut compound = NbtCompound::new();
39 compound.insert("pattern", self.pattern.clone().to_nbt_tag());
40 compound.insert("color", self.color.to_nbt_tag());
41 NbtTag::Compound(compound)
42 }
43
44 fn from_nbt_compound(compound: simdnbt::borrow::NbtCompound<'_, '_>) -> Option<Self> {
45 Some(Self::new(
46 RegistryHolder::from_nbt_tag(compound.get("pattern")?)?,
47 DyeColor::from_nbt_tag(compound.get("color")?)?,
48 ))
49 }
50}
51
52impl WriteTo for BannerPatternLayer {
53 fn write(&self, writer: &mut impl Write) -> Result<()> {
54 self.pattern.write(writer)?;
55 self.color.write(writer)
56 }
57}
58
59impl ReadFrom for BannerPatternLayer {
60 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
61 Ok(Self::new(
62 RegistryHolder::read(data)?,
63 DyeColor::read(data)?,
64 ))
65 }
66}
67
68impl ToNbtTag for BannerPatternLayer {
69 fn to_nbt_tag(self) -> NbtTag {
70 self.to_nbt_tag_ref()
71 }
72}
73
74impl FromNbtTag for BannerPatternLayer {
75 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
76 Self::from_nbt_compound(tag.compound()?)
77 }
78}
79
80impl HashComponent for BannerPatternLayer {
81 fn hash_component(&self, hasher: &mut ComponentHasher) {
82 let mut entries = Vec::with_capacity(2);
83 push_hash_entry(&mut entries, "pattern", &self.pattern);
84 push_hash_entry(&mut entries, "color", &self.color);
85 sort_map_entries(&mut entries);
86 hasher.start_map();
87 for entry in &entries {
88 hasher.put_raw_bytes(&entry.key_bytes);
89 hasher.put_raw_bytes(&entry.value_bytes);
90 }
91 hasher.end_map();
92 }
93}
94
95#[derive(Debug, Default, Clone, PartialEq)]
97pub struct BannerPatternLayers {
98 layers: Vec<BannerPatternLayer>,
99}
100
101impl BannerPatternLayers {
102 #[must_use]
103 pub const fn empty() -> Self {
104 Self { layers: Vec::new() }
105 }
106
107 #[must_use]
108 pub const fn new(layers: Vec<BannerPatternLayer>) -> Self {
109 Self { layers }
110 }
111
112 #[must_use]
113 pub fn layers(&self) -> &[BannerPatternLayer] {
114 &self.layers
115 }
116
117 fn to_nbt_tag_ref(&self) -> NbtTag {
118 if self.layers.is_empty() {
119 return NbtTag::List(NbtList::Empty);
120 }
121 NbtTag::List(NbtList::Compound(
122 self.layers
123 .iter()
124 .map(|layer| match layer.to_nbt_tag_ref() {
125 NbtTag::Compound(compound) => compound,
126 _ => unreachable!("banner layer codec always produces a compound"),
127 })
128 .collect(),
129 ))
130 }
131}
132
133impl WriteTo for BannerPatternLayers {
134 fn write(&self, writer: &mut impl Write) -> Result<()> {
135 let count = i32::try_from(self.layers.len())
136 .map_err(|_| Error::other("Too many banner pattern layers"))?;
137 VarInt(count).write(writer)?;
138 for layer in &self.layers {
139 layer.write(writer)?;
140 }
141 Ok(())
142 }
143}
144
145impl ReadFrom for BannerPatternLayers {
146 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
147 let count = VarInt::read(data)?.0;
148 let count = usize::try_from(count)
149 .map_err(|_| Error::other("Negative banner pattern layer count"))?;
150 let mut layers = Vec::with_capacity(count.min(65_536));
151 for _ in 0..count {
152 layers.push(BannerPatternLayer::read(data)?);
153 }
154 Ok(Self::new(layers))
155 }
156}
157
158impl ToNbtTag for BannerPatternLayers {
159 fn to_nbt_tag(self) -> NbtTag {
160 self.to_nbt_tag_ref()
161 }
162}
163
164impl FromNbtTag for BannerPatternLayers {
165 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
166 let list = tag.list()?;
167 if list.to_owned().as_nbt_tags().is_empty() {
168 return Some(Self::empty());
169 }
170 let layers = list
171 .compounds()?
172 .into_iter()
173 .map(BannerPatternLayer::from_nbt_compound)
174 .collect::<Option<Vec<_>>>()?;
175 Some(Self::new(layers))
176 }
177}
178
179impl HashComponent for BannerPatternLayers {
180 fn hash_component(&self, hasher: &mut ComponentHasher) {
181 hasher.start_list();
182 for layer in &self.layers {
183 hasher.put_component_hash(layer);
184 }
185 hasher.end_list();
186 }
187}
188
189fn push_hash_entry<T: HashComponent + ?Sized>(entries: &mut Vec<HashEntry>, key: &str, value: &T) {
190 let mut key_hasher = ComponentHasher::new();
191 key.hash_component(&mut key_hasher);
192 let mut value_hasher = ComponentHasher::new();
193 value.hash_component(&mut value_hasher);
194 entries.push(HashEntry::new(key_hasher, value_hasher));
195}
196
197#[cfg(test)]
198mod tests {
199 use std::borrow::Cow;
200 use std::io::Cursor;
201
202 use simdnbt::borrow::read_tag;
203 use simdnbt::{FromNbtTag as _, ToNbtTag as _};
204 use steel_utils::Identifier;
205 use steel_utils::hash::HashComponent as _;
206 use steel_utils::serial::{ReadFrom as _, WriteTo as _};
207
208 use super::{BannerPatternLayer, BannerPatternLayers};
209 use crate::banner_pattern::BannerPatternValue;
210 use crate::data_components::vanilla_components::BANNER_PATTERNS;
211 use crate::init_vanilla_registry;
212 use crate::{DyeColor, REGISTRY, RegistryHolder, vanilla_banner_patterns};
213
214 fn parse(tag: simdnbt::owned::NbtTag) -> Option<BannerPatternLayers> {
215 let mut bytes = Vec::new();
216 tag.write(&mut bytes);
217 let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
218 BannerPatternLayers::from_nbt_tag(borrowed.as_tag())
219 }
220
221 #[test]
222 fn layer_references_round_trip_both_codecs_and_hash_the_list() {
223 init_vanilla_registry();
224 let layers = BannerPatternLayers::new(vec![BannerPatternLayer::new(
225 RegistryHolder::reference(&vanilla_banner_patterns::CREEPER),
226 DyeColor::Lime,
227 )]);
228 let nbt = layers.clone().to_nbt_tag();
229 assert_eq!(parse(nbt), Some(layers.clone()));
230
231 let mut network = Vec::new();
232 layers.write(&mut network).expect("layers should encode");
233 let decoded = BannerPatternLayers::read(&mut Cursor::new(network.as_slice()))
234 .expect("layers should decode");
235 assert_eq!(decoded, layers);
236 assert_eq!(decoded.compute_hash(), layers.compute_hash());
237 }
238
239 #[test]
240 fn inline_patterns_round_trip_both_holder_codecs() {
241 init_vanilla_registry();
242 let direct = BannerPatternValue::new(
243 Identifier::new_static("steel", "wave"),
244 Cow::Borrowed("block.steel.banner.wave"),
245 );
246 let layers = BannerPatternLayers::new(vec![BannerPatternLayer::new(
247 RegistryHolder::direct(direct),
248 DyeColor::Blue,
249 )]);
250 assert_eq!(parse(layers.clone().to_nbt_tag()), Some(layers.clone()));
251 let mut network = Vec::new();
252 layers.write(&mut network).expect("layers should encode");
253 assert_eq!(
254 BannerPatternLayers::read(&mut Cursor::new(network.as_slice()))
255 .expect("layers should decode"),
256 layers
257 );
258 }
259
260 #[test]
261 fn empty_layers_use_vanilla_empty_list_shape() {
262 let layers = BannerPatternLayers::empty();
263 assert_eq!(parse(layers.clone().to_nbt_tag()), Some(layers.clone()));
264 let mut network = Vec::new();
265 layers.write(&mut network).expect("layers should encode");
266 assert_eq!(network, [0]);
267 }
268
269 #[test]
270 fn extracted_banners_and_shield_keep_empty_layers() {
271 init_vanilla_registry();
272 let items = REGISTRY
273 .items
274 .iter()
275 .filter(|(_, item)| item.components.has(BANNER_PATTERNS))
276 .collect::<Vec<_>>();
277 assert_eq!(items.len(), 17);
278 assert!(items.iter().all(|(_, item)| {
279 item.components.get(BANNER_PATTERNS) == Some(BannerPatternLayers::empty())
280 }));
281 }
282}