Skip to main content

steel_registry/data_components/components/
jukebox_playable.rs

1//! Vanilla `minecraft:jukebox_playable` item component.
2
3use std::io::{Cursor, Error, Result, Write};
4use std::str::FromStr;
5
6use simdnbt::owned::NbtTag;
7use steel_utils::Identifier;
8use steel_utils::hash::{ComponentHasher, HashComponent};
9use steel_utils::serial::{ReadFrom, WriteTo};
10
11use crate::jukebox_song::{JukeboxSong, JukeboxSongRef, JukeboxSongValue};
12use crate::{REGISTRY, RegistryExt, RegistryHolder};
13
14/// A jukebox song attached to an item stack.
15///
16#[derive(Debug, Clone, PartialEq)]
17pub struct JukeboxPlayable {
18    song: RegistryHolder<JukeboxSong>,
19}
20
21impl JukeboxPlayable {
22    #[must_use]
23    pub const fn new(song: JukeboxSongRef) -> Self {
24        Self {
25            song: RegistryHolder::reference(song),
26        }
27    }
28
29    #[must_use]
30    pub const fn direct(song: JukeboxSongValue) -> Self {
31        Self {
32            song: RegistryHolder::direct(song),
33        }
34    }
35
36    #[must_use]
37    pub const fn song(&self) -> &RegistryHolder<JukeboxSong> {
38        &self.song
39    }
40
41    /// Decodes `JukeboxSong.CODEC`, which is a registry-fixed holder codec.
42    #[must_use]
43    pub fn from_persistent_nbt(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
44        let key = Identifier::from_str(&tag.string()?.to_str()).ok()?;
45        REGISTRY.jukebox_songs.by_key(&key).map(Self::new)
46    }
47
48    /// Encodes `JukeboxSong.CODEC`, which is a registry-fixed holder codec.
49    pub fn to_persistent_nbt(&self) -> Result<NbtTag> {
50        let Some(song) = self.song.as_reference() else {
51            return Err(Error::other("Direct jukebox song holder is not persistent"));
52        };
53        Ok(NbtTag::String(song.key.to_string().into()))
54    }
55}
56
57impl HashComponent for JukeboxPlayable {
58    fn hash_component(&self, hasher: &mut ComponentHasher) {
59        self.song.hash_component(hasher);
60    }
61}
62
63impl ReadFrom for JukeboxPlayable {
64    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
65        RegistryHolder::read(data).map(|song| Self { song })
66    }
67}
68
69impl WriteTo for JukeboxPlayable {
70    fn write(&self, writer: &mut impl Write) -> Result<()> {
71        self.song.write(writer)
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use std::io::Cursor;
78
79    use simdnbt::owned::NbtTag;
80    use steel_utils::serial::{ReadFrom, WriteTo};
81
82    use super::JukeboxPlayable;
83    use crate::init_vanilla_registry;
84    use crate::vanilla_jukebox_songs;
85
86    #[test]
87    fn registry_reference_round_trips_both_codecs() {
88        init_vanilla_registry();
89        let component = JukeboxPlayable::new(&vanilla_jukebox_songs::CAT);
90
91        let mut network = Vec::new();
92        component
93            .write(&mut network)
94            .expect("registry jukebox holder should encode");
95        assert_eq!(
96            JukeboxPlayable::read(&mut Cursor::new(network.as_slice()))
97                .expect("registry jukebox holder should decode"),
98            component
99        );
100
101        let nbt = component
102            .to_persistent_nbt()
103            .expect("reference is persistent");
104        assert_eq!(nbt, NbtTag::String("minecraft:cat".into()));
105    }
106
107    #[test]
108    fn direct_holder_round_trips_stream_and_is_not_persistent() {
109        use crate::jukebox_song::JukeboxSongValue;
110        use crate::sound_event::SoundEventHolder;
111        use steel_utils::Identifier;
112        use text_components::TextComponent;
113
114        init_vanilla_registry();
115        let component = JukeboxPlayable::direct(JukeboxSongValue {
116            sound_event: SoundEventHolder::Direct {
117                sound_id: Identifier::vanilla_static("custom_song"),
118                fixed_range: Some(12.0),
119            },
120            description: TextComponent::plain("Custom song"),
121            length_in_seconds: -1.0,
122            comparator_output: 99,
123        });
124        let mut network = Vec::new();
125        component
126            .write(&mut network)
127            .expect("direct holder should encode");
128        assert_eq!(
129            JukeboxPlayable::read(&mut Cursor::new(network.as_slice()))
130                .expect("direct holder should decode"),
131            component
132        );
133        assert!(component.to_persistent_nbt().is_err());
134    }
135}