Skip to main content

steel_registry/data_components/components/
bees.rs

1//! Vanilla `minecraft:bees` item component.
2
3use std::io::{Cursor, Error, Result, Write};
4
5use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
6use simdnbt::{FromNbtTag, ToNbtTag};
7use steel_utils::codec::VarInt;
8use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
9use steel_utils::nbt::NbtNumeric as _;
10use steel_utils::serial::{ReadFrom, WriteTo};
11
12use super::EntityData;
13
14/// One entity stored inside a beehive block item.
15#[derive(Debug, Clone, PartialEq)]
16pub struct BeehiveOccupant {
17    entity_data: EntityData,
18    ticks_in_hive: i32,
19    min_ticks_in_hive: i32,
20}
21
22impl BeehiveOccupant {
23    #[must_use]
24    pub const fn new(entity_data: EntityData, ticks_in_hive: i32, min_ticks_in_hive: i32) -> Self {
25        Self {
26            entity_data,
27            ticks_in_hive,
28            min_ticks_in_hive,
29        }
30    }
31
32    #[must_use]
33    pub const fn entity_data(&self) -> &EntityData {
34        &self.entity_data
35    }
36
37    #[must_use]
38    pub const fn ticks_in_hive(&self) -> i32 {
39        self.ticks_in_hive
40    }
41
42    #[must_use]
43    pub const fn min_ticks_in_hive(&self) -> i32 {
44        self.min_ticks_in_hive
45    }
46
47    fn to_nbt_compound(&self) -> NbtCompound {
48        let mut compound = NbtCompound::new();
49        compound.insert("entity_data", self.entity_data.clone().to_nbt_tag());
50        compound.insert("ticks_in_hive", self.ticks_in_hive);
51        compound.insert("min_ticks_in_hive", self.min_ticks_in_hive);
52        compound
53    }
54
55    fn from_nbt_compound(compound: &NbtCompound) -> Option<Self> {
56        let entity_data = EntityData::from_owned_nbt(compound.get("entity_data")?)?;
57        let ticks_in_hive = compound.get("ticks_in_hive")?.codec_i32()?;
58        let min_ticks_in_hive = compound.get("min_ticks_in_hive")?.codec_i32()?;
59        Some(Self::new(entity_data, ticks_in_hive, min_ticks_in_hive))
60    }
61}
62
63impl WriteTo for BeehiveOccupant {
64    fn write(&self, writer: &mut impl Write) -> Result<()> {
65        self.entity_data.write(writer)?;
66        VarInt(self.ticks_in_hive).write(writer)?;
67        VarInt(self.min_ticks_in_hive).write(writer)
68    }
69}
70
71impl ReadFrom for BeehiveOccupant {
72    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
73        Ok(Self::new(
74            EntityData::read(data)?,
75            VarInt::read(data)?.0,
76            VarInt::read(data)?.0,
77        ))
78    }
79}
80
81impl HashComponent for BeehiveOccupant {
82    fn hash_component(&self, hasher: &mut ComponentHasher) {
83        let mut entries = Vec::with_capacity(3);
84        push_hash_entry(&mut entries, "entity_data", &self.entity_data);
85        push_hash_entry(&mut entries, "ticks_in_hive", &self.ticks_in_hive);
86        push_hash_entry(&mut entries, "min_ticks_in_hive", &self.min_ticks_in_hive);
87        sort_map_entries(&mut entries);
88        hasher.start_map();
89        for entry in &entries {
90            hasher.put_raw_bytes(&entry.key_bytes);
91            hasher.put_raw_bytes(&entry.value_bytes);
92        }
93        hasher.end_map();
94    }
95}
96
97/// Ordered occupants stored inside a beehive block item.
98#[derive(Debug, Default, Clone, PartialEq)]
99pub struct Bees {
100    bees: Vec<BeehiveOccupant>,
101}
102
103impl Bees {
104    #[must_use]
105    pub const fn empty() -> Self {
106        Self { bees: Vec::new() }
107    }
108
109    #[must_use]
110    pub const fn new(bees: Vec<BeehiveOccupant>) -> Self {
111        Self { bees }
112    }
113
114    #[must_use]
115    pub fn bees(&self) -> &[BeehiveOccupant] {
116        &self.bees
117    }
118}
119
120impl WriteTo for Bees {
121    fn write(&self, writer: &mut impl Write) -> Result<()> {
122        write_count(self.bees.len(), writer)?;
123        for bee in &self.bees {
124            bee.write(writer)?;
125        }
126        Ok(())
127    }
128}
129
130impl ReadFrom for Bees {
131    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
132        let count = read_count(data)?;
133        let mut bees = Vec::with_capacity(count.min(65_536));
134        for _ in 0..count {
135            bees.push(BeehiveOccupant::read(data)?);
136        }
137        Ok(Self::new(bees))
138    }
139}
140
141impl ToNbtTag for Bees {
142    fn to_nbt_tag(self) -> NbtTag {
143        if self.bees.is_empty() {
144            NbtTag::List(NbtList::Empty)
145        } else {
146            NbtTag::List(NbtList::Compound(
147                self.bees
148                    .iter()
149                    .map(BeehiveOccupant::to_nbt_compound)
150                    .collect(),
151            ))
152        }
153    }
154}
155
156impl FromNbtTag for Bees {
157    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
158        let values = tag.list()?.to_owned().as_nbt_tags();
159        let bees = values
160            .iter()
161            .map(|tag| BeehiveOccupant::from_nbt_compound(tag.compound()?))
162            .collect::<Option<Vec<_>>>()?;
163        Some(Self::new(bees))
164    }
165}
166
167impl HashComponent for Bees {
168    fn hash_component(&self, hasher: &mut ComponentHasher) {
169        hasher.start_list();
170        for bee in &self.bees {
171            hasher.put_component_hash(bee);
172        }
173        hasher.end_list();
174    }
175}
176
177fn write_count(count: usize, writer: &mut impl Write) -> Result<()> {
178    let count = i32::try_from(count)
179        .map_err(|_| Error::other("Bee occupant list exceeds protocol range"))?;
180    VarInt(count).write(writer)
181}
182
183fn read_count(data: &mut Cursor<&[u8]>) -> Result<usize> {
184    let count = VarInt::read(data)?.0;
185    usize::try_from(count).map_err(|_| Error::other(format!("Negative bee count: {count}")))
186}
187
188fn push_hash_entry<T: HashComponent + ?Sized>(entries: &mut Vec<HashEntry>, key: &str, value: &T) {
189    let mut key_hasher = ComponentHasher::new();
190    key_hasher.put_string(key);
191    let mut value_hasher = ComponentHasher::new();
192    value.hash_component(&mut value_hasher);
193    entries.push(HashEntry::new(key_hasher, value_hasher));
194}
195
196#[cfg(test)]
197mod tests {
198    use std::io::Cursor;
199
200    use simdnbt::owned::{NbtCompound, NbtTag};
201    use simdnbt::{FromNbtTag as _, ToNbtTag as _};
202    use steel_utils::hash::HashComponent as _;
203    use steel_utils::serial::{ReadFrom as _, WriteTo as _};
204
205    use super::{BeehiveOccupant, Bees};
206    use crate::data_components::components::{CustomData, EntityData};
207    use crate::data_components::vanilla_components::BEES;
208    use crate::init_vanilla_registry;
209    use crate::{REGISTRY, RegistryExt};
210
211    fn parse(tag: NbtTag) -> Option<Bees> {
212        let mut bytes = Vec::new();
213        tag.write(&mut bytes);
214        let borrowed = simdnbt::borrow::read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
215        Bees::from_nbt_tag(borrowed.as_tag())
216    }
217
218    #[test]
219    fn bee_occupants_round_trip_both_codecs_and_hash_as_a_list() {
220        init_vanilla_registry();
221        let bee = REGISTRY
222            .entity_types
223            .by_key(&steel_utils::Identifier::vanilla_static("bee"))
224            .expect("bee should be registered");
225        let mut payload = NbtCompound::new();
226        payload.insert("HasNectar", true);
227        let value = Bees::new(vec![BeehiveOccupant::new(
228            EntityData::new(
229                bee,
230                CustomData::try_from_compound(payload).expect("valid bee data"),
231            ),
232            17,
233            600,
234        )]);
235        let nbt = value.clone().to_nbt_tag();
236        assert_eq!(parse(nbt.clone()), Some(value.clone()));
237        assert_eq!(value.compute_hash(), nbt.compute_hash());
238
239        let mut network = Vec::new();
240        value.write(&mut network).expect("bees should encode");
241        assert_eq!(
242            Bees::read(&mut Cursor::new(network.as_slice())).expect("bees should decode"),
243            value
244        );
245    }
246
247    #[test]
248    fn extracted_beehives_start_with_no_occupants() {
249        init_vanilla_registry();
250        for key in ["bee_nest", "beehive"] {
251            let item = REGISTRY
252                .items
253                .by_key(&steel_utils::Identifier::vanilla(key.to_owned()))
254                .unwrap_or_else(|| panic!("{key} should be registered"));
255            assert_eq!(item.components.get(BEES), Some(Bees::empty()));
256        }
257    }
258}