Skip to main content

steel_protocol/packets/game/
c_sound.rs

1use glam::{DVec3, IVec3};
2use steel_macros::{ClientPacket, WriteTo};
3use steel_registry::packets::play::C_SOUND;
4use steel_registry::sound_event::SoundEventRef;
5
6/// Sound source categories (matches vanilla `SoundSource` enum order).
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[repr(u8)]
9pub enum SoundSource {
10    Master = 0,
11    Music = 1,
12    Records = 2,
13    Weather = 3,
14    Blocks = 4,
15    Hostile = 5,
16    Neutral = 6,
17    Players = 7,
18    Ambient = 8,
19    Voice = 9,
20    Ui = 10,
21}
22
23impl SoundSource {
24    /// Returns the `VarInt` value for the enum.
25    #[must_use]
26    pub const fn as_varint(self) -> i32 {
27        self as i32
28    }
29}
30
31/// Sent to play a sound effect at a specific position.
32///
33/// The position is encoded at 8x precision (divide by 8 to get actual block coordinates).
34/// This allows sub-block positioning for more accurate sound placement.
35#[derive(WriteTo, ClientPacket, Clone, Debug)]
36#[packet_id(Play = C_SOUND)]
37pub struct CSound {
38    /// The holder-encoded sound event ID (`VarInt`).
39    ///
40    /// Vanilla reserves `0` for direct sound events, so registered sound events
41    /// are encoded as `registry_id + 1`.
42    #[write(as = VarInt)]
43    pub sound_id: i32,
44    /// The sound source category (`VarInt`).
45    #[write(as = VarInt)]
46    pub source: i32,
47    /// X position multiplied by 8 (fixed-point).
48    pub pos: IVec3,
49    /// Volume (1.0 = normal).
50    pub volume: f32,
51    /// Pitch (1.0 = normal).
52    pub pitch: f32,
53    /// Random seed for sound variations.
54    pub seed: i64,
55}
56
57impl CSound {
58    /// Creates a new sound packet.
59    ///
60    /// # Arguments
61    /// * `sound` - Sound event to play
62    /// * `source` - Sound source category
63    /// * `x`, `y`, `z` - Position in block coordinates (will be scaled by 8)
64    /// * `volume` - Volume multiplier (1.0 = normal)
65    /// * `pitch` - Pitch multiplier (1.0 = normal)
66    /// * `seed` - Random seed for sound variations
67    #[must_use]
68    pub fn new(
69        sound: SoundEventRef,
70        source: SoundSource,
71        pos: DVec3,
72        volume: f32,
73        pitch: f32,
74        seed: i64,
75    ) -> Self {
76        Self {
77            sound_id: sound.packet_holder_id(),
78            source: source.as_varint(),
79            pos: IVec3::new(
80                (pos.x * 8.0) as i32,
81                (pos.y * 8.0) as i32,
82                (pos.z * 8.0) as i32,
83            ),
84            volume,
85            pitch,
86            seed,
87        }
88    }
89
90    /// Creates a block sound packet at the center of a block position.
91    ///
92    /// # Arguments
93    /// * `sound` - Sound event to play
94    /// * `pos` - Block position (will be centered at +0.5)
95    /// * `volume` - Volume multiplier
96    /// * `pitch` - Pitch multiplier
97    /// * `seed` - Random seed
98    #[must_use]
99    pub fn block_sound(
100        sound: SoundEventRef,
101        pos: steel_utils::BlockPos,
102        volume: f32,
103        pitch: f32,
104        seed: i64,
105    ) -> Self {
106        Self::new(
107            sound,
108            SoundSource::Blocks,
109            pos.0.as_dvec3().map(|v| v + 0.5),
110            volume,
111            pitch,
112            seed,
113        )
114    }
115}
116
117#[cfg(test)]
118mod tests {
119
120    use steel_registry::init_vanilla_registry;
121    use steel_registry::{RegistryEntry, sound_events};
122    use steel_utils::BlockPos;
123
124    use super::CSound;
125
126    #[test]
127    fn registered_sound_packet_uses_holder_id() {
128        init_vanilla_registry();
129
130        let packet = CSound::block_sound(
131            &sound_events::BLOCK_WOODEN_BUTTON_CLICK_ON,
132            BlockPos::ZERO,
133            1.0,
134            1.0,
135            0,
136        );
137
138        let expected_holder_id = sound_events::BLOCK_WOODEN_BUTTON_CLICK_ON.id() as i32 + 1;
139        assert_eq!(
140            sound_events::BLOCK_WOODEN_BUTTON_CLICK_ON.packet_holder_id(),
141            expected_holder_id
142        );
143        assert_eq!(packet.sound_id, expected_holder_id);
144    }
145}