1use serde::{Deserialize, Serialize};
2
3use super::super::combat::ResourceType;
4
5#[derive(
6 Clone,
7 Copy,
8 Debug,
9 Eq,
10 Hash,
11 PartialEq,
12 num_enum::IntoPrimitive,
13 num_enum::TryFromPrimitive,
14 serde::Deserialize,
15 serde::Serialize,
16 strum::Display,
17 strum::EnumCount,
18 strum::EnumIter,
19 strum::EnumString,
20)]
21#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
22#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
23#[repr(u8)]
24pub enum ClassId {
26 Warrior = 1,
27 Paladin = 2,
28 Hunter = 3,
29 Rogue = 4,
30 Priest = 5,
31 #[strum(serialize = "Death Knight", serialize = "DeathKnight")]
32 DeathKnight = 6,
33 Shaman = 7,
34 Mage = 8,
35 Warlock = 9,
36 Monk = 10,
37 Druid = 11,
38 #[strum(serialize = "Demon Hunter", serialize = "DemonHunter")]
39 DemonHunter = 12,
40 Evoker = 13,
41}
42
43macro_rules! spec_table {
44 (
45 $(
46 $variant:ident => wow: $wow:expr, toml: $toml:literal, slug: $slug:literal,
47 class: $class:ident, resource: $resource:ident, role: $role:ident
48 ),+ $(,)?
49 ) => {
50 impl SpecId {
51 pub const fn wow_spec_id(self) -> u32 {
52 match self { $(Self::$variant => $wow),+ }
53 }
54
55 pub const fn from_wow_spec_id(wow_id: u32) -> Option<Self> {
56 Some(match wow_id {
57 $($wow => Self::$variant,)+
58 _ => return None,
59 })
60 }
61
62 pub const fn toml_key(self) -> &'static str {
63 match self { $(Self::$variant => $toml),+ }
64 }
65
66 pub fn from_toml_key(value: &str) -> Option<Self> {
67 Some(match value {
68 $($toml => Self::$variant,)+
69 _ => return None,
70 })
71 }
72
73 pub fn from_manifest_slug(slug: &str) -> Option<Self> {
74 Some(match slug {
75 $($slug => Self::$variant,)+
76 _ => return None,
77 })
78 }
79
80 pub const fn class(self) -> ClassId {
81 match self { $(Self::$variant => ClassId::$class),+ }
82 }
83
84 pub const fn slug(self) -> &'static str {
86 match self { $(Self::$variant => $slug),+ }
87 }
88
89 pub const fn primary_resource(self) -> ResourceType {
90 match self { $(Self::$variant => ResourceType::$resource),+ }
91 }
92
93 pub const fn is_dps(self) -> bool {
94 match self { $(Self::$variant => spec_table!(@is_role dps $role)),+ }
95 }
96
97 pub const fn is_tank(self) -> bool {
98 match self { $(Self::$variant => spec_table!(@is_role tank $role)),+ }
99 }
100
101 pub const fn is_healer(self) -> bool {
102 match self { $(Self::$variant => spec_table!(@is_role healer $role)),+ }
103 }
104 }
105 };
106
107 (@is_role dps dps) => { true };
108 (@is_role tank tank) => { true };
109 (@is_role healer healer) => { true };
110 (@is_role $want:ident $have:ident) => { false };
111}
112
113#[derive(
114 Clone,
115 Copy,
116 Debug,
117 Eq,
118 Hash,
119 PartialEq,
120 num_enum::IntoPrimitive,
121 num_enum::TryFromPrimitive,
122 serde::Deserialize,
123 serde::Serialize,
124 strum::Display,
125 strum::EnumCount,
126 strum::EnumIter,
127 strum::EnumString,
128)]
129#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
130#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
131#[repr(u8)]
132pub enum SpecId {
134 Arms = 1,
135 Fury = 2,
136 #[strum(serialize = "Protection Warrior", serialize = "ProtWarrior")]
137 ProtWarrior = 3,
138 #[strum(serialize = "Holy Paladin", serialize = "HolyPaladin")]
139 HolyPaladin = 4,
140 #[strum(serialize = "Protection Paladin", serialize = "ProtPaladin")]
141 ProtPaladin = 5,
142 Retribution = 6,
143 #[strum(
144 serialize = "Beast Mastery",
145 serialize = "BeastMastery",
146 serialize = "bm"
147 )]
148 BeastMastery = 7,
149 #[strum(serialize = "Marksmanship", serialize = "mm")]
150 Marksmanship = 8,
151 Survival = 9,
152 Assassination = 10,
153 Outlaw = 11,
154 Subtlety = 12,
155 Discipline = 13,
156 #[strum(serialize = "Holy Priest", serialize = "HolyPriest")]
157 HolyPriest = 14,
158 Shadow = 15,
159 Blood = 16,
160 #[strum(serialize = "Frost DK", serialize = "FrostDK")]
161 FrostDK = 17,
162 Unholy = 18,
163 Elemental = 19,
164 Enhancement = 20,
165 #[strum(serialize = "Restoration Shaman", serialize = "RestoShaman")]
166 RestoShaman = 21,
167 Arcane = 22,
168 Fire = 23,
169 #[strum(serialize = "Frost Mage", serialize = "FrostMage")]
170 FrostMage = 24,
171 Affliction = 25,
172 Demonology = 26,
173 Destruction = 27,
174 Brewmaster = 28,
175 Mistweaver = 29,
176 Windwalker = 30,
177 Balance = 31,
178 Feral = 32,
179 Guardian = 33,
180 #[strum(serialize = "Restoration Druid", serialize = "RestoDruid")]
181 RestoDruid = 34,
182 Havoc = 35,
183 Vengeance = 36,
184 Devourer = 40,
185 Devastation = 37,
186 Preservation = 38,
187 Augmentation = 39,
188}
189
190#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
191#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
192#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
193pub struct ImplementedSpecInfo {
194 pub spec_id: u32,
195 pub class_id: u32,
196 pub class_name: String,
197 pub spec_name: String,
198 pub display_name: String,
199 pub slug: String,
200 pub spell_count: usize,
201 pub aura_count: usize,
202 pub talent_count: usize,
203}
204
205wowlab_engine_macros::define_error! {
206#[derive(Debug)]
208pub struct SpecIdParseError {
209 kind: SpecIdParseErrorKind,
210}
211
212#[derive(Debug, thiserror::Error)]
213enum SpecIdParseErrorKind {
214 #[error("unknown wow spec id: {wow_id}")]
215 UnknownWowId { wow_id: u32 },
216 #[error("unknown spec key: '{value}'")]
217 UnknownTomlKey { value: String },
218}
219}
220
221impl SpecIdParseError {
222 const fn unknown_wow_id(wow_id: u32) -> Self {
223 Self {
224 kind: SpecIdParseErrorKind::UnknownWowId { wow_id },
225 }
226 }
227
228 fn unknown_toml_key(value: impl Into<String>) -> Self {
229 Self {
230 kind: SpecIdParseErrorKind::UnknownTomlKey {
231 value: value.into(),
232 },
233 }
234 }
235}
236
237impl SpecId {
238 pub const COUNT: usize = <Self as strum::EnumCount>::COUNT;
239
240 pub fn parse_wow_spec_id(wow_id: u32) -> Result<Self, SpecIdParseError> {
246 Self::from_wow_spec_id(wow_id).ok_or_else(|| SpecIdParseError::unknown_wow_id(wow_id))
247 }
248
249 pub fn parse_toml_key(value: &str) -> Result<Self, SpecIdParseError> {
255 Self::from_toml_key(value).ok_or_else(|| SpecIdParseError::unknown_toml_key(value))
256 }
257}
258
259spec_table! {
260 Arms => wow: 71, toml: "arms", slug: "arms_warrior", class: Warrior, resource: Rage, role: dps,
261 Fury => wow: 72, toml: "fury", slug: "fury_warrior", class: Warrior, resource: Rage, role: dps,
262 ProtWarrior => wow: 73, toml: "prot_warrior", slug: "protection_warrior", class: Warrior, resource: Rage, role: tank,
263 HolyPaladin => wow: 65, toml: "holy_paladin", slug: "holy_paladin", class: Paladin, resource: HolyPower, role: healer,
264 ProtPaladin => wow: 66, toml: "prot_paladin", slug: "protection_paladin", class: Paladin, resource: HolyPower, role: tank,
265 Retribution => wow: 70, toml: "retribution", slug: "retribution_paladin", class: Paladin, resource: HolyPower, role: dps,
266 BeastMastery => wow: 253, toml: "beast_mastery", slug: "beast_mastery_hunter", class: Hunter, resource: Focus, role: dps,
267 Marksmanship => wow: 254, toml: "marksmanship", slug: "marksmanship_hunter", class: Hunter, resource: Focus, role: dps,
268 Survival => wow: 255, toml: "survival", slug: "survival_hunter", class: Hunter, resource: Focus, role: dps,
269 Assassination => wow: 259, toml: "assassination", slug: "assassination_rogue", class: Rogue, resource: Energy, role: dps,
270 Outlaw => wow: 260, toml: "outlaw", slug: "outlaw_rogue", class: Rogue, resource: Energy, role: dps,
271 Subtlety => wow: 261, toml: "subtlety", slug: "subtlety_rogue", class: Rogue, resource: Energy, role: dps,
272 Discipline => wow: 256, toml: "discipline", slug: "discipline_priest", class: Priest, resource: Mana, role: healer,
273 HolyPriest => wow: 257, toml: "holy_priest", slug: "holy_priest", class: Priest, resource: Mana, role: healer,
274 Shadow => wow: 258, toml: "shadow", slug: "shadow_priest", class: Priest, resource: Mana, role: dps,
275 Blood => wow: 250, toml: "blood", slug: "blood_death_knight", class: DeathKnight, resource: RunicPower, role: tank,
276 FrostDK => wow: 251, toml: "frost_dk", slug: "frost_death_knight", class: DeathKnight, resource: RunicPower, role: dps,
277 Unholy => wow: 252, toml: "unholy", slug: "unholy_death_knight", class: DeathKnight, resource: RunicPower, role: dps,
278 Elemental => wow: 262, toml: "elemental", slug: "elemental_shaman", class: Shaman, resource: Maelstrom, role: dps,
279 Enhancement => wow: 263, toml: "enhancement", slug: "enhancement_shaman", class: Shaman, resource: Maelstrom, role: dps,
280 RestoShaman => wow: 264, toml: "resto_shaman", slug: "restoration_shaman", class: Shaman, resource: Maelstrom, role: healer,
281 Arcane => wow: 62, toml: "arcane", slug: "arcane_mage", class: Mage, resource: Mana, role: dps,
282 Fire => wow: 63, toml: "fire", slug: "fire_mage", class: Mage, resource: Mana, role: dps,
283 FrostMage => wow: 64, toml: "frost_mage", slug: "frost_mage", class: Mage, resource: Mana, role: dps,
284 Affliction => wow: 265, toml: "affliction", slug: "affliction_warlock", class: Warlock, resource: SoulShards, role: dps,
285 Demonology => wow: 266, toml: "demonology", slug: "demonology_warlock", class: Warlock, resource: SoulShards, role: dps,
286 Destruction => wow: 267, toml: "destruction", slug: "destruction_warlock", class: Warlock, resource: SoulShards, role: dps,
287 Brewmaster => wow: 268, toml: "brewmaster", slug: "brewmaster_monk", class: Monk, resource: Chi, role: tank,
288 Mistweaver => wow: 270, toml: "mistweaver", slug: "mistweaver_monk", class: Monk, resource: Chi, role: healer,
289 Windwalker => wow: 269, toml: "windwalker", slug: "windwalker_monk", class: Monk, resource: Chi, role: dps,
290 Balance => wow: 102, toml: "balance", slug: "balance_druid", class: Druid, resource: LunarPower, role: dps,
291 Feral => wow: 103, toml: "feral", slug: "feral_druid", class: Druid, resource: Energy, role: dps,
292 Guardian => wow: 104, toml: "guardian", slug: "guardian_druid", class: Druid, resource: Energy, role: tank,
293 RestoDruid => wow: 105, toml: "resto_druid", slug: "restoration_druid", class: Druid, resource: Mana, role: healer,
294 Havoc => wow: 577, toml: "havoc", slug: "havoc_demon_hunter", class: DemonHunter, resource: Fury, role: dps,
295 Vengeance => wow: 581, toml: "vengeance", slug: "vengeance_demon_hunter", class: DemonHunter, resource: Fury, role: tank,
296 Devourer => wow: 1480, toml: "devourer", slug: "devourer_demon_hunter", class: DemonHunter, resource: Fury, role: dps,
297 Devastation => wow: 1467, toml: "devastation", slug: "devastation_evoker", class: Evoker, resource: Essence, role: dps,
298 Preservation => wow: 1468, toml: "preservation", slug: "preservation_evoker", class: Evoker, resource: Essence, role: healer,
299 Augmentation => wow: 1473, toml: "augmentation", slug: "augmentation_evoker", class: Evoker, resource: Essence, role: dps,
300}
301
302impl ClassId {
303 #[must_use]
304 pub const fn slug(self) -> &'static str {
305 match self {
306 Self::Warrior => "warrior",
307 Self::Paladin => "paladin",
308 Self::Hunter => "hunter",
309 Self::Rogue => "rogue",
310 Self::Priest => "priest",
311 Self::DeathKnight => "death_knight",
312 Self::Shaman => "shaman",
313 Self::Mage => "mage",
314 Self::Warlock => "warlock",
315 Self::Monk => "monk",
316 Self::Druid => "druid",
317 Self::DemonHunter => "demon_hunter",
318 Self::Evoker => "evoker",
319 }
320 }
321}
322
323#[derive(
324 Clone,
325 Copy,
326 Debug,
327 Eq,
328 Hash,
329 PartialEq,
330 num_enum::IntoPrimitive,
331 num_enum::TryFromPrimitive,
332 serde::Deserialize,
333 serde::Serialize,
334 strum::Display,
335 strum::EnumCount,
336 strum::EnumIter,
337 strum::EnumString,
338)]
339#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
340#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
341#[repr(u8)]
342pub enum RaceId {
344 #[strum(serialize = "Human", serialize = "human")]
345 Human = 1,
346 #[strum(serialize = "Orc", serialize = "orc")]
347 Orc = 2,
348 #[strum(serialize = "Dwarf", serialize = "dwarf")]
349 Dwarf = 3,
350 #[strum(
351 serialize = "Night Elf",
352 serialize = "NightElf",
353 serialize = "night_elf"
354 )]
355 NightElf = 4,
356 #[strum(serialize = "Undead", serialize = "undead")]
357 Undead = 5,
358 #[strum(serialize = "Tauren", serialize = "tauren")]
359 Tauren = 6,
360 #[strum(serialize = "Gnome", serialize = "gnome")]
361 Gnome = 7,
362 #[strum(serialize = "Troll", serialize = "troll")]
363 Troll = 8,
364 #[strum(serialize = "Goblin", serialize = "goblin")]
365 Goblin = 9,
366 #[strum(
367 serialize = "Blood Elf",
368 serialize = "BloodElf",
369 serialize = "blood_elf"
370 )]
371 BloodElf = 10,
372 #[strum(serialize = "Draenei", serialize = "draenei")]
373 Draenei = 11,
374 #[strum(serialize = "Worgen", serialize = "worgen")]
375 Worgen = 12,
376 #[strum(
377 serialize = "Pandaren (Alliance)",
378 serialize = "PandarenA",
379 serialize = "pandaren_alliance",
380 serialize = "Pandaren",
381 serialize = "pandaren"
382 )]
383 PandarenA = 13,
384 #[strum(
385 serialize = "Pandaren (Horde)",
386 serialize = "PandarenH",
387 serialize = "pandaren_horde"
388 )]
389 PandarenH = 14,
390 #[strum(serialize = "Nightborne", serialize = "nightborne")]
391 Nightborne = 15,
392 #[strum(
393 serialize = "Highmountain Tauren",
394 serialize = "HighmountainTauren",
395 serialize = "highmountain_tauren"
396 )]
397 HighmountainTauren = 16,
398 #[strum(serialize = "Void Elf", serialize = "VoidElf", serialize = "void_elf")]
399 VoidElf = 17,
400 #[strum(
401 serialize = "Lightforged Draenei",
402 serialize = "LightforgedDraenei",
403 serialize = "lightforged_draenei"
404 )]
405 LightforgedDraenei = 18,
406 #[strum(
407 serialize = "Zandalari Troll",
408 serialize = "ZandalariTroll",
409 serialize = "zandalari_troll"
410 )]
411 ZandalariTroll = 19,
412 #[strum(
413 serialize = "Kul Tiran",
414 serialize = "KulTiran",
415 serialize = "kul_tiran"
416 )]
417 KulTiran = 20,
418 #[strum(
419 serialize = "Dark Iron Dwarf",
420 serialize = "DarkIronDwarf",
421 serialize = "dark_iron_dwarf"
422 )]
423 DarkIronDwarf = 21,
424 #[strum(serialize = "Vulpera", serialize = "vulpera")]
425 Vulpera = 22,
426 #[strum(
427 serialize = "Mag'har Orc",
428 serialize = "MagharOrc",
429 serialize = "mag_har_orc",
430 serialize = "maghar_orc"
431 )]
432 MagharOrc = 23,
433 #[strum(serialize = "Mechagnome", serialize = "mechagnome")]
434 Mechagnome = 24,
435 #[strum(serialize = "Dracthyr", serialize = "dracthyr")]
436 Dracthyr = 25,
437 #[strum(
438 serialize = "Earthen (Alliance)",
439 serialize = "EarthenA",
440 serialize = "earthen_alliance"
441 )]
442 EarthenA = 26,
443 #[strum(
444 serialize = "Earthen (Horde)",
445 serialize = "EarthenH",
446 serialize = "earthen_horde"
447 )]
448 EarthenH = 27,
449}
450
451impl RaceId {
452 #[must_use]
454 pub const fn wow_race_id(self) -> i32 {
457 match self {
458 Self::Human => 1,
459 Self::Orc => 2,
460 Self::Dwarf => 3,
461 Self::NightElf => 4,
462 Self::Undead => 5,
463 Self::Tauren => 6,
464 Self::Gnome => 7,
465 Self::Troll => 8,
466 Self::Goblin => 9,
467 Self::BloodElf => 10,
468 Self::Draenei => 11,
469 Self::Worgen => 22,
470 Self::PandarenA => 25,
471 Self::PandarenH => 26,
472 Self::Nightborne => 27,
473 Self::HighmountainTauren => 28,
474 Self::VoidElf => 29,
475 Self::LightforgedDraenei => 30,
476 Self::ZandalariTroll => 31,
477 Self::KulTiran => 32,
478 Self::DarkIronDwarf => 34,
479 Self::Vulpera => 35,
480 Self::MagharOrc => 36,
481 Self::Mechagnome => 37,
482 Self::Dracthyr => 52,
483 Self::EarthenA => 85,
484 Self::EarthenH => 84,
485 }
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use googletest::prelude::*;
492 use rstest::rstest;
493 use strum::IntoEnumIterator;
494
495 use super::*;
496
497 #[gtest]
498 fn wow_race_id_is_distinct_for_every_race() -> Result<()> {
499 let mut seen = std::collections::BTreeSet::new();
500
501 for race in RaceId::iter() {
502 scoped_trace!("race {race:?} maps to {}", race.wow_race_id());
503 verify_true!(seen.insert(race.wow_race_id()))?;
504 }
505
506 verify_that!(seen.len(), eq(<RaceId as strum::EnumCount>::COUNT))
507 }
508
509 #[gtest]
510 #[rstest]
511 #[case::orc(RaceId::Orc, 2)]
512 #[case::worgen(RaceId::Worgen, 22)]
513 #[case::pandaren_alliance(RaceId::PandarenA, 25)]
514 #[case::dark_iron_dwarf(RaceId::DarkIronDwarf, 34)]
515 #[case::dracthyr(RaceId::Dracthyr, 52)]
516 #[case::earthen_alliance(RaceId::EarthenA, 85)]
517 fn wow_race_id_matches_chr_races(#[case] race: RaceId, #[case] expected: i32) -> Result<()> {
518 verify_that!(race.wow_race_id(), eq(expected))
519 }
520
521 #[gtest]
522 fn from_wow_spec_id_round_trips_for_all_specs() -> Result<()> {
523 for spec in SpecId::iter() {
524 let wow_id = spec.wow_spec_id();
525
526 scoped_trace!("round-trip for {spec:?} (wow_id={wow_id})");
527 verify_that!(SpecId::from_wow_spec_id(wow_id), some(eq(spec)))?;
528 }
529
530 Ok(())
531 }
532
533 #[gtest]
534 fn from_wow_spec_id_returns_none_for_unknown() -> Result<()> {
535 verify_that!(SpecId::from_wow_spec_id(0), none())?;
536
537 verify_that!(SpecId::from_wow_spec_id(99_999), none())
538 }
539
540 #[gtest]
541 #[rstest]
542 #[case::outlaw(SpecId::Outlaw)]
543 #[case::arms(SpecId::Arms)]
544 #[case::resto_druid(SpecId::RestoDruid)]
545 fn spec_toml_key_round_trips(#[case] spec: SpecId) -> Result<()> {
546 verify_that!(SpecId::from_toml_key(spec.toml_key()), some(eq(spec)))
547 }
548
549 #[gtest]
550 fn spec_from_toml_key_unknown_is_none() -> Result<()> {
551 verify_that!(SpecId::from_toml_key("nonsense"), none())
552 }
553
554 #[gtest]
555 fn spec_parse_toml_key_ok() -> Result<()> {
556 verify_that!(
557 SpecId::parse_toml_key("outlaw").ok(),
558 some(eq(SpecId::Outlaw))
559 )
560 }
561
562 #[gtest]
563 fn spec_parse_toml_key_err_message() -> Result<()> {
564 let err = SpecId::parse_toml_key("bogus").unwrap_err();
565
566 verify_that!(err, displays_as(contains_substring("unknown spec key")))
567 }
568
569 #[gtest]
570 fn spec_parse_wow_spec_id_ok() -> Result<()> {
571 verify_that!(
572 SpecId::parse_wow_spec_id(260).ok(),
573 some(eq(SpecId::Outlaw))
574 )
575 }
576
577 #[gtest]
578 fn spec_parse_wow_spec_id_err_message() -> Result<()> {
579 let err = SpecId::parse_wow_spec_id(0).unwrap_err();
580
581 verify_that!(err, displays_as(contains_substring("unknown wow spec id")))
582 }
583
584 #[gtest]
585 #[rstest]
586 #[case::outlaw(SpecId::Outlaw, "outlaw_rogue")]
587 #[case::prot_warrior(SpecId::ProtWarrior, "protection_warrior")]
588 fn spec_manifest_slug_round_trips(#[case] spec: SpecId, #[case] slug: &str) -> Result<()> {
589 verify_that!(spec.slug(), eq(slug))?;
590
591 verify_that!(SpecId::from_manifest_slug(slug), some(eq(spec)))
592 }
593
594 #[gtest]
595 fn spec_from_manifest_slug_unknown_is_none() -> Result<()> {
596 verify_that!(SpecId::from_manifest_slug("x"), none())
597 }
598
599 #[gtest]
600 #[rstest]
601 #[case::outlaw(SpecId::Outlaw, ClassId::Rogue, ResourceType::Energy)]
602 #[case::blood(SpecId::Blood, ClassId::DeathKnight, ResourceType::RunicPower)]
603 #[case::augmentation(SpecId::Augmentation, ClassId::Evoker, ResourceType::Essence)]
604 fn spec_class_and_resource(
605 #[case] spec: SpecId,
606 #[case] class: ClassId,
607 #[case] resource: ResourceType,
608 ) -> Result<()> {
609 verify_that!(spec.class(), eq(class))?;
610
611 verify_that!(spec.primary_resource(), eq(resource))
612 }
613
614 #[gtest]
615 #[rstest]
616 #[case::outlaw_dps(SpecId::Outlaw, true, false, false)]
617 #[case::blood_tank(SpecId::Blood, false, true, false)]
618 #[case::holy_paladin_healer(SpecId::HolyPaladin, false, false, true)]
619 fn spec_role_predicates(
620 #[case] spec: SpecId,
621 #[case] is_dps: bool,
622 #[case] is_tank: bool,
623 #[case] is_healer: bool,
624 ) -> Result<()> {
625 verify_that!(spec.is_dps(), eq(is_dps))?;
626 verify_that!(spec.is_tank(), eq(is_tank))?;
627
628 verify_that!(spec.is_healer(), eq(is_healer))
629 }
630
631 #[gtest]
632 #[rstest]
633 #[case::warrior(ClassId::Warrior, "warrior")]
634 #[case::death_knight(ClassId::DeathKnight, "death_knight")]
635 #[case::demon_hunter(ClassId::DemonHunter, "demon_hunter")]
636 #[case::evoker(ClassId::Evoker, "evoker")]
637 fn class_slug(#[case] class: ClassId, #[case] slug: &str) -> Result<()> {
638 verify_that!(class.slug(), eq(slug))
639 }
640
641 #[gtest]
642 fn class_enum_string_aliases() -> Result<()> {
643 verify_that!(
644 "DeathKnight".parse::<ClassId>().ok(),
645 some(eq(ClassId::DeathKnight))
646 )?;
647
648 verify_that!(
649 "Death Knight".parse::<ClassId>().ok(),
650 some(eq(ClassId::DeathKnight))
651 )
652 }
653
654 #[gtest]
655 fn spec_enum_string_aliases() -> Result<()> {
656 verify_that!("bm".parse::<SpecId>().ok(), some(eq(SpecId::BeastMastery)))?;
657
658 verify_that!("FrostDK".parse::<SpecId>().ok(), some(eq(SpecId::FrostDK)))
659 }
660
661 #[gtest]
662 fn race_enum_string_aliases() -> Result<()> {
663 verify_that!(
664 "MagharOrc".parse::<RaceId>().ok(),
665 some(eq(RaceId::MagharOrc))
666 )?;
667
668 verify_that!(
669 "night_elf".parse::<RaceId>().ok(),
670 some(eq(RaceId::NightElf))
671 )
672 }
673}
674
675#[derive(
677 Clone,
678 Copy,
679 Debug,
680 Eq,
681 Hash,
682 PartialEq,
683 num_enum::IntoPrimitive,
684 num_enum::TryFromPrimitive,
685 serde::Deserialize,
686 serde::Serialize,
687 strum::Display,
688 strum::EnumCount,
689 strum::EnumIter,
690 strum::EnumString,
691)]
692#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
693#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
694#[repr(u8)]
695pub enum PetType {
697 Ferocity = 0,
698 Tenacity = 1,
699 Cunning = 2,
700}
701
702#[derive(
703 Clone,
704 Copy,
705 Debug,
706 Eq,
707 Hash,
708 PartialEq,
709 num_enum::IntoPrimitive,
710 num_enum::TryFromPrimitive,
711 serde::Deserialize,
712 serde::Serialize,
713 strum::Display,
714 strum::EnumCount,
715 strum::EnumIter,
716 strum::EnumString,
717)]
718#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
719#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
720#[repr(u8)]
721pub enum PetKind {
723 Permanent = 0,
724 Guardian = 1,
725 Summon = 2,
726}