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        (&self).to_nbt_tag()
153    }
154}
155
156impl ToNbtTag for &SuspiciousStewEffects {
157    fn to_nbt_tag(self) -> NbtTag {
158        if self.effects.is_empty() {
159            NbtTag::List(NbtList::Empty)
160        } else {
161            NbtTag::List(NbtList::Compound(
162                self.effects
163                    .iter()
164                    .map(SuspiciousStewEffect::to_nbt_compound)
165                    .collect(),
166            ))
167        }
168    }
169}
170
171impl FromNbtTag for SuspiciousStewEffects {
172    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
173        let effects = tag
174            .list()?
175            .to_owned()
176            .as_nbt_tags()
177            .iter()
178            .map(|tag| SuspiciousStewEffect::from_nbt_compound(tag.compound()?))
179            .collect::<Option<Vec<_>>>()?;
180        Some(Self::new(effects))
181    }
182}
183
184impl HashComponent for SuspiciousStewEffects {
185    fn hash_component(&self, hasher: &mut ComponentHasher) {
186        hasher.start_list();
187        for effect in &self.effects {
188            hasher.put_component_hash(effect);
189        }
190        hasher.end_list();
191    }
192}
193
194fn write_count(count: usize, writer: &mut impl Write) -> Result<()> {
195    let count = i32::try_from(count).map_err(|_| Error::other("Effect list is too large"))?;
196    VarInt(count).write(writer)
197}
198
199fn read_count(data: &mut Cursor<&[u8]>) -> Result<usize> {
200    let count = VarInt::read(data)?.0;
201    usize::try_from(count).map_err(|_| Error::other(format!("Negative effect count: {count}")))
202}
203
204fn push_hash_entry<T: HashComponent + ?Sized>(entries: &mut Vec<HashEntry>, key: &str, value: &T) {
205    let mut key_hasher = ComponentHasher::new();
206    key_hasher.put_string(key);
207    let mut value_hasher = ComponentHasher::new();
208    value.hash_component(&mut value_hasher);
209    entries.push(HashEntry::new(key_hasher, value_hasher));
210}
211
212fn hash_entries(hasher: &mut ComponentHasher, entries: &mut [HashEntry]) {
213    sort_map_entries(entries);
214    hasher.start_map();
215    for entry in entries {
216        hasher.put_raw_bytes(&entry.key_bytes);
217        hasher.put_raw_bytes(&entry.value_bytes);
218    }
219    hasher.end_map();
220}
221
222#[cfg(test)]
223mod tests {
224    use std::io::Cursor;
225
226    use simdnbt::{FromNbtTag as _, ToNbtTag as _};
227    use steel_utils::serial::{ReadFrom as _, WriteTo as _};
228
229    use super::{SuspiciousStewEffect, SuspiciousStewEffects};
230    use crate::data_components::vanilla_components::SUSPICIOUS_STEW_EFFECTS;
231    use crate::init_vanilla_registry;
232    use crate::{REGISTRY, RegistryExt};
233
234    #[test]
235    fn stew_effects_round_trip_and_malformed_duration_uses_lenient_default() {
236        init_vanilla_registry();
237        let night_vision = REGISTRY
238            .mob_effects
239            .by_key(&steel_utils::Identifier::vanilla_static("night_vision"))
240            .expect("night vision should be registered");
241        let value = SuspiciousStewEffects::new(vec![SuspiciousStewEffect::new(
242            night_vision,
243            SuspiciousStewEffect::DEFAULT_DURATION,
244        )]);
245        let nbt = value.clone().to_nbt_tag();
246        let mut bytes = Vec::new();
247        nbt.write(&mut bytes);
248        let borrowed = simdnbt::borrow::read_tag(&mut Cursor::new(bytes.as_slice()))
249            .expect("stew NBT should parse");
250        assert_eq!(
251            SuspiciousStewEffects::from_nbt_tag(borrowed.as_tag()),
252            Some(value.clone())
253        );
254        let mut network = Vec::new();
255        value.write(&mut network).expect("effects should encode");
256        assert_eq!(
257            SuspiciousStewEffects::read(&mut Cursor::new(network.as_slice()))
258                .expect("effects should decode"),
259            value
260        );
261    }
262
263    #[test]
264    fn extracted_suspicious_stew_has_an_empty_effect_list() {
265        init_vanilla_registry();
266        let stew = REGISTRY
267            .items
268            .by_key(&steel_utils::Identifier::vanilla_static("suspicious_stew"))
269            .expect("suspicious stew should be registered");
270        assert_eq!(
271            stew.components.get(SUSPICIOUS_STEW_EFFECTS),
272            Some(SuspiciousStewEffects::empty())
273        );
274    }
275}