Skip to main content

wowlab_types/types/data/
spell_label.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
4#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
5#[repr(transparent)]
6// #t(rust_newtype_pub_field) transparent DBC value with established tuple construction
7pub struct SpellLabel(pub i32);
8
9impl SpellLabel {
10    pub const CLASS_SPELLS: Self = Self(16);
11    pub const MAGE: Self = Self(17);
12    pub const PRIEST: Self = Self(18);
13    pub const WARLOCK: Self = Self(19);
14    pub const ROGUE: Self = Self(20);
15    pub const DRUID: Self = Self(21);
16    pub const MONK: Self = Self(22);
17    pub const HUNTER: Self = Self(23);
18    pub const SHAMAN: Self = Self(24);
19    pub const WARRIOR: Self = Self(25);
20    pub const PALADIN: Self = Self(26);
21    pub const DEATH_KNIGHT: Self = Self(27);
22    pub const MAGE_SECONDARY: Self = Self(28);
23    pub const DEMON_HUNTER: Self = Self(66);
24    pub const EVOKER: Self = Self(1216);
25
26    /// Red / Devastation.
27    pub const EVOKER_RED: Self = Self(1464);
28    /// Blue / Preservation.
29    pub const EVOKER_BLUE: Self = Self(1465);
30    /// Green / Augmentation.
31    pub const EVOKER_GREEN: Self = Self(1466);
32    /// Bronze / utility.
33    pub const EVOKER_BRONZE: Self = Self(1467);
34    /// Black / melee.
35    pub const EVOKER_BLACK: Self = Self(1468);
36
37    pub const TALENTS: Self = Self(52);
38    pub const TELEPORT: Self = Self(64);
39    pub const RANGED_DPS: Self = Self(71);
40    pub const MAJOR_OFFENSIVE_CD: Self = Self(115);
41    pub const DISPEL_UTILITY: Self = Self(118);
42    pub const DIE_ON_FAILURE: Self = Self(237);
43    /// Internal "Do Not Touch" spells.
44    pub const INTERNAL_DNT: Self = Self(292);
45    pub const SPEC_CORE: Self = Self(295);
46    pub const TALENT_ROW_A: Self = Self(374);
47    pub const TALENT_ROW_B: Self = Self(375);
48    pub const MAJOR_COOLDOWNS: Self = Self(690);
49    pub const HEALING_SPELLS: Self = Self(741);
50    pub const ROGUE_LETHAL_POISONS: Self = Self(869);
51    pub const ROGUE_NON_LETHAL_POISONS: Self = Self(870);
52    pub const PVP: Self = Self(999);
53    pub const NPC_ABILITIES: Self = Self(2832);
54
55    pub const LEGENDARY_ITEMS: Self = Self(187);
56    pub const ARTIFACT_TRAITS: Self = Self(277);
57    pub const ARTIFACT_PASSIVES: Self = Self(293);
58    pub const LEGENDARY_EFFECTS: Self = Self(294);
59
60    pub const AZERITE_ESSENCES: Self = Self(640);
61
62    pub const SHADOWLANDS_GENERAL: Self = Self(627);
63    pub const SHADOWLANDS_COMBAT: Self = Self(630);
64    pub const CONDUITS: Self = Self(931);
65    pub const COVENANT: Self = Self(976);
66
67    pub const DRAGON_RACING: Self = Self(1447);
68    pub const DRAGONFLIGHT_CRAFTED_GEAR: Self = Self(1565);
69    pub const PRIMORDIAL_STONE: Self = Self(2513);
70    pub const SINGING_CITRINE: Self = Self(3922);
71
72    pub const BRANN_DELVES: Self = Self(617);
73    pub const HERO_TALENTS: Self = Self(3022);
74    pub const ITEM_EFFECTS: Self = Self(3959);
75    pub const FURNITURE_CRAFTS: Self = Self(5702);
76
77    pub const BLACKSMITHING: Self = Self(565);
78    pub const LEATHERWORKING: Self = Self(590);
79    pub const TAILORING: Self = Self(592);
80    pub const SKINNING: Self = Self(600);
81    pub const ALCHEMY: Self = Self(602);
82    pub const JEWELCRAFTING: Self = Self(609);
83    pub const ALL_PROFESSIONS: Self = Self(969);
84    pub const COOKING: Self = Self(971);
85    pub const MODIFIED_CRAFTING_REAGENTS: Self = Self(972);
86    pub const ENCHANTING: Self = Self(974);
87    pub const INSCRIPTION: Self = Self(975);
88    pub const ALL_CRAFTED: Self = Self(1396);
89    pub const ENGINEERING: Self = Self(1530);
90    pub const MINING: Self = Self(1762);
91    pub const HERBALISM: Self = Self(1930);
92
93    pub const PROF_CLASSIC: Self = Self(2409);
94    pub const PROF_TBC: Self = Self(2410);
95    pub const PROF_WOTLK: Self = Self(2411);
96    pub const PROF_CATA: Self = Self(2412);
97    pub const PROF_MOP: Self = Self(2413);
98    pub const PROF_WOD: Self = Self(2414);
99    pub const PROF_LEGION: Self = Self(2415);
100    pub const PROF_BFA: Self = Self(2416);
101    pub const PROF_DF: Self = Self(3232);
102
103    pub const KNOWN: &'static [(Self, &'static str)] = &[
104        (Self::CLASS_SPELLS, "ClassSpells"),
105        (Self::MAGE, "Mage"),
106        (Self::PRIEST, "Priest"),
107        (Self::WARLOCK, "Warlock"),
108        (Self::ROGUE, "Rogue"),
109        (Self::DRUID, "Druid"),
110        (Self::MONK, "Monk"),
111        (Self::HUNTER, "Hunter"),
112        (Self::SHAMAN, "Shaman"),
113        (Self::WARRIOR, "Warrior"),
114        (Self::PALADIN, "Paladin"),
115        (Self::DEATH_KNIGHT, "DeathKnight"),
116        (Self::MAGE_SECONDARY, "MageSecondary"),
117        (Self::DEMON_HUNTER, "DemonHunter"),
118        (Self::EVOKER, "Evoker"),
119        (Self::EVOKER_RED, "EvokerRed"),
120        (Self::EVOKER_BLUE, "EvokerBlue"),
121        (Self::EVOKER_GREEN, "EvokerGreen"),
122        (Self::EVOKER_BRONZE, "EvokerBronze"),
123        (Self::EVOKER_BLACK, "EvokerBlack"),
124        (Self::TALENTS, "Talents"),
125        (Self::TELEPORT, "Teleport"),
126        (Self::RANGED_DPS, "RangedDps"),
127        (Self::MAJOR_OFFENSIVE_CD, "MajorOffensiveCd"),
128        (Self::DISPEL_UTILITY, "DispelUtility"),
129        (Self::DIE_ON_FAILURE, "DieOnFailure"),
130        (Self::INTERNAL_DNT, "InternalDnt"),
131        (Self::SPEC_CORE, "SpecCore"),
132        (Self::TALENT_ROW_A, "TalentRowA"),
133        (Self::TALENT_ROW_B, "TalentRowB"),
134        (Self::MAJOR_COOLDOWNS, "MajorCooldowns"),
135        (Self::HEALING_SPELLS, "HealingSpells"),
136        (Self::ROGUE_LETHAL_POISONS, "RogueLethalPoisons"),
137        (Self::ROGUE_NON_LETHAL_POISONS, "RogueNonLethalPoisons"),
138        (Self::PVP, "Pvp"),
139        (Self::NPC_ABILITIES, "NpcAbilities"),
140        (Self::LEGENDARY_ITEMS, "LegendaryItems"),
141        (Self::ARTIFACT_TRAITS, "ArtifactTraits"),
142        (Self::ARTIFACT_PASSIVES, "ArtifactPassives"),
143        (Self::LEGENDARY_EFFECTS, "LegendaryEffects"),
144        (Self::AZERITE_ESSENCES, "AzeriteEssences"),
145        (Self::SHADOWLANDS_GENERAL, "ShadowlandsGeneral"),
146        (Self::SHADOWLANDS_COMBAT, "ShadowlandsCombat"),
147        (Self::CONDUITS, "Conduits"),
148        (Self::COVENANT, "Covenant"),
149        (Self::DRAGON_RACING, "DragonRacing"),
150        (Self::DRAGONFLIGHT_CRAFTED_GEAR, "DragonflightCraftedGear"),
151        (Self::PRIMORDIAL_STONE, "PrimordialStone"),
152        (Self::SINGING_CITRINE, "SingingCitrine"),
153        (Self::BRANN_DELVES, "BrannDelves"),
154        (Self::HERO_TALENTS, "HeroTalents"),
155        (Self::ITEM_EFFECTS, "ItemEffects"),
156        (Self::FURNITURE_CRAFTS, "FurnitureCrafts"),
157        (Self::BLACKSMITHING, "Blacksmithing"),
158        (Self::LEATHERWORKING, "Leatherworking"),
159        (Self::TAILORING, "Tailoring"),
160        (Self::SKINNING, "Skinning"),
161        (Self::ALCHEMY, "Alchemy"),
162        (Self::JEWELCRAFTING, "Jewelcrafting"),
163        (Self::ALL_PROFESSIONS, "AllProfessions"),
164        (Self::COOKING, "Cooking"),
165        (Self::MODIFIED_CRAFTING_REAGENTS, "ModifiedCraftingReagents"),
166        (Self::ENCHANTING, "Enchanting"),
167        (Self::INSCRIPTION, "Inscription"),
168        (Self::ALL_CRAFTED, "AllCrafted"),
169        (Self::ENGINEERING, "Engineering"),
170        (Self::MINING, "Mining"),
171        (Self::HERBALISM, "Herbalism"),
172        (Self::PROF_CLASSIC, "ProfClassic"),
173        (Self::PROF_TBC, "ProfTbc"),
174        (Self::PROF_WOTLK, "ProfWotlk"),
175        (Self::PROF_CATA, "ProfCata"),
176        (Self::PROF_MOP, "ProfMop"),
177        (Self::PROF_WOD, "ProfWod"),
178        (Self::PROF_LEGION, "ProfLegion"),
179        (Self::PROF_BFA, "ProfBfa"),
180        (Self::PROF_DF, "ProfDf"),
181    ];
182
183    pub fn known() -> impl Iterator<Item = (Self, &'static str)> + 'static {
184        Self::KNOWN.iter().copied()
185    }
186
187    #[must_use]
188    pub fn is_known(self) -> bool {
189        Self::KNOWN.iter().any(|(label, _)| *label == self)
190    }
191
192    #[must_use]
193    pub fn name(self) -> Option<&'static str> {
194        Self::KNOWN
195            .iter()
196            .find(|(label, _)| *label == self)
197            .map(|(_, name)| *name)
198    }
199}
200
201impl From<i32> for SpellLabel {
202    fn from(value: i32) -> Self {
203        Self(value)
204    }
205}
206
207impl From<SpellLabel> for i32 {
208    fn from(label: SpellLabel) -> Self {
209        label.0
210    }
211}
212
213impl std::fmt::Display for SpellLabel {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        match self.name() {
216            Some(name) => f.write_str(name),
217            None => write!(f, "Unknown({})", self.0),
218        }
219    }
220}
221
222impl Serialize for SpellLabel {
223    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224    where
225        S: serde::Serializer,
226    {
227        serializer.serialize_i32(self.0)
228    }
229}
230
231impl<'de> Deserialize<'de> for SpellLabel {
232    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
233    where
234        D: serde::Deserializer<'de>,
235    {
236        let value = i32::deserialize(deserializer)?;
237
238        Ok(Self(value))
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use googletest::prelude::*;
245    use rstest::rstest;
246
247    use super::*;
248
249    #[gtest]
250    #[rstest]
251    #[case::rogue(SpellLabel::ROGUE, Some("Rogue"))]
252    #[case::mage(SpellLabel::MAGE, Some("Mage"))]
253    #[case::evoker_bronze(SpellLabel::EVOKER_BRONZE, Some("EvokerBronze"))]
254    #[case::unknown(SpellLabel(999_999), None)]
255    fn name(#[case] label: SpellLabel, #[case] expected: Option<&'static str>) -> Result<()> {
256        if let Some(name) = expected {
257            verify_that!(label.name(), some(eq(name)))
258        } else {
259            verify_that!(label.name(), none())
260        }
261    }
262
263    #[gtest]
264    #[rstest]
265    #[case::mage(SpellLabel::MAGE, true)]
266    #[case::rogue(SpellLabel::ROGUE, true)]
267    #[case::unknown(SpellLabel(999_999), false)]
268    fn is_known(#[case] label: SpellLabel, #[case] expected: bool) -> Result<()> {
269        verify_that!(label.is_known(), eq(expected))
270    }
271
272    #[gtest]
273    #[rstest]
274    #[case::known(SpellLabel::ROGUE, "Rogue")]
275    #[case::unknown(SpellLabel(42), "Unknown(42)")]
276    fn display(#[case] label: SpellLabel, #[case] expected: &str) -> Result<()> {
277        verify_that!(format!("{label}"), eq(expected))
278    }
279
280    #[gtest]
281    fn from_i32() -> Result<()> {
282        verify_that!(SpellLabel::from(20).0, eq(20))
283    }
284
285    #[gtest]
286    fn into_i32() -> Result<()> {
287        verify_that!(i32::from(SpellLabel::ROGUE), eq(20))
288    }
289
290    #[gtest]
291    fn serialize_as_bare_i32() -> Result<()> {
292        verify_that!(
293            serde_json::to_string(&SpellLabel::ROGUE).ok(),
294            some(eq("20"))
295        )
296    }
297
298    #[gtest]
299    fn deserialize_known() -> Result<()> {
300        verify_that!(
301            serde_json::from_str::<SpellLabel>("20").ok(),
302            some(eq(SpellLabel(20)))
303        )
304    }
305
306    #[gtest]
307    fn round_trip_unknown() -> Result<()> {
308        let label = SpellLabel(88);
309        let json = serde_json::to_string(&label).or_fail()?;
310
311        verify_that!(json, eq("88"))?;
312
313        verify_that!(
314            serde_json::from_str::<SpellLabel>(&json).ok(),
315            some(eq(label))
316        )
317    }
318}