Skip to main content

steel_registry/data_components/components/
painting_variant.rs

1//! Vanilla `minecraft:painting/variant` item component.
2
3use std::io::{Cursor, Error, Result, Write};
4use std::str::FromStr;
5
6use simdnbt::FromNbtTag;
7use simdnbt::owned::NbtTag;
8use steel_utils::Identifier;
9use steel_utils::hash::{ComponentHasher, HashComponent};
10use steel_utils::serial::{ReadFrom, WriteTo};
11
12use crate::painting_variant::{PaintingVariant, PaintingVariantRef, PaintingVariantValue};
13use crate::{REGISTRY, RegistryExt, RegistryHolder};
14
15/// Registry-owned painting variant stored on a painting item.
16///
17#[derive(Debug, Clone, PartialEq)]
18pub struct PaintingVariantComponent {
19    variant: RegistryHolder<PaintingVariant>,
20}
21
22impl PaintingVariantComponent {
23    #[must_use]
24    pub const fn new(variant: PaintingVariantRef) -> Self {
25        Self {
26            variant: RegistryHolder::reference(variant),
27        }
28    }
29
30    #[must_use]
31    pub const fn direct(variant: PaintingVariantValue) -> Self {
32        Self {
33            variant: RegistryHolder::direct(variant),
34        }
35    }
36
37    #[must_use]
38    pub const fn variant(&self) -> &RegistryHolder<PaintingVariant> {
39        &self.variant
40    }
41}
42
43impl WriteTo for PaintingVariantComponent {
44    fn write(&self, writer: &mut impl Write) -> Result<()> {
45        self.variant.write(writer)
46    }
47}
48
49impl ReadFrom for PaintingVariantComponent {
50    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
51        RegistryHolder::read(data).map(|variant| Self { variant })
52    }
53}
54
55impl FromNbtTag for PaintingVariantComponent {
56    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
57        let key = Identifier::from_str(&tag.string()?.to_str()).ok()?;
58        REGISTRY.painting_variants.by_key(&key).map(Self::new)
59    }
60}
61
62impl HashComponent for PaintingVariantComponent {
63    fn hash_component(&self, hasher: &mut ComponentHasher) {
64        self.variant.hash_component(hasher);
65    }
66}
67
68impl PaintingVariantComponent {
69    pub(crate) fn try_to_persistent_nbt(&self) -> Result<NbtTag> {
70        let Some(variant) = self.variant.as_reference() else {
71            return Err(Error::other(
72                "Direct painting variant holder is not persistent",
73            ));
74        };
75        Ok(NbtTag::String(variant.key.to_string().into()))
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use std::io::Cursor;
82
83    use simdnbt::FromNbtTag as _;
84    use simdnbt::borrow::read_tag;
85    use steel_utils::hash::HashComponent as _;
86    use steel_utils::serial::{ReadFrom as _, WriteTo as _};
87
88    use super::PaintingVariantComponent;
89    use crate::init_vanilla_registry;
90    use crate::vanilla_painting_variants;
91
92    #[test]
93    fn registry_reference_round_trips_both_codecs() {
94        init_vanilla_registry();
95        let component = PaintingVariantComponent::new(&vanilla_painting_variants::KEBAB);
96
97        let mut network = Vec::new();
98        component
99            .write(&mut network)
100            .expect("variant should encode");
101        assert_eq!(
102            PaintingVariantComponent::read(&mut Cursor::new(network.as_slice()))
103                .expect("variant should decode"),
104            component
105        );
106
107        let nbt = component
108            .try_to_persistent_nbt()
109            .expect("reference is persistent");
110        assert_eq!(component.compute_hash(), nbt.compute_hash());
111        let mut bytes = Vec::new();
112        nbt.write(&mut bytes);
113        let borrowed =
114            read_tag(&mut Cursor::new(bytes.as_slice())).expect("variant NBT should parse");
115        assert_eq!(
116            PaintingVariantComponent::from_nbt_tag(borrowed.as_tag()),
117            Some(component)
118        );
119    }
120
121    #[test]
122    fn direct_stream_holder_round_trips_but_is_not_persistent() {
123        use crate::painting_variant::PaintingVariantValue;
124        use steel_utils::Identifier;
125
126        let component = PaintingVariantComponent::direct(PaintingVariantValue {
127            width: -1,
128            height: 32,
129            asset_id: Identifier::vanilla_static("custom"),
130            title: None,
131            author: None,
132        });
133        let mut network = Vec::new();
134        component
135            .write(&mut network)
136            .expect("direct variant should encode");
137        assert_eq!(
138            PaintingVariantComponent::read(&mut Cursor::new(network.as_slice()))
139                .expect("direct variant should decode"),
140            component
141        );
142        assert!(component.try_to_persistent_nbt().is_err());
143    }
144}