Skip to main content

steel_registry/data_components/components/
provides_trim_material.rs

1//! Vanilla `minecraft:provides_trim_material` item component.
2
3use std::io::{Cursor, Result, Write};
4
5use simdnbt::{FromNbtTag, ToNbtTag};
6use steel_utils::hash::{ComponentHasher, HashComponent};
7use steel_utils::serial::{ReadFrom, WriteTo};
8
9use crate::RegistryHolder;
10use crate::trim_material::{TrimMaterial, TrimMaterialValue};
11
12/// Trim material supplied by an ingredient in the smithing table.
13#[derive(Debug, Clone, PartialEq)]
14pub struct ProvidesTrimMaterial {
15    material: RegistryHolder<TrimMaterial>,
16}
17
18impl ProvidesTrimMaterial {
19    #[must_use]
20    pub const fn new(material: RegistryHolder<TrimMaterial>) -> Self {
21        Self { material }
22    }
23
24    #[must_use]
25    pub const fn material(&self) -> &RegistryHolder<TrimMaterial> {
26        &self.material
27    }
28
29    #[must_use]
30    pub fn value(&self) -> &TrimMaterialValue {
31        self.material.value()
32    }
33}
34
35impl WriteTo for ProvidesTrimMaterial {
36    fn write(&self, writer: &mut impl Write) -> Result<()> {
37        self.material.write(writer)
38    }
39}
40
41impl ReadFrom for ProvidesTrimMaterial {
42    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
43        RegistryHolder::read(data).map(Self::new)
44    }
45}
46
47impl ToNbtTag for ProvidesTrimMaterial {
48    fn to_nbt_tag(self) -> simdnbt::owned::NbtTag {
49        self.material.to_nbt_tag()
50    }
51}
52
53impl FromNbtTag for ProvidesTrimMaterial {
54    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
55        RegistryHolder::from_nbt_tag(tag).map(Self::new)
56    }
57}
58
59impl HashComponent for ProvidesTrimMaterial {
60    fn hash_component(&self, hasher: &mut ComponentHasher) {
61        self.material.hash_component(hasher);
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use std::io::Cursor;
68
69    use simdnbt::borrow::read_tag;
70    use simdnbt::{FromNbtTag as _, ToNbtTag as _};
71    use steel_utils::Identifier;
72    use steel_utils::hash::HashComponent as _;
73    use steel_utils::serial::{ReadFrom as _, WriteTo as _};
74    use text_components::{TextComponent, format::Color};
75
76    use super::ProvidesTrimMaterial;
77    use crate::RegistryHolder;
78    use crate::data_components::vanilla_components::PROVIDES_TRIM_MATERIAL;
79    use crate::init_vanilla_registry;
80    use crate::item_stack::ItemStack;
81    use crate::trim_material::TrimMaterialValue;
82    use crate::{REGISTRY, vanilla_items, vanilla_trim_materials};
83
84    fn parse_component(tag: simdnbt::owned::NbtTag) -> Option<ProvidesTrimMaterial> {
85        let mut bytes = Vec::new();
86        tag.write(&mut bytes);
87        let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
88        ProvidesTrimMaterial::from_nbt_tag(borrowed.as_tag())
89    }
90
91    fn inline_component() -> ProvidesTrimMaterial {
92        let mut description = TextComponent::plain("Custom trim material");
93        description.format.color = Some(Color::Rgb(0x12, 0x34, 0x56));
94        ProvidesTrimMaterial::new(RegistryHolder::direct(TrimMaterialValue::new(
95            Identifier::vanilla_static("trim/custom"),
96            description,
97        )))
98    }
99
100    #[test]
101    fn registry_reference_round_trips_both_codecs() {
102        init_vanilla_registry();
103        let component =
104            ProvidesTrimMaterial::new(RegistryHolder::reference(&vanilla_trim_materials::IRON));
105
106        let mut network = Vec::new();
107        component
108            .write(&mut network)
109            .expect("registry trim material should encode");
110        assert_eq!(
111            ProvidesTrimMaterial::read(&mut Cursor::new(network.as_slice()))
112                .expect("registry trim material should decode"),
113            component
114        );
115
116        let nbt = component.clone().to_nbt_tag();
117        assert_eq!(nbt, simdnbt::owned::NbtTag::String("minecraft:iron".into()));
118        assert_eq!(parse_component(nbt), Some(component));
119    }
120
121    #[test]
122    fn inline_material_round_trips_and_hashes_its_flattened_record() {
123        init_vanilla_registry();
124        let component = inline_component();
125
126        let mut network = Vec::new();
127        component
128            .write(&mut network)
129            .expect("inline trim material should encode");
130        assert_eq!(
131            ProvidesTrimMaterial::read(&mut Cursor::new(network.as_slice()))
132                .expect("inline trim material should decode"),
133            component
134        );
135
136        let nbt = component.clone().to_nbt_tag();
137        assert_eq!(parse_component(nbt.clone()), Some(component.clone()));
138        assert_eq!(component.compute_hash(), nbt.compute_hash());
139
140        let simdnbt::owned::NbtTag::Compound(compound) = nbt else {
141            panic!("inline trim material should encode as a compound");
142        };
143        assert_eq!(
144            compound
145                .compound("description")
146                .and_then(|description| description.string("color"))
147                .map(|color| color.to_str().into_owned()),
148            Some("#123456".to_owned())
149        );
150    }
151
152    #[test]
153    fn extracted_item_prototypes_reference_every_vanilla_trim_material() {
154        init_vanilla_registry();
155        let prototypes = [
156            (
157                &*vanilla_items::REDSTONE,
158                &*vanilla_trim_materials::REDSTONE,
159            ),
160            (&*vanilla_items::DIAMOND, &*vanilla_trim_materials::DIAMOND),
161            (&*vanilla_items::EMERALD, &*vanilla_trim_materials::EMERALD),
162            (
163                &*vanilla_items::LAPIS_LAZULI,
164                &*vanilla_trim_materials::LAPIS,
165            ),
166            (&*vanilla_items::QUARTZ, &*vanilla_trim_materials::QUARTZ),
167            (
168                &*vanilla_items::AMETHYST_SHARD,
169                &*vanilla_trim_materials::AMETHYST,
170            ),
171            (&*vanilla_items::IRON_INGOT, &*vanilla_trim_materials::IRON),
172            (
173                &*vanilla_items::COPPER_INGOT,
174                &*vanilla_trim_materials::COPPER,
175            ),
176            (&*vanilla_items::GOLD_INGOT, &*vanilla_trim_materials::GOLD),
177            (
178                &*vanilla_items::NETHERITE_INGOT,
179                &*vanilla_trim_materials::NETHERITE,
180            ),
181            (
182                &*vanilla_items::RESIN_BRICK,
183                &*vanilla_trim_materials::RESIN,
184            ),
185        ];
186
187        assert_eq!(
188            REGISTRY
189                .items
190                .iter()
191                .filter(|(_, item)| item.components.has(PROVIDES_TRIM_MATERIAL))
192                .count(),
193            prototypes.len()
194        );
195        for (item, material) in prototypes {
196            assert_eq!(
197                ItemStack::new(item)
198                    .get(PROVIDES_TRIM_MATERIAL)
199                    .and_then(|component| component.material().as_reference()),
200                Some(material)
201            );
202        }
203    }
204}