1use std::fmt::{self, Display, Formatter};
2use std::io::{Cursor, Error, Result as IoResult, Write};
3
4use rustc_hash::FxHashMap;
5use simdnbt::owned::{NbtCompound, NbtTag};
6use simdnbt::{FromNbtTag, ToNbtTag};
7use steel_utils::Identifier;
8use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
9use steel_utils::nbt::NbtNumeric as _;
10use steel_utils::serial::{ReadFrom, WriteTo};
11use text_components::TextComponent;
12
13use crate::sound_event::SoundEventHolder;
14use crate::{REGISTRY, RegistryExt, RegistryHolderEntry, RegistryTags};
15
16#[derive(Debug, Clone)]
22pub struct InstrumentValue {
23 sound_event: SoundEventHolder,
24 use_duration: f32,
25 range: f32,
26 description: TextComponent,
27}
28
29impl InstrumentValue {
30 pub fn new(
31 sound_event: SoundEventHolder,
32 use_duration: f32,
33 range: f32,
34 description: TextComponent,
35 ) -> Result<Self, InvalidInstrumentValue> {
36 if !is_positive_float(use_duration) {
37 return Err(InvalidInstrumentValue::UseDuration(use_duration));
38 }
39 if !is_positive_float(range) {
40 return Err(InvalidInstrumentValue::Range(range));
41 }
42 Ok(Self {
43 sound_event,
44 use_duration,
45 range,
46 description,
47 })
48 }
49
50 pub(crate) const fn from_validated_parts(
51 sound_event: SoundEventHolder,
52 use_duration: f32,
53 range: f32,
54 description: TextComponent,
55 ) -> Self {
56 assert!(is_positive_float(use_duration));
57 assert!(is_positive_float(range));
58 Self {
59 sound_event,
60 use_duration,
61 range,
62 description,
63 }
64 }
65
66 #[must_use]
67 pub const fn sound_event(&self) -> &SoundEventHolder {
68 &self.sound_event
69 }
70
71 #[must_use]
72 pub const fn use_duration(&self) -> f32 {
73 self.use_duration
74 }
75
76 #[must_use]
77 pub const fn range(&self) -> f32 {
78 self.range
79 }
80
81 #[must_use]
82 pub const fn description(&self) -> &TextComponent {
83 &self.description
84 }
85
86 fn to_nbt_tag_ref(&self) -> NbtTag {
87 let mut compound = NbtCompound::new();
88 compound.insert("sound_event", self.sound_event.clone().to_nbt_tag());
89 compound.insert("use_duration", self.use_duration);
90 compound.insert("range", self.range);
91 compound.insert("description", self.description.to_codec_nbt());
92 NbtTag::Compound(compound)
93 }
94}
95
96const fn is_positive_float(value: f32) -> bool {
97 value > 0.0 && value <= f32::MAX
98}
99
100impl PartialEq for InstrumentValue {
101 fn eq(&self, other: &Self) -> bool {
102 self.sound_event == other.sound_event
103 && java_float_equals(self.use_duration, other.use_duration)
104 && java_float_equals(self.range, other.range)
105 && self.description == other.description
106 }
107}
108
109const fn java_float_equals(left: f32, right: f32) -> bool {
110 (left.is_nan() && right.is_nan()) || left.to_bits() == right.to_bits()
111}
112
113impl WriteTo for InstrumentValue {
114 fn write(&self, writer: &mut impl Write) -> IoResult<()> {
115 self.sound_event.write(writer)?;
116 self.use_duration.write(writer)?;
117 self.range.write(writer)?;
118 WriteTo::write(&self.description.to_codec_nbt(), writer)
119 }
120}
121
122impl ReadFrom for InstrumentValue {
123 fn read(data: &mut Cursor<&[u8]>) -> IoResult<Self> {
124 Self::new(
125 SoundEventHolder::read(data)?,
126 f32::read(data)?,
127 f32::read(data)?,
128 TextComponent::read(data)?,
129 )
130 .map_err(Error::other)
131 }
132}
133
134impl ToNbtTag for InstrumentValue {
135 fn to_nbt_tag(self) -> NbtTag {
136 self.to_nbt_tag_ref()
137 }
138}
139
140impl FromNbtTag for InstrumentValue {
141 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
142 let compound = tag.compound()?;
143 let description = TextComponent::from_nbt(&compound.get("description")?.to_owned())?;
144 Self::new(
145 SoundEventHolder::from_nbt_tag(compound.get("sound_event")?)?,
146 compound.get("use_duration")?.codec_f32()?,
147 compound.get("range")?.codec_f32()?,
148 description,
149 )
150 .ok()
151 }
152}
153
154impl HashComponent for InstrumentValue {
155 fn hash_component(&self, hasher: &mut ComponentHasher) {
156 let mut entries = Vec::new();
157 push_hash_entry(&mut entries, "sound_event", &self.sound_event);
158 push_hash_entry(&mut entries, "use_duration", &self.use_duration);
159 push_hash_entry(&mut entries, "range", &self.range);
160 push_hash_entry(&mut entries, "description", &self.description);
161 sort_map_entries(&mut entries);
162 hasher.start_map();
163 for entry in &entries {
164 hasher.put_raw_bytes(&entry.key_bytes);
165 hasher.put_raw_bytes(&entry.value_bytes);
166 }
167 hasher.end_map();
168 }
169}
170
171fn push_hash_entry<T: HashComponent + ?Sized>(entries: &mut Vec<HashEntry>, key: &str, value: &T) {
172 let mut key_hasher = ComponentHasher::new();
173 key.hash_component(&mut key_hasher);
174 let mut value_hasher = ComponentHasher::new();
175 value.hash_component(&mut value_hasher);
176 entries.push(HashEntry::new(key_hasher, value_hasher));
177}
178
179#[derive(Debug, Clone, Copy, PartialEq)]
181pub enum InvalidInstrumentValue {
182 UseDuration(f32),
183 Range(f32),
184}
185
186impl Display for InvalidInstrumentValue {
187 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
188 match self {
189 Self::UseDuration(value) => write!(formatter, "use duration must be positive: {value}"),
190 Self::Range(value) => write!(formatter, "range must be positive: {value}"),
191 }
192 }
193}
194
195impl std::error::Error for InvalidInstrumentValue {}
196
197#[derive(Debug)]
199pub struct Instrument {
200 pub key: Identifier,
201 value: InstrumentValue,
202}
203
204impl Instrument {
205 #[must_use]
206 pub const fn new(key: Identifier, value: InstrumentValue) -> Self {
207 Self { key, value }
208 }
209
210 #[must_use]
211 pub const fn value(&self) -> &InstrumentValue {
212 &self.value
213 }
214}
215
216impl ToNbtTag for &Instrument {
217 fn to_nbt_tag(self) -> NbtTag {
218 self.value.to_nbt_tag_ref()
219 }
220}
221
222pub type InstrumentRef = &'static Instrument;
223
224pub struct InstrumentRegistry {
225 instruments_by_id: Vec<InstrumentRef>,
226 instruments_by_key: FxHashMap<Identifier, usize>,
227 tags: RegistryTags,
228 allows_registering: bool,
229}
230
231impl InstrumentRegistry {
232 #[must_use]
233 pub fn new() -> Self {
234 Self {
235 instruments_by_id: Vec::new(),
236 instruments_by_key: FxHashMap::default(),
237 tags: RegistryTags::default(),
238 allows_registering: true,
239 }
240 }
241}
242
243crate::impl_standard_methods!(
244 InstrumentRegistry,
245 InstrumentRef,
246 instruments_by_id,
247 instruments_by_key,
248 allows_registering
249);
250
251crate::impl_registry!(
252 InstrumentRegistry,
253 Instrument,
254 instruments_by_id,
255 instruments_by_key,
256 instruments
257);
258
259crate::impl_tagged_registry!(InstrumentRegistry, instruments_by_key, "instrument");
260
261impl RegistryHolderEntry for Instrument {
262 type Value = InstrumentValue;
263
264 const REGISTRY_NAME: &'static str = "instrument";
265
266 fn holder_value(&self) -> &Self::Value {
267 &self.value
268 }
269
270 fn holder_by_id(id: usize) -> Option<&'static Self> {
271 REGISTRY.instruments.by_id(id)
272 }
273
274 fn holder_by_key(key: &Identifier) -> Option<&'static Self> {
275 REGISTRY.instruments.by_key(key)
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use simdnbt::ToNbtTag;
282 use simdnbt::owned::NbtTag;
283
284 use crate::{init_vanilla_registry, vanilla_instruments};
285
286 #[test]
287 fn nbt_uses_sound_event_registry_key() {
288 init_vanilla_registry();
289
290 let NbtTag::Compound(compound) = (&vanilla_instruments::PONDER_GOAT_HORN).to_nbt_tag()
291 else {
292 panic!("instrument did not serialize to a compound tag");
293 };
294
295 let Some(sound_event) = compound.string("sound_event") else {
296 panic!("instrument NBT is missing sound_event string");
297 };
298
299 assert_eq!(
300 sound_event.to_str().as_ref(),
301 "minecraft:item.goat_horn.sound.0"
302 );
303 }
304}