steel_registry/data_components/components/
suspicious_stew.rs1use 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#[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#[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}