Skip to main content

mcproto_types/
sound_event.rs

1//! Minecraft protocol sound event values.
2
3use crate::{
4    TypeCodec,
5    basic::{Float, Identifier},
6    contextual::PrefixedOptional,
7};
8use mcproto_codec::error::{CodecError, CodecKind};
9
10/// Describes a sound that can be played.
11///
12/// A sound event contains its resource [`Identifier`], followed by a boolean
13/// indicating whether a fixed range is present, followed by the optional
14/// [`Float`] range itself. In memory, the boolean is represented by whether
15/// [`fixed_range`](Self::fixed_range) is [`Some`]:
16///
17/// ```text
18/// Identifier + Boolean(has fixed range) + Optional Float
19/// ```
20///
21/// When no fixed range is supplied, playback volume varies with distance
22/// according to the sound's normal behavior.
23///
24/// # Examples
25///
26/// ```
27/// use mcproto_types::{Float, Identifier, SoundEvent, TypeCodec};
28///
29/// let sound = SoundEvent::fixed(
30///     Identifier::new("minecraft:block.note_block.harp")?,
31///     Float(16.0),
32/// );
33/// let mut encoded = Vec::new();
34/// sound.encode(&mut encoded)?;
35///
36/// let mut input = encoded.as_slice();
37/// assert_eq!(SoundEvent::decode(&mut input)?, sound);
38/// assert!(input.is_empty());
39/// # Ok::<(), Box<dyn std::error::Error>>(())
40/// ```
41#[derive(Debug, Clone, PartialEq)]
42pub struct SoundEvent {
43    /// The sound's resource identifier.
44    pub sound_name: Identifier,
45    /// Maximum playback range, or `None` for distance-dependent volume.
46    pub fixed_range: Option<Float>,
47}
48
49impl SoundEvent {
50    /// Creates a sound event with an optional fixed range.
51    #[must_use]
52    pub const fn new(sound_name: Identifier, fixed_range: Option<Float>) -> Self {
53        Self {
54            sound_name,
55            fixed_range,
56        }
57    }
58
59    /// Creates a sound event with distance-dependent volume.
60    #[must_use]
61    pub const fn variable(sound_name: Identifier) -> Self {
62        Self::new(sound_name, None)
63    }
64
65    /// Creates a sound event with a fixed maximum range.
66    #[must_use]
67    pub const fn fixed(sound_name: Identifier, fixed_range: Float) -> Self {
68        Self::new(sound_name, Some(fixed_range))
69    }
70
71    /// Returns whether this sound has a fixed maximum range.
72    #[must_use]
73    pub const fn has_fixed_range(&self) -> bool {
74        self.fixed_range.is_some()
75    }
76}
77
78impl TypeCodec for SoundEvent {
79    fn encode(&self, writer: &mut impl std::io::Write) -> Result<(), CodecError> {
80        self.sound_name
81            .encode(writer)
82            .map_err(|error| error.with_context(CodecKind::SoundEvent))?;
83        PrefixedOptional::from(self.fixed_range)
84            .encode(writer)
85            .map_err(|error| error.with_context(CodecKind::SoundEvent))
86    }
87
88    fn decode(reader: &mut impl std::io::Read) -> Result<Self, CodecError> {
89        let sound_name = Identifier::decode(reader)
90            .map_err(|error| error.with_context(CodecKind::SoundEvent))?;
91        let fixed_range = PrefixedOptional::<Float>::decode(reader)
92            .map_err(|error| error.with_context(CodecKind::SoundEvent))?
93            .into_option();
94        Ok(Self::new(sound_name, fixed_range))
95    }
96}