diff options
Diffstat (limited to 'src/playerclass.rs')
-rw-r--r-- | src/playerclass.rs | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/src/playerclass.rs b/src/playerclass.rs new file mode 100644 index 0000000..77b9794 --- /dev/null +++ b/src/playerclass.rs @@ -0,0 +1,141 @@ +use std::{fmt, str::FromStr}; + +use evtclib::{EliteSpec, Profession}; + +use thiserror::Error; + +/// An enum containing either a profession or an elite spec. +/// +/// This enum provides us with a variety of things: +/// +/// Game mechanic wise, a Dragonhunter is also a Guardian, because Dragonhunter is only the elite +/// specialization. However, when outputting that to the user, we usually only write Dragonhunter, +/// and not Guardian, as that is implied. Same when filtering, when we filter for Guardian, we +/// probably don't want any Dragonhunters. +/// +/// So this enum unifies the handling between core specs and elite specs, and provides them with a +/// convenient [`Display`][Display] implementation as well. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum PlayerClass { + Profession(Profession), + EliteSpec(EliteSpec), +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Error)] +#[error("could not parse the class: {0}")] +pub struct ParsePlayerClassError(String); + +impl From<Profession> for PlayerClass { + fn from(p: Profession) -> Self { + PlayerClass::Profession(p) + } +} + +impl From<EliteSpec> for PlayerClass { + fn from(e: EliteSpec) -> Self { + PlayerClass::EliteSpec(e) + } +} + +impl FromStr for PlayerClass { + type Err = ParsePlayerClassError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let err = ParsePlayerClassError(s.to_owned()); + Profession::from_str(s) + .map(Into::into) + .map_err(|_| err.clone()) + .or_else(|_| EliteSpec::from_str(s).map(Into::into).map_err(|_| err)) + } +} + +impl From<(Profession, Option<EliteSpec>)> for PlayerClass { + fn from((profession, elite_spec): (Profession, Option<EliteSpec>)) -> Self { + if let Some(spec) = elite_spec { + spec.into() + } else { + profession.into() + } + } +} + +impl fmt::Display for PlayerClass { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use EliteSpec::*; + use Profession::*; + + let name = match *self { + PlayerClass::EliteSpec(elite) => match elite { + Dragonhunter => "Dragonhunter", + Firebrand => "Firebrand", + Berserker => "Berserker", + Spellbreaker => "Spellbreaker", + Herald => "Herald", + Renegade => "Renegade", + Scrapper => "Scrapper", + Holosmith => "Holosmith", + Druid => "Druid", + Soulbeast => "Soulbeast", + Daredevil => "Daredevil", + Deadeye => "Deadeye", + Tempest => "Tempest", + Weaver => "Weaver", + Chronomancer => "Chronomancer", + Mirage => "Mirage", + Reaper => "Reaper", + Scourge => "Scourge", + }, + PlayerClass::Profession(prof) => match prof { + Guardian => "Guardian", + Warrior => "Warrior", + Revenant => "Revenant", + Engineer => "Engineer", + Ranger => "Ranger", + Thief => "Thief", + Elementalist => "Elementalist", + Mesmer => "Mesmer", + Necromancer => "Necromancer", + }, + }; + write!(f, "{}", name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_player_class_from() { + let tests: &[(Profession, Option<EliteSpec>, PlayerClass)] = &[ + ( + Profession::Guardian, + None, + PlayerClass::Profession(Profession::Guardian), + ), + ( + Profession::Guardian, + Some(EliteSpec::Dragonhunter), + PlayerClass::EliteSpec(EliteSpec::Dragonhunter), + ), + ]; + + for (prof, elite_spec, expected) in tests { + assert_eq!(PlayerClass::from((*prof, *elite_spec)), *expected); + } + } + + #[test] + fn test_parse_player_class() { + let tests: &[(&'static str, PlayerClass)] = &[ + ("guardian", Profession::Guardian.into()), + ("dragonhunter", EliteSpec::Dragonhunter.into()), + ("warrior", Profession::Warrior.into()), + ("scourge", EliteSpec::Scourge.into()), + ]; + + for (input, expected) in tests { + assert_eq!(input.parse(), Ok(*expected)); + } + } +} |