1use crate::error::TryFromSpellError;
2use crate::{
3 Attribute,
4 Attributes,
5 AttributeDef,
6 AttributeValue,
7 DescriptionFormat,
8 EffectType,
9 ItemAttribute,
10 TryFromIntAttributeValue,
11};
12use crate::econ_attributes::{
13 HalloweenDeathGhosts,
14 HalloweenGreenFlames,
15 HalloweenPumpkinExplosions,
16 HalloweenVoiceModulation,
17};
18use std::fmt;
19use std::str::FromStr;
20use num_enum::{IntoPrimitive, TryFromPrimitive};
21use serde::de::{self, Deserializer, Visitor};
22use serde::{Deserialize, Serialize, Serializer};
23use serde_repr::{Deserialize_repr, Serialize_repr};
24use strum::{Display, EnumCount, EnumIter, EnumString};
25
26#[derive(
31 Debug,
32 Hash,
33 Eq,
34 PartialEq,
35 Ord,
36 PartialOrd,
37 Display,
38 EnumString,
39 EnumIter,
40 EnumCount,
41 Clone,
42 Copy,
43)]
44#[strum(serialize_all = "title_case")]
45#[allow(missing_docs)]
46pub enum Spell {
47 TeamSpiritFootprints,
48 HeadlessHorseshoes,
49 CorpseGrayFootprints,
50 ViolentVioletFootprints,
51 BruisedPurpleFootprints,
52 GangreenFootprints,
53 RottenOrangeFootprints,
54 DieJob,
55 ChromaticCorruption,
56 PutrescentPigmentation,
57 SpectralSpectrum,
58 SinisterStaining,
59 #[strum(serialize = "Voices From Below", serialize = "Voices from Below")]
61 VoicesFromBelow,
62 PumpkinBombs,
63 HalloweenFire,
64 Exorcism,
65}
66
67impl Spell {
68 pub const DEFINDEX_PAINT: u32 = 1004;
70 pub const DEFINDEX_FOOTPRINTS: u32 = 1005;
72 pub const DEFINDEX_VOICES_FROM_BELOW: u32 = 1006;
74 pub const DEFINDEX_PUMPKIN_BOMBS: u32 = 1007;
76 pub const DEFINDEX_HALLOWEEN_FIRE: u32 = 1008;
78 pub const DEFINDEX_EXORCISM: u32 = 1009;
80
81 pub fn attribute_defindex(&self) -> u32 {
83 match self {
84 Self::DieJob |
85 Self::ChromaticCorruption |
86 Self::PutrescentPigmentation |
87 Self::SpectralSpectrum |
88 Self::SinisterStaining => Self::DEFINDEX_PAINT,
89 Self::TeamSpiritFootprints |
90 Self::GangreenFootprints |
91 Self::CorpseGrayFootprints |
92 Self::ViolentVioletFootprints |
93 Self::RottenOrangeFootprints |
94 Self::BruisedPurpleFootprints |
95 Self::HeadlessHorseshoes => Self::DEFINDEX_FOOTPRINTS,
96 Self::VoicesFromBelow => Self::DEFINDEX_VOICES_FROM_BELOW,
97 Self::PumpkinBombs => Self::DEFINDEX_PUMPKIN_BOMBS,
98 Self::HalloweenFire => Self::DEFINDEX_HALLOWEEN_FIRE,
99 Self::Exorcism => Self::DEFINDEX_EXORCISM,
100 }
101 }
102
103 pub fn attribute_id(&self) -> Option<u32> {
106 match self {
107 Self::DieJob => Some(0),
108 Self::ChromaticCorruption => Some(1),
109 Self::PutrescentPigmentation => Some(2),
110 Self::SpectralSpectrum => Some(3),
111 Self::SinisterStaining => Some(4),
112 Self::TeamSpiritFootprints => Some(1),
113 Self::HeadlessHorseshoes => Some(2),
114 Self::CorpseGrayFootprints => Some(3100495),
115 Self::ViolentVioletFootprints => Some(5322826),
116 Self::BruisedPurpleFootprints => Some(8208497),
117 Self::GangreenFootprints => Some(8421376),
118 Self::RottenOrangeFootprints => Some(13595446),
119 _ => None,
121 }
122 }
123
124 pub fn is_paint_spell(&self) -> bool {
126 matches!(
127 self,
128 Self::DieJob |
129 Self::ChromaticCorruption |
130 Self::PutrescentPigmentation |
131 Self::SpectralSpectrum |
132 Self::SinisterStaining,
133 )
134 }
135
136 pub fn is_footprints_spell(&self) -> bool {
138 matches!(
139 self,
140 Self::TeamSpiritFootprints |
141 Self::HeadlessHorseshoes |
142 Self::CorpseGrayFootprints |
143 Self::ViolentVioletFootprints |
144 Self::BruisedPurpleFootprints |
145 Self::GangreenFootprints |
146 Self::RottenOrangeFootprints,
147 )
148 }
149}
150
151impl Attributes for Spell {
152 const DEFINDEX: &'static [u32] = &[
153 1004,
154 1005,
155 1006,
156 1007,
157 1008,
158 1009,
159 ];
160 const USES_FLOAT_VALUE: bool = true;
161 const ATTRIBUTES: &'static [AttributeDef] = &[
165 AttributeDef {
166 defindex: 1004,
167 name: "SPELL: set item tint RGB",
168 attribute_class: Some("set_item_tint_rgb_override"),
169 description_string: Some("%s1"),
170 description_format: Some(DescriptionFormat::ValueIsFromLookupTable),
171 effect_type: EffectType::Positive,
172 hidden: false,
173 stored_as_integer: false,
174 },
175 AttributeDef {
176 defindex: 1005,
177 name: "SPELL: set Halloween footstep type",
178 attribute_class: Some("halloween_footstep_type"),
179 description_string: Some("%s1"),
180 description_format: Some(DescriptionFormat::ValueIsFromLookupTable),
181 effect_type: EffectType::Positive,
182 hidden: false,
183 stored_as_integer: false,
184 },
185 AttributeDef {
186 defindex: 1006,
187 name: "SPELL: Halloween voice modulation",
188 attribute_class: Some("halloween_voice_modulation"),
189 description_string: Some("Voices from Below"),
190 description_format: Some(DescriptionFormat::ValueIsAdditive),
191 effect_type: EffectType::Positive,
192 hidden: false,
193 stored_as_integer: false,
194 },
195 AttributeDef {
196 defindex: 1007,
197 name: "SPELL: Halloween pumpkin explosions",
198 attribute_class: Some("halloween_pumpkin_explosions"),
199 description_string: Some("Pumpkin Bombs"),
200 description_format: Some(DescriptionFormat::ValueIsAdditive),
201 effect_type: EffectType::Positive,
202 hidden: false,
203 stored_as_integer: false,
204 },
205 AttributeDef {
206 defindex: 1008,
207 name: "SPELL: Halloween green flames",
208 attribute_class: Some("halloween_green_flames"),
209 description_string: Some("Halloween Fire"),
210 description_format: Some(DescriptionFormat::ValueIsAdditive),
211 effect_type: EffectType::Positive,
212 hidden: false,
213 stored_as_integer: false,
214 },
215 AttributeDef {
216 defindex: 1009,
217 name: "SPELL: Halloween death ghosts",
218 attribute_class: Some("halloween_death_ghosts"),
219 description_string: Some("Exorcism"),
220 description_format: Some(DescriptionFormat::ValueIsAdditive),
221 effect_type: EffectType::Positive,
222 hidden: false,
223 stored_as_integer: false,
224 },
225 ];
226
227 fn attribute_float_value(&self) -> Option<f32> {
242 Some(match self {
243 Self::DieJob => 0.0,
244 Self::ChromaticCorruption => 1.0,
245 Self::PutrescentPigmentation => 2.0,
246 Self::SpectralSpectrum => 3.0,
247 Self::SinisterStaining => 4.0,
248 Self::TeamSpiritFootprints => 1.0,
249 Self::HeadlessHorseshoes => 2.0,
250 Self::CorpseGrayFootprints => 3100495.0,
251 Self::ViolentVioletFootprints => 5322826.0,
252 Self::BruisedPurpleFootprints => 8208497.0,
253 Self::GangreenFootprints => 8421376.0,
254 Self::RottenOrangeFootprints => 13595446.0,
255 _ => 1.0,
257 })
258 }
259}
260
261impl From<PaintSpell> for Spell {
262 fn from(val: PaintSpell) -> Self {
263 match val {
264 PaintSpell::DieJob => Spell::DieJob,
265 PaintSpell::ChromaticCorruption => Spell::ChromaticCorruption,
266 PaintSpell::PutrescentPigmentation => Spell::PutrescentPigmentation,
267 PaintSpell::SpectralSpectrum => Spell::SpectralSpectrum,
268 PaintSpell::SinisterStaining => Spell::SinisterStaining,
269 }
270 }
271}
272
273impl From<&PaintSpell> for Spell {
274 fn from(val: &PaintSpell) -> Self {
275 Self::from(*val)
276 }
277}
278
279impl From<FootprintsSpell> for Spell {
280 fn from(val: FootprintsSpell) -> Self {
281 match val {
282 FootprintsSpell::TeamSpiritFootprints => Self::TeamSpiritFootprints,
283 FootprintsSpell::HeadlessHorseshoes => Self::HeadlessHorseshoes,
284 FootprintsSpell::CorpseGrayFootprints => Self::CorpseGrayFootprints,
285 FootprintsSpell::ViolentVioletFootprints => Self::ViolentVioletFootprints,
286 FootprintsSpell::BruisedPurpleFootprints => Self::BruisedPurpleFootprints,
287 FootprintsSpell::GangreenFootprints => Self::GangreenFootprints,
288 FootprintsSpell::RottenOrangeFootprints => Self::RottenOrangeFootprints,
289 }
290 }
291}
292
293impl From<&FootprintsSpell> for Spell {
294 fn from(val: &FootprintsSpell) -> Self {
295 Self::from(*val)
296 }
297}
298
299impl From<HalloweenVoiceModulation> for Spell {
300 fn from(_: HalloweenVoiceModulation) -> Self {
301 Spell::VoicesFromBelow
302 }
303}
304
305impl From<&HalloweenVoiceModulation> for Spell {
306 fn from(_: &HalloweenVoiceModulation) -> Self {
307 Spell::VoicesFromBelow
308 }
309}
310
311impl From<HalloweenPumpkinExplosions> for Spell {
312 fn from(_: HalloweenPumpkinExplosions) -> Self {
313 Spell::PumpkinBombs
314 }
315}
316
317impl From<&HalloweenPumpkinExplosions> for Spell {
318 fn from(_: &HalloweenPumpkinExplosions) -> Self {
319 Spell::PumpkinBombs
320 }
321}
322
323impl From<HalloweenGreenFlames> for Spell {
324 fn from(_: HalloweenGreenFlames) -> Self {
325 Spell::HalloweenFire
326 }
327}
328
329impl From<&HalloweenGreenFlames> for Spell {
330 fn from(_: &HalloweenGreenFlames) -> Self {
331 Spell::HalloweenFire
332 }
333}
334
335impl From<HalloweenDeathGhosts> for Spell {
336 fn from(_: HalloweenDeathGhosts) -> Self {
337 Spell::Exorcism
338 }
339}
340
341impl From<&HalloweenDeathGhosts> for Spell {
342 fn from(_: &HalloweenDeathGhosts) -> Self {
343 Spell::Exorcism
344 }
345}
346
347impl From<Spell> for ItemAttribute {
348 fn from(val: Spell) -> Self {
349 ItemAttribute {
350 defindex: val.attribute_defindex(),
351 value: val.attribute_value(),
352 float_value: val.attribute_float_value(),
353 }
354 }
355}
356
357struct SpellVisitor;
358
359impl<'de> Visitor<'de> for SpellVisitor {
360 type Value = Spell;
361
362 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
363 formatter.write_str("a string")
364 }
365
366 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
367 where
368 E: de::Error,
369 {
370 Spell::from_str(value).map_err(serde::de::Error::custom)
371 }
372}
373
374impl<'de> Deserialize<'de> for Spell {
375 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
376 where
377 D: Deserializer<'de>,
378 {
379 deserializer.deserialize_any(SpellVisitor)
380 }
381}
382
383impl Serialize for Spell {
384 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
385 where
386 S: Serializer,
387 {
388 serializer.serialize_str(&self.to_string())
389 }
390}
391
392#[derive(
394 Debug,
395 Clone,
396 Copy,
397 Eq,
398 PartialEq,
399 Ord,
400 PartialOrd,
401 Hash,
402 Display,
403 Serialize_repr,
404 Deserialize_repr,
405 EnumString,
406 EnumIter,
407 EnumCount,
408 TryFromPrimitive,
409 IntoPrimitive,
410)]
411#[repr(u32)]
412#[strum(serialize_all = "title_case")]
413#[allow(missing_docs)]
414pub enum PaintSpell {
415 DieJob = 0,
416 ChromaticCorruption = 1,
417 PutrescentPigmentation = 2,
418 SpectralSpectrum = 3,
419 SinisterStaining = 4,
420}
421
422impl Attribute for PaintSpell {
423 const DEFINDEX: u32 = 1004;
424 const USES_FLOAT_VALUE: bool = true;
425 const ATTRIBUTE: AttributeDef = AttributeDef {
427 defindex: 1004,
428 name: "SPELL: set item tint RGB",
429 attribute_class: Some("set_item_tint_rgb_override"),
430 description_string: Some("%s1"),
431 description_format: Some(DescriptionFormat::ValueIsFromLookupTable),
432 effect_type: EffectType::Positive,
433 hidden: false,
434 stored_as_integer: false,
435 };
436
437 fn attribute_float_value(&self) -> Option<f32> {
438 Some((*self as u32) as f32)
439 }
440}
441
442impl TryFromIntAttributeValue for PaintSpell {
443 fn try_from_attribute_value(_v: AttributeValue) -> Option<Self> {
444 None
445 }
446}
447
448impl TryFrom<Spell> for PaintSpell {
449 type Error = TryFromSpellError;
450
451 fn try_from(value: Spell) -> Result<Self, Self::Error> {
452 match value {
453 Spell::DieJob => Ok(Self ::DieJob),
454 Spell::ChromaticCorruption => Ok(Self::ChromaticCorruption),
455 Spell::PutrescentPigmentation => Ok(Self::PutrescentPigmentation),
456 Spell::SpectralSpectrum => Ok(Self::SpectralSpectrum),
457 Spell::SinisterStaining => Ok(Self::SinisterStaining),
458 _ => Err(TryFromSpellError {
459 defindex: Self::DEFINDEX,
460 value
461 }),
462 }
463 }
464}
465
466impl TryFrom<&Spell> for PaintSpell {
467 type Error = TryFromSpellError;
468
469 fn try_from(value: &Spell) -> Result<Self, Self::Error> {
470 Self::try_from(*value)
471 }
472}
473
474impl From<PaintSpell> for ItemAttribute {
475 fn from(val: PaintSpell) -> Self {
476 ItemAttribute {
477 defindex: PaintSpell::DEFINDEX,
478 value: val.attribute_value(),
479 float_value: val.attribute_float_value(),
480 }
481 }
482}
483
484#[derive(
486 Debug,
487 Clone,
488 Copy,
489 Eq,
490 PartialEq,
491 Ord,
492 PartialOrd,
493 Hash,
494 Display,
495 Serialize_repr,
496 Deserialize_repr,
497 EnumString,
498 EnumIter,
499 EnumCount,
500 TryFromPrimitive,
501 IntoPrimitive,
502)]
503#[repr(u32)]
504#[strum(serialize_all = "title_case")]
505#[allow(missing_docs)]
506pub enum FootprintsSpell {
507 TeamSpiritFootprints = 1,
508 HeadlessHorseshoes = 2,
509 CorpseGrayFootprints = 3100495,
510 ViolentVioletFootprints = 5322826,
511 BruisedPurpleFootprints = 8208497,
512 GangreenFootprints = 8421376,
513 RottenOrangeFootprints = 13595446,
514}
515
516impl Attribute for FootprintsSpell {
517 const DEFINDEX: u32 = 1005;
518 const USES_FLOAT_VALUE: bool = true;
519 const ATTRIBUTE: AttributeDef = AttributeDef {
521 defindex: 1005,
522 name: "SPELL: set Halloween footstep type",
523 attribute_class: Some("halloween_footstep_type"),
524 description_string: Some("%s1"),
525 description_format: Some(DescriptionFormat::ValueIsFromLookupTable),
526 effect_type: EffectType::Positive,
527 hidden: false,
528 stored_as_integer: false,
529 };
530
531 fn attribute_float_value(&self) -> Option<f32> {
532 Some((*self as u32) as f32)
533 }
534}
535
536impl TryFromIntAttributeValue for FootprintsSpell {}
537
538impl TryFrom<Spell> for FootprintsSpell {
539 type Error = TryFromSpellError;
540
541 fn try_from(value: Spell) -> Result<Self, Self::Error> {
542 match value {
543 Spell::TeamSpiritFootprints => Ok(Self::TeamSpiritFootprints),
544 Spell::HeadlessHorseshoes => Ok(Self::HeadlessHorseshoes),
545 Spell::CorpseGrayFootprints => Ok(Self::CorpseGrayFootprints),
546 Spell::ViolentVioletFootprints => Ok(Self::ViolentVioletFootprints),
547 Spell::BruisedPurpleFootprints => Ok(Self::BruisedPurpleFootprints),
548 Spell::GangreenFootprints => Ok(Self::GangreenFootprints),
549 Spell::RottenOrangeFootprints => Ok(Self::RottenOrangeFootprints),
550 _ => Err(TryFromSpellError {
551 defindex: Self::DEFINDEX,
552 value
553 }),
554 }
555 }
556}
557
558impl TryFrom<&Spell> for FootprintsSpell {
559 type Error = TryFromSpellError;
560
561 fn try_from(value: &Spell) -> Result<Self, Self::Error> {
562 Self::try_from(*value)
563 }
564}
565
566impl From<FootprintsSpell> for ItemAttribute {
567 fn from(val: FootprintsSpell) -> Self {
568 ItemAttribute {
569 defindex: PaintSpell::DEFINDEX,
570 value: val.attribute_value(),
571 float_value: val.attribute_float_value(),
572 }
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use std::str::FromStr;
580
581 #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
582 struct SpellAttribute {
583 spell: Spell,
584 }
585
586 #[test]
587 fn from_str() {
588 assert_eq!(Spell::from_str("Headless Horseshoes").unwrap(), Spell::HeadlessHorseshoes);
589 }
590
591 #[test]
592 fn serialize_spell() {
593 let attribute = SpellAttribute {
594 spell: Spell::HeadlessHorseshoes,
595 };
596 let json = serde_json::to_string(&attribute).unwrap();
597
598 assert_eq!(json, "{\"spell\":\"Headless Horseshoes\"}");
599 assert_eq!(serde_json::from_str::<SpellAttribute>(&json).unwrap(), attribute);
600 assert_eq!(serde_json::to_string(&Spell::HeadlessHorseshoes).unwrap(), "\"Headless Horseshoes\"");
601 }
602
603 #[test]
604 fn deserializes_spell() {
605 let json = "{\"spell\":\"Headless Horseshoes\"}";
606 let attribute: SpellAttribute = serde_json::from_str(json).unwrap();
607
608 assert_eq!(attribute.spell, Spell::HeadlessHorseshoes);
609 assert_eq!(serde_json::from_str::<Spell>("\"Headless Horseshoes\"").unwrap(), Spell::HeadlessHorseshoes);
610 }
611
612 #[test]
613 fn to_string() {
614 assert_eq!(Spell::HeadlessHorseshoes.to_string(), "Headless Horseshoes");
615 }
616
617 #[test]
618 fn from_repr() {
619 assert_eq!(FootprintsSpell::try_from(2).unwrap(), FootprintsSpell::HeadlessHorseshoes);
620 }
621
622 #[test]
623 fn voices_from_below_from_str() {
624 assert_eq!(Spell::VoicesFromBelow.to_string(), "Voices from Below");
625 assert_eq!(Spell::from_str("Voices from Below").unwrap(), Spell::VoicesFromBelow);
626 assert_eq!(Spell::from_str("Voices From Below").unwrap(), Spell::VoicesFromBelow);
627 }
628
629 #[test]
630 fn attribute_slices_are_equal_length() {
631 assert_eq!(Spell::DEFINDEX.len(), Spell::ATTRIBUTES.len());
632 }
633}