Skip to main content

steel_registry/data_components/components/
instrument.rs

1//! Vanilla `minecraft:instrument` 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::instrument::{Instrument, InstrumentValue};
11
12/// Instrument selected for an instrument item such as a goat horn.
13#[derive(Debug, Clone, PartialEq)]
14pub struct InstrumentComponent {
15    instrument: RegistryHolder<Instrument>,
16}
17
18impl InstrumentComponent {
19    #[must_use]
20    pub const fn new(instrument: RegistryHolder<Instrument>) -> Self {
21        Self { instrument }
22    }
23
24    #[must_use]
25    pub const fn instrument(&self) -> &RegistryHolder<Instrument> {
26        &self.instrument
27    }
28
29    #[must_use]
30    pub fn value(&self) -> &InstrumentValue {
31        self.instrument.value()
32    }
33}
34
35impl WriteTo for InstrumentComponent {
36    fn write(&self, writer: &mut impl Write) -> Result<()> {
37        self.instrument.write(writer)
38    }
39}
40
41impl ReadFrom for InstrumentComponent {
42    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
43        RegistryHolder::read(data).map(Self::new)
44    }
45}
46
47impl ToNbtTag for InstrumentComponent {
48    fn to_nbt_tag(self) -> simdnbt::owned::NbtTag {
49        self.instrument.to_nbt_tag()
50    }
51}
52
53impl FromNbtTag for InstrumentComponent {
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 InstrumentComponent {
60    fn hash_component(&self, hasher: &mut ComponentHasher) {
61        self.instrument.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::codec::VarInt;
73    use steel_utils::hash::HashComponent as _;
74    use steel_utils::serial::{ReadFrom as _, WriteTo as _};
75    use text_components::TextComponent;
76
77    use super::InstrumentComponent;
78    use crate::RegistryHolder;
79    use crate::data_components::vanilla_components::INSTRUMENT;
80    use crate::init_vanilla_registry;
81    use crate::instrument::InstrumentValue;
82    use crate::item_stack::ItemStack;
83    use crate::sound_event::SoundEventHolder;
84    use crate::{sound_events, vanilla_instruments, vanilla_items};
85
86    fn parse_component(tag: simdnbt::owned::NbtTag) -> Option<InstrumentComponent> {
87        let mut bytes = Vec::new();
88        tag.write(&mut bytes);
89        let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
90        InstrumentComponent::from_nbt_tag(borrowed.as_tag())
91    }
92
93    #[test]
94    fn registry_reference_round_trips_both_codecs() {
95        init_vanilla_registry();
96        let component = InstrumentComponent::new(RegistryHolder::reference(
97            &vanilla_instruments::PONDER_GOAT_HORN,
98        ));
99
100        let mut network = Vec::new();
101        component
102            .write(&mut network)
103            .expect("registry instrument should encode");
104        assert_eq!(
105            InstrumentComponent::read(&mut Cursor::new(network.as_slice()))
106                .expect("registry instrument should decode"),
107            component
108        );
109
110        let nbt = component.clone().to_nbt_tag();
111        assert_eq!(
112            nbt,
113            simdnbt::owned::NbtTag::String("minecraft:ponder_goat_horn".into())
114        );
115        assert_eq!(parse_component(nbt), Some(component));
116    }
117
118    #[test]
119    fn valid_inline_instrument_round_trips_and_hashes_its_record() {
120        init_vanilla_registry();
121        let value = InstrumentValue::new(
122            SoundEventHolder::Direct {
123                sound_id: Identifier::vanilla_static("custom_horn"),
124                fixed_range: Some(32.0),
125            },
126            3.5,
127            48.0,
128            TextComponent::plain("Custom horn"),
129        )
130        .expect("inline instrument should be valid");
131        let component = InstrumentComponent::new(RegistryHolder::direct(value));
132
133        let mut network = Vec::new();
134        component
135            .write(&mut network)
136            .expect("inline instrument should encode");
137        assert_eq!(
138            InstrumentComponent::read(&mut Cursor::new(network.as_slice()))
139                .expect("inline instrument should decode"),
140            component
141        );
142
143        let nbt = component.clone().to_nbt_tag();
144        assert_eq!(parse_component(nbt.clone()), Some(component.clone()));
145        assert_eq!(component.compute_hash(), nbt.compute_hash());
146    }
147
148    #[test]
149    fn network_rejects_inline_values_that_cannot_persist() {
150        init_vanilla_registry();
151        let mut network = Vec::new();
152        VarInt(0)
153            .write(&mut network)
154            .expect("direct holder discriminator should encode");
155        SoundEventHolder::registry(&sound_events::ITEM_GOAT_HORN_SOUND_0)
156            .write(&mut network)
157            .expect("sound event should encode");
158        (-1.0_f32)
159            .write(&mut network)
160            .expect("duration should encode");
161        16.0_f32.write(&mut network).expect("range should encode");
162        TextComponent::plain("Invalid horn")
163            .write(&mut network)
164            .expect("description should encode");
165
166        assert!(InstrumentComponent::read(&mut Cursor::new(network.as_slice())).is_err());
167    }
168
169    #[test]
170    fn extracted_goat_horn_uses_ponder_instrument() {
171        init_vanilla_registry();
172        let goat_horn = ItemStack::new(&vanilla_items::GOAT_HORN);
173
174        assert_eq!(
175            goat_horn
176                .get(INSTRUMENT)
177                .and_then(|component| component.instrument().as_reference()),
178            Some(&vanilla_instruments::PONDER_GOAT_HORN)
179        );
180    }
181}