Skip to main content

steel_registry/data_components/components/
registry_holder_sets.rs

1//! Components backed by registry holder sets.
2
3use std::io::{Cursor, Result, Write};
4
5use simdnbt::owned::{NbtCompound, NbtTag};
6use simdnbt::{FromNbtTag, ToNbtTag};
7use steel_utils::hash::{ComponentHasher, HashComponent};
8use steel_utils::serial::{ReadFrom, WriteTo};
9
10use crate::RegistryHolderSet;
11use crate::banner_pattern::BannerPattern;
12use crate::damage_type::{DamageType, DamageTypeRef};
13use crate::item_stack::ItemStack;
14use crate::items::Item;
15
16/// Banner patterns unlocked by an ingredient in the loom.
17pub type ProvidesBannerPatterns = RegistryHolderSet<BannerPattern>;
18
19/// Damage types that cannot hurt an item stack.
20#[derive(Debug, Clone, PartialEq)]
21pub struct DamageResistant {
22    types: RegistryHolderSet<DamageType>,
23}
24
25impl DamageResistant {
26    #[must_use]
27    pub const fn new(types: RegistryHolderSet<DamageType>) -> Self {
28        Self { types }
29    }
30
31    #[must_use]
32    pub const fn types(&self) -> &RegistryHolderSet<DamageType> {
33        &self.types
34    }
35
36    /// Returns whether this component protects against `damage_type`.
37    #[must_use]
38    pub fn is_resistant_to(&self, damage_type: DamageTypeRef) -> bool {
39        self.types.contains(damage_type)
40    }
41}
42
43impl WriteTo for DamageResistant {
44    fn write(&self, writer: &mut impl Write) -> Result<()> {
45        self.types.write(writer)
46    }
47}
48
49impl ReadFrom for DamageResistant {
50    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
51        Ok(Self::new(RegistryHolderSet::read(data)?))
52    }
53}
54
55impl ToNbtTag for DamageResistant {
56    fn to_nbt_tag(self) -> NbtTag {
57        let mut compound = NbtCompound::new();
58        compound.insert("types", self.types.to_nbt_tag());
59        NbtTag::Compound(compound)
60    }
61}
62
63impl FromNbtTag for DamageResistant {
64    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
65        let compound = tag.compound()?;
66        Some(Self::new(RegistryHolderSet::from_nbt_tag(
67            compound.get("types")?,
68        )?))
69    }
70}
71
72impl HashComponent for DamageResistant {
73    fn hash_component(&self, hasher: &mut ComponentHasher) {
74        self.clone().to_nbt_tag().hash_component(hasher);
75    }
76}
77
78/// Items accepted as repair materials for an item stack.
79#[derive(Debug, Clone, PartialEq)]
80pub struct Repairable {
81    items: RegistryHolderSet<Item>,
82}
83
84impl Repairable {
85    #[must_use]
86    pub const fn new(items: RegistryHolderSet<Item>) -> Self {
87        Self { items }
88    }
89
90    #[must_use]
91    pub const fn items(&self) -> &RegistryHolderSet<Item> {
92        &self.items
93    }
94
95    /// Returns whether `repair_item` belongs to this component's holder set.
96    #[must_use]
97    pub fn is_valid_repair_item(&self, repair_item: &ItemStack) -> bool {
98        self.items.contains(repair_item.item())
99    }
100}
101
102impl WriteTo for Repairable {
103    fn write(&self, writer: &mut impl Write) -> Result<()> {
104        self.items.write(writer)
105    }
106}
107
108impl ReadFrom for Repairable {
109    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
110        Ok(Self::new(RegistryHolderSet::read(data)?))
111    }
112}
113
114impl ToNbtTag for Repairable {
115    fn to_nbt_tag(self) -> NbtTag {
116        let mut compound = NbtCompound::new();
117        compound.insert("items", self.items.to_nbt_tag());
118        NbtTag::Compound(compound)
119    }
120}
121
122impl FromNbtTag for Repairable {
123    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
124        let compound = tag.compound()?;
125        Some(Self::new(RegistryHolderSet::from_nbt_tag(
126            compound.get("items")?,
127        )?))
128    }
129}
130
131impl HashComponent for Repairable {
132    fn hash_component(&self, hasher: &mut ComponentHasher) {
133        self.clone().to_nbt_tag().hash_component(hasher);
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use std::io::Cursor;
140
141    use simdnbt::borrow::read_tag;
142    use simdnbt::{FromNbtTag, ToNbtTag as _};
143    use steel_utils::hash::HashComponent as _;
144    use steel_utils::serial::{ReadFrom as _, WriteTo as _};
145
146    use super::{DamageResistant, ProvidesBannerPatterns, Repairable};
147    use crate::REGISTRY;
148    use crate::RegistryHolderSet;
149    use crate::data_components::vanilla_components::{
150        DAMAGE_RESISTANT, PROVIDES_BANNER_PATTERNS, REPAIRABLE,
151    };
152    use crate::init_vanilla_registry;
153    use crate::item_stack::ItemStack;
154    use crate::vanilla_banner_pattern_tags::BannerPatternTag;
155    use crate::vanilla_banner_patterns;
156    use crate::vanilla_damage_type_tags::DamageTypeTag;
157    use crate::vanilla_damage_types;
158    use crate::vanilla_item_tags::ItemTag;
159    use crate::vanilla_items;
160
161    fn parse<T: FromNbtTag>(tag: simdnbt::owned::NbtTag) -> Option<T> {
162        let mut bytes = Vec::new();
163        tag.write(&mut bytes);
164        let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
165        T::from_nbt_tag(borrowed.as_tag())
166    }
167
168    #[test]
169    fn damage_resistant_uses_the_vanilla_record_and_holder_set_codecs() {
170        init_vanilla_registry();
171
172        let component = DamageResistant::new(RegistryHolderSet::Tag(DamageTypeTag::IS_FIRE));
173        assert!(component.is_resistant_to(&vanilla_damage_types::IN_FIRE));
174        assert!(!component.is_resistant_to(&vanilla_damage_types::GENERIC));
175        assert_eq!(
176            parse::<DamageResistant>(component.clone().to_nbt_tag()),
177            Some(component.clone())
178        );
179        assert_eq!(
180            component.compute_hash(),
181            component.clone().to_nbt_tag().compute_hash()
182        );
183
184        let mut bytes = Vec::new();
185        component.write(&mut bytes).expect("component should write");
186        assert_eq!(
187            DamageResistant::read(&mut Cursor::new(bytes.as_slice()))
188                .expect("component should read"),
189            component
190        );
191    }
192
193    #[test]
194    fn repairable_accepts_tagged_and_direct_repair_items() {
195        init_vanilla_registry();
196
197        let wooden = Repairable::new(RegistryHolderSet::Tag(ItemTag::WOODEN_TOOL_MATERIALS));
198        assert!(wooden.is_valid_repair_item(&ItemStack::new(&vanilla_items::OAK_PLANKS)));
199        assert!(!wooden.is_valid_repair_item(&ItemStack::new(&vanilla_items::DIAMOND)));
200
201        let direct = Repairable::new(RegistryHolderSet::Direct(vec![
202            &vanilla_items::PHANTOM_MEMBRANE,
203        ]));
204        assert!(direct.is_valid_repair_item(&ItemStack::new(&vanilla_items::PHANTOM_MEMBRANE)));
205        assert!(!direct.is_valid_repair_item(&ItemStack::new(&vanilla_items::BREEZE_ROD)));
206        assert_eq!(
207            parse::<Repairable>(direct.clone().to_nbt_tag()),
208            Some(direct.clone())
209        );
210
211        let mut bytes = Vec::new();
212        direct.write(&mut bytes).expect("component should write");
213        assert_eq!(
214            Repairable::read(&mut Cursor::new(bytes.as_slice())).expect("component should read"),
215            direct
216        );
217    }
218
219    #[test]
220    fn provides_banner_patterns_uses_fixed_registry_holder_sets() {
221        init_vanilla_registry();
222
223        let component = ProvidesBannerPatterns::Tag(BannerPatternTag::PATTERN_ITEM_FLOWER);
224        assert!(component.contains(&vanilla_banner_patterns::FLOWER));
225        assert_eq!(
226            component.clone().to_nbt_tag(),
227            simdnbt::owned::NbtTag::String("#minecraft:pattern_item/flower".into())
228        );
229        assert_eq!(
230            parse::<ProvidesBannerPatterns>(component.clone().to_nbt_tag()),
231            Some(component.clone())
232        );
233        assert_eq!(
234            component.compute_hash(),
235            component.clone().to_nbt_tag().compute_hash()
236        );
237
238        let mut bytes = Vec::new();
239        component.write(&mut bytes).expect("component should write");
240        assert_eq!(
241            ProvidesBannerPatterns::read(&mut Cursor::new(bytes.as_slice()))
242                .expect("component should read"),
243            component
244        );
245    }
246
247    #[test]
248    fn extracted_item_prototypes_include_all_holder_set_components() {
249        init_vanilla_registry();
250
251        let damage_resistant_count = REGISTRY
252            .items
253            .iter()
254            .filter(|(_, item)| item.components.has(DAMAGE_RESISTANT))
255            .count();
256        let repairable_count = REGISTRY
257            .items
258            .iter()
259            .filter(|(_, item)| item.components.has(REPAIRABLE))
260            .count();
261        assert_eq!(damage_resistant_count, 17);
262        assert_eq!(repairable_count, 75);
263
264        let banner_pattern_count = REGISTRY
265            .items
266            .iter()
267            .filter(|(_, item)| item.components.has(PROVIDES_BANNER_PATTERNS))
268            .count();
269        assert_eq!(banner_pattern_count, 10);
270
271        let flower_pattern = ItemStack::new(&vanilla_items::FLOWER_BANNER_PATTERN);
272        assert!(
273            flower_pattern
274                .get(PROVIDES_BANNER_PATTERNS)
275                .is_some_and(|patterns| patterns.contains(&vanilla_banner_patterns::FLOWER))
276        );
277
278        let netherite = ItemStack::new(&vanilla_items::NETHERITE_INGOT);
279        assert!(!netherite.can_be_hurt_by(&vanilla_damage_types::IN_FIRE));
280        assert!(netherite.can_be_hurt_by(&vanilla_damage_types::GENERIC));
281
282        let nether_star = ItemStack::new(&vanilla_items::NETHER_STAR);
283        assert!(!nether_star.can_be_hurt_by(&vanilla_damage_types::EXPLOSION));
284        assert!(nether_star.can_be_hurt_by(&vanilla_damage_types::IN_FIRE));
285
286        let elytra = ItemStack::new(&vanilla_items::ELYTRA);
287        assert!(elytra.is_valid_repair_item(&ItemStack::new(&vanilla_items::PHANTOM_MEMBRANE)));
288        assert!(!elytra.is_valid_repair_item(&ItemStack::new(&vanilla_items::BREEZE_ROD)));
289
290        let mace = ItemStack::new(&vanilla_items::MACE);
291        assert!(mace.is_valid_repair_item(&ItemStack::new(&vanilla_items::BREEZE_ROD)));
292        assert!(!mace.is_valid_repair_item(&ItemStack::new(&vanilla_items::PHANTOM_MEMBRANE)));
293    }
294}