Skip to main content

steel_registry/data_components/components/
banner_patterns.rs

1//! Vanilla `minecraft:banner_patterns` item component.
2
3use 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/// One pattern and color layer on a banner or shield.
15#[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/// Ordered banner pattern layers.
96#[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}