Skip to main content

steel_registry/data_components/components/
suspicious_stew.rs

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