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 rustc_hash::FxHashMap;
70    use simdnbt::borrow::read_tag;
71    use simdnbt::{FromNbtTag as _, ToNbtTag as _};
72    use steel_utils::Identifier;
73    use steel_utils::codec::VarInt;
74    use steel_utils::hash::HashComponent as _;
75    use steel_utils::serial::{PrefixedWrite as _, ReadFrom as _, WriteTo as _};
76    use text_components::{TextComponent, format::Color};
77
78    use super::ProvidesTrimMaterial;
79    use crate::RegistryHolder;
80    use crate::data_components::vanilla_components::PROVIDES_TRIM_MATERIAL;
81    use crate::init_vanilla_registry;
82    use crate::item_stack::ItemStack;
83    use crate::trim_material::{MaterialAssetGroup, MaterialAssetInfo, TrimMaterialValue};
84    use crate::{REGISTRY, vanilla_items, vanilla_trim_materials};
85
86    fn parse_component(tag: simdnbt::owned::NbtTag) -> Option<ProvidesTrimMaterial> {
87        let mut bytes = Vec::new();
88        tag.write(&mut bytes);
89        let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
90        ProvidesTrimMaterial::from_nbt_tag(borrowed.as_tag())
91    }
92
93    fn inline_component() -> ProvidesTrimMaterial {
94        let mut description = TextComponent::plain("Custom trim material");
95        description.format.color = Some(Color::Rgb(0x12, 0x34, 0x56));
96        let overrides = FxHashMap::from_iter([(
97            Identifier::vanilla_static("iron"),
98            MaterialAssetInfo::new("custom_darker").expect("test suffix should be valid"),
99        )]);
100        ProvidesTrimMaterial::new(RegistryHolder::direct(TrimMaterialValue::new(
101            MaterialAssetGroup::new(
102                MaterialAssetInfo::new("custom").expect("test suffix should be valid"),
103                overrides,
104            ),
105            description,
106        )))
107    }
108
109    #[test]
110    fn registry_reference_round_trips_both_codecs() {
111        init_vanilla_registry();
112        let component =
113            ProvidesTrimMaterial::new(RegistryHolder::reference(&vanilla_trim_materials::IRON));
114
115        let mut network = Vec::new();
116        component
117            .write(&mut network)
118            .expect("registry trim material should encode");
119        assert_eq!(
120            ProvidesTrimMaterial::read(&mut Cursor::new(network.as_slice()))
121                .expect("registry trim material should decode"),
122            component
123        );
124
125        let nbt = component.clone().to_nbt_tag();
126        assert_eq!(nbt, simdnbt::owned::NbtTag::String("minecraft:iron".into()));
127        assert_eq!(parse_component(nbt), Some(component));
128    }
129
130    #[test]
131    fn inline_material_round_trips_and_hashes_its_flattened_record() {
132        init_vanilla_registry();
133        let component = inline_component();
134
135        let mut network = Vec::new();
136        component
137            .write(&mut network)
138            .expect("inline trim material should encode");
139        assert_eq!(
140            ProvidesTrimMaterial::read(&mut Cursor::new(network.as_slice()))
141                .expect("inline trim material should decode"),
142            component
143        );
144
145        let nbt = component.clone().to_nbt_tag();
146        assert_eq!(parse_component(nbt.clone()), Some(component.clone()));
147        assert_eq!(component.compute_hash(), nbt.compute_hash());
148
149        let simdnbt::owned::NbtTag::Compound(compound) = nbt else {
150            panic!("inline trim material should encode as a compound");
151        };
152        assert_eq!(
153            compound
154                .compound("description")
155                .and_then(|description| description.string("color"))
156                .map(|color| color.to_str().into_owned()),
157            Some("#123456".to_owned())
158        );
159    }
160
161    #[test]
162    fn invalid_asset_suffixes_are_rejected_by_both_codecs() {
163        init_vanilla_registry();
164        assert!(MaterialAssetInfo::new("Bad Suffix").is_err());
165
166        let mut network = Vec::new();
167        VarInt(0)
168            .write(&mut network)
169            .expect("direct holder discriminator should encode");
170        "Bad Suffix"
171            .write_prefixed::<VarInt>(&mut network)
172            .expect("invalid test suffix should encode as a string");
173        VarInt(0)
174            .write(&mut network)
175            .expect("empty override map should encode");
176        TextComponent::plain("Invalid material")
177            .write(&mut network)
178            .expect("description should encode");
179        assert!(ProvidesTrimMaterial::read(&mut Cursor::new(network.as_slice())).is_err());
180
181        let mut invalid = simdnbt::owned::NbtCompound::new();
182        invalid.insert("asset_name", "Bad Suffix");
183        invalid.insert("description", "Invalid material");
184        assert!(parse_component(simdnbt::owned::NbtTag::Compound(invalid)).is_none());
185    }
186
187    #[test]
188    fn extracted_item_prototypes_reference_every_vanilla_trim_material() {
189        init_vanilla_registry();
190        let prototypes = [
191            (
192                &*vanilla_items::REDSTONE,
193                &*vanilla_trim_materials::REDSTONE,
194            ),
195            (&*vanilla_items::DIAMOND, &*vanilla_trim_materials::DIAMOND),
196            (&*vanilla_items::EMERALD, &*vanilla_trim_materials::EMERALD),
197            (
198                &*vanilla_items::LAPIS_LAZULI,
199                &*vanilla_trim_materials::LAPIS,
200            ),
201            (&*vanilla_items::QUARTZ, &*vanilla_trim_materials::QUARTZ),
202            (
203                &*vanilla_items::AMETHYST_SHARD,
204                &*vanilla_trim_materials::AMETHYST,
205            ),
206            (&*vanilla_items::IRON_INGOT, &*vanilla_trim_materials::IRON),
207            (
208                &*vanilla_items::COPPER_INGOT,
209                &*vanilla_trim_materials::COPPER,
210            ),
211            (&*vanilla_items::GOLD_INGOT, &*vanilla_trim_materials::GOLD),
212            (
213                &*vanilla_items::NETHERITE_INGOT,
214                &*vanilla_trim_materials::NETHERITE,
215            ),
216            (
217                &*vanilla_items::RESIN_BRICK,
218                &*vanilla_trim_materials::RESIN,
219            ),
220        ];
221
222        assert_eq!(
223            REGISTRY
224                .items
225                .iter()
226                .filter(|(_, item)| item.components.has(PROVIDES_TRIM_MATERIAL))
227                .count(),
228            prototypes.len()
229        );
230        for (item, material) in prototypes {
231            assert_eq!(
232                ItemStack::new(item)
233                    .get(PROVIDES_TRIM_MATERIAL)
234                    .and_then(|component| component.material().as_reference()),
235                Some(material)
236            );
237        }
238    }
239}