steel_registry/data_components/components/
instrument.rs1use 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#[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}