1use crate::error::TryFromSpellError;
2use crate::{Attribute, Attributes};
3use std::fmt;
4use std::str::FromStr;
5use strum_macros::{Display, EnumString, EnumIter, EnumCount};
6use num_enum::{TryFromPrimitive, IntoPrimitive};
7use serde_repr::{Serialize_repr, Deserialize_repr};
8use serde::{Serialize, Deserialize, Serializer};
9use serde::de::{self, Visitor, Deserializer};
10
11#[derive(
14 Debug,
15 Hash,
16 Eq,
17 PartialEq,
18 Ord,
19 PartialOrd,
20 Display,
21 EnumString,
22 EnumIter,
23 EnumCount,
24 Clone,
25 Copy,
26)]
27pub enum Spell {
28 #[strum(serialize = "Team Spirit Footprints")]
29 TeamSpiritFootprints,
30 #[strum(serialize = "Gangreen Footprints")]
31 GangreenFootprints,
32 #[strum(serialize = "Corpse Gray Footprints")]
33 CorpseGrayFootprints,
34 #[strum(serialize = "Violent Violet Footprints")]
35 ViolentVioletFootprints,
36 #[strum(serialize = "Rotten Orange Footprints")]
37 RottenOrangeFootprints,
38 #[strum(serialize = "Bruised Purple Footprints")]
39 BruisedPurpleFootprints,
40 #[strum(serialize = "Headless Horseshoes")]
41 HeadlessHorseshoes,
42 #[strum(serialize = "Die Job")]
43 DieJob,
44 #[strum(serialize = "Chromatic Corruption")]
45 ChromaticCorruption,
46 #[strum(serialize = "Putrescent Pigmentation")]
47 PutrescentPigmentation,
48 #[strum(serialize = "Spectral Spectrum")]
49 SpectralSpectrum,
50 #[strum(serialize = "Sinister Staining")]
51 SinisterStaining,
52 #[strum(serialize = "Voices From Below", serialize = "Voices from Below")]
54 VoicesFromBelow,
55 #[strum(serialize = "Pumpkin Bombs")]
56 PumpkinBombs,
57 #[strum(serialize = "Halloween Fire")]
58 HalloweenFire,
59 #[strum(serialize = "Exorcism")]
60 Exorcism,
61}
62
63impl Spell {
64 pub const DEFINDEX_PAINT: u32 = 1004;
66 pub const DEFINDEX_FOOTPRINTS: u32 = 1005;
68 pub const DEFINDEX_VOICES_FROM_BELOW: u32 = 1006;
70 pub const DEFINDEX_PUMPKIN_BOMBS: u32 = 1007;
72 pub const DEFINDEX_HALLOWEEN_FIRE: u32 = 1008;
74 pub const DEFINDEX_EXORCISM: u32 = 1009;
76
77 pub fn attribute_defindex(&self) -> u32 {
79 match self {
80 Self::DieJob |
81 Self::ChromaticCorruption |
82 Self::PutrescentPigmentation |
83 Self::SpectralSpectrum |
84 Self::SinisterStaining => Self::DEFINDEX_PAINT,
85 Self::TeamSpiritFootprints |
86 Self::GangreenFootprints |
87 Self::CorpseGrayFootprints |
88 Self::ViolentVioletFootprints |
89 Self::RottenOrangeFootprints |
90 Self::BruisedPurpleFootprints |
91 Self::HeadlessHorseshoes => Self::DEFINDEX_FOOTPRINTS,
92 Self::VoicesFromBelow => Self::DEFINDEX_VOICES_FROM_BELOW,
93 Self::PumpkinBombs => Self::DEFINDEX_PUMPKIN_BOMBS,
94 Self::HalloweenFire => Self::DEFINDEX_HALLOWEEN_FIRE,
95 Self::Exorcism => Self::DEFINDEX_EXORCISM,
96 }
97 }
98
99 pub fn attribute_value(&self) -> Option<u32> {
113 match self {
114 Self::DieJob => Some(0),
115 Self::ChromaticCorruption => Some(1),
116 Self::PutrescentPigmentation => Some(2),
117 Self::SpectralSpectrum => Some(3),
118 Self::SinisterStaining => Some(4),
119 Self::TeamSpiritFootprints => Some(1),
120 Self::GangreenFootprints => Some(8421376),
121 Self::CorpseGrayFootprints => Some(3100495),
122 Self::ViolentVioletFootprints => Some(5322826),
123 Self::RottenOrangeFootprints => Some(13595446),
124 Self::BruisedPurpleFootprints => Some(8208497),
125 Self::HeadlessHorseshoes => Some(2),
126 _ => None,
127 }
128 }
129}
130
131impl Attributes for Spell {
138 const DEFINDEX: &[u32] = &[
139 1004,
140 1005,
141 1006,
142 1007,
143 1008,
144 1009,
145 ];
146 const NAME: &[&str] = &[
147 "SPELL: set item tint RGB",
148 "SPELL: set Halloween footstep type",
149 "SPELL: Halloween voice modulation",
150 "SPELL: Halloween pumpkin explosions",
151 "SPELL: Halloween green flames",
152 "SPELL: Halloween death ghosts",
153 ];
154 const ATTRIBUTE_CLASS: &[&str] = &[
155 "set_item_tint_rgb_override",
156 "halloween_footstep_type",
157 "halloween_voice_modulation",
158 "halloween_pumpkin_explosions",
159 "halloween_green_flames",
160 "halloween_death_ghosts",
161 ];
162 const DESCRIPTION_STRING: &[Option<&str>] = &[
163 Some("%s1"),
164 Some("%s1"),
165 Some("Voices from Below"),
166 Some("Pumpkin Bombs"),
167 Some("Halloween Fire"),
168 Some("Exorcism"),
169 ];
170 const DESCRIPTION_FORMAT: &[Option<&str>] = &[
171 Some("value_is_from_lookup_table"),
172 Some("value_is_from_lookup_table"),
173 Some("value_is_additive"),
174 Some("value_is_additive"),
175 Some("value_is_additive"),
176 Some("value_is_additive"),
177 ];
178 const EFFECT_TYPE: &[&str] = &[
179 "positive",
180 "positive",
181 "positive",
182 "positive",
183 "positive",
184 "positive",
185 ];
186 const HIDDEN: &[bool] = &[
187 false,
188 false,
189 false,
190 false,
191 false,
192 false,
193 ];
194 const STORED_AS_INTEGER: &[bool] = &[
195 false,
196 false,
197 false,
198 false,
199 false,
200 false,
201 ];
202}
203
204struct SpellVisitor;
205
206impl<'de> Visitor<'de> for SpellVisitor {
207 type Value = Spell;
208
209 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
210 formatter.write_str("a string")
211 }
212
213 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
214 where
215 E: de::Error,
216 {
217 Spell::from_str(value).map_err(serde::de::Error::custom)
218 }
219}
220
221impl<'de> Deserialize<'de> for Spell {
222 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
223 where
224 D: Deserializer<'de>,
225 {
226 deserializer.deserialize_any(SpellVisitor)
227 }
228}
229
230impl Serialize for Spell {
231 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
232 where
233 S: Serializer,
234 {
235 serializer.serialize_str(&self.to_string())
236 }
237}
238
239impl TryInto<u32> for Spell {
240 type Error = ();
241
242 fn try_into(self) -> Result<u32, Self::Error> {
243 self.attribute_value().ok_or(())
244 }
245}
246
247#[derive(
249 Serialize_repr,
250 Deserialize_repr,
251 Debug,
252 Hash,
253 Eq,
254 PartialEq,
255 Ord,
256 PartialOrd,
257 Display,
258 EnumString,
259 EnumIter,
260 EnumCount,
261 TryFromPrimitive,
262 IntoPrimitive,
263 Clone,
264 Copy,
265)]
266#[repr(u32)]
267pub enum PaintSpell {
268 #[strum(serialize = "Die Job")]
269 DieJob = 0,
270 #[strum(serialize = "Chromatic Corruption")]
271 ChromaticCorruption = 1,
272 #[strum(serialize = "Putrescent Pigmentation")]
273 PutrescentPigmentation = 2,
274 #[strum(serialize = "Spectral Spectrum")]
275 SpectralSpectrum = 3,
276 #[strum(serialize = "Sinister Staining")]
277 SinisterStaining = 4,
278}
279
280impl Attribute for PaintSpell {
282 const DEFINDEX: u32 = 1004;
283 const NAME: &str = "SPELL: set item tint RGB";
284 const ATTRIBUTE_CLASS: &str = "set_item_tint_rgb_override";
285 const DESCRIPTION_STRING: Option<&str> = Some("%s1");
286 const DESCRIPTION_FORMAT: Option<&str> = Some("value_is_from_lookup_table");
287 const EFFECT_TYPE: &str = "positive";
288 const HIDDEN: bool = false;
289 const STORED_AS_INTEGER: bool = false;
290}
291
292impl From<PaintSpell> for Spell {
293 fn from(val: PaintSpell) -> Self {
294 match val {
295 PaintSpell::DieJob => Spell::DieJob,
296 PaintSpell::ChromaticCorruption => Spell::ChromaticCorruption,
297 PaintSpell::PutrescentPigmentation => Spell::PutrescentPigmentation,
298 PaintSpell::SpectralSpectrum => Spell::SpectralSpectrum,
299 PaintSpell::SinisterStaining => Spell::SinisterStaining,
300 }
301 }
302}
303
304impl TryFrom<Spell> for PaintSpell {
305 type Error = TryFromSpellError;
306
307 fn try_from(value: Spell) -> Result<Self, Self::Error> {
308 match value {
309 Spell::DieJob => Ok(PaintSpell::DieJob),
310 Spell::ChromaticCorruption => Ok(PaintSpell::ChromaticCorruption),
311 Spell::PutrescentPigmentation => Ok(PaintSpell::PutrescentPigmentation),
312 Spell::SpectralSpectrum => Ok(PaintSpell::SpectralSpectrum),
313 Spell::SinisterStaining => Ok(PaintSpell::SinisterStaining),
314 _ => Err(TryFromSpellError {
315 defindex: Self::DEFINDEX,
316 value
317 }),
318 }
319 }
320}
321
322#[derive(
324 Serialize_repr,
325 Deserialize_repr,
326 Debug,
327 Hash,
328 Eq,
329 PartialEq,
330 Ord,
331 PartialOrd,
332 Display,
333 EnumString,
334 EnumIter,
335 EnumCount,
336 TryFromPrimitive,
337 IntoPrimitive,
338 Clone,
339 Copy,
340)]
341#[repr(u32)]
342pub enum FootprintsSpell {
343 #[strum(serialize = "Team Spirit Footprints")]
344 TeamSpiritFootprints = 1,
345 #[strum(serialize = "Headless Horseshoes")]
346 HeadlessHorseshoes = 2,
347 #[strum(serialize = "Rotten Orange Footprints")]
348 RottenOrangeFootprints = 13595446,
349 #[strum(serialize = "Corpse Gray Footprints")]
350 CorpseGrayFootprints = 3100495,
351 #[strum(serialize = "Violent Violet Footprints")]
352 ViolentVioletFootprints = 5322826,
353 #[strum(serialize = "Bruised Purple Footprints")]
354 BruisedPurpleFootprints = 8208497,
355 #[strum(serialize = "Gangreen Footprints")]
356 GangreenFootprints = 8421376,
357}
358
359impl Attribute for FootprintsSpell {
360 const DEFINDEX: u32 = 1005;
361 const NAME: &str = "SPELL: set Halloween footstep type";
362 const ATTRIBUTE_CLASS: &str = "halloween_footstep_type";
363 const DESCRIPTION_STRING: Option<&str> = Some("%s1");
364 const DESCRIPTION_FORMAT: Option<&str> = Some("value_is_from_lookup_table");
365 const EFFECT_TYPE: &str = "positive";
366 const HIDDEN: bool = false;
367 const STORED_AS_INTEGER: bool = false;
368}
369
370impl From<FootprintsSpell> for Spell {
371 fn from(val: FootprintsSpell) -> Self {
372 match val {
373 FootprintsSpell::TeamSpiritFootprints => Spell::TeamSpiritFootprints,
374 FootprintsSpell::GangreenFootprints => Spell::GangreenFootprints,
375 FootprintsSpell::CorpseGrayFootprints => Spell::CorpseGrayFootprints,
376 FootprintsSpell::ViolentVioletFootprints => Spell::ViolentVioletFootprints,
377 FootprintsSpell::RottenOrangeFootprints => Spell::RottenOrangeFootprints,
378 FootprintsSpell::BruisedPurpleFootprints => Spell::BruisedPurpleFootprints,
379 FootprintsSpell::HeadlessHorseshoes => Spell::HeadlessHorseshoes,
380 }
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387 use std::str::FromStr;
388
389 #[test]
390 fn from_str() {
391 assert_eq!(Spell::from_str("Headless Horseshoes").unwrap(), Spell::HeadlessHorseshoes);
392 }
393
394 #[test]
395 fn serialize_spell() {
396 #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
397 struct SpellAttribute {
398 spell: Spell,
399 }
400
401 let attribute = SpellAttribute {
402 spell: Spell::HeadlessHorseshoes,
403 };
404 let json = serde_json::to_string(&attribute).unwrap();
405
406 assert_eq!(json, "{\"spell\":\"Headless Horseshoes\"}");
407 assert_eq!(serde_json::from_str::<SpellAttribute>(&json).unwrap(), attribute);
408 }
409
410 #[test]
411 fn to_string() {
412 assert_eq!(Spell::HeadlessHorseshoes.to_string(), "Headless Horseshoes");
413 }
414
415 #[test]
416 fn from_repr() {
417 assert_eq!(FootprintsSpell::try_from(2).unwrap(), FootprintsSpell::HeadlessHorseshoes);
418 }
419
420 #[test]
421 fn voices_from_below_from_str() {
422 assert_eq!(Spell::VoicesFromBelow.to_string(), "Voices from Below");
423 assert_eq!(Spell::from_str("Voices from Below").unwrap(), Spell::VoicesFromBelow);
424 assert_eq!(Spell::from_str("Voices From Below").unwrap(), Spell::VoicesFromBelow);
425 }
426}