diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2020-09-28 13:28:29 +0200 | 
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2020-09-28 13:28:29 +0200 | 
| commit | ba10db6e8120fe9315bf0dec99e9dee188b8332c (patch) | |
| tree | 9d3a2d831aa0b9ecc20cc21010f2d782fba55d2e | |
| parent | 3b79ad8fa9b4a8c9c535b417129e3f70769074e0 (diff) | |
| parent | 132bc6e276bf996b8a67990ad32042b8023d8786 (diff) | |
| download | evtclib-ba10db6e8120fe9315bf0dec99e9dee188b8332c.tar.gz evtclib-ba10db6e8120fe9315bf0dec99e9dee188b8332c.tar.bz2 evtclib-ba10db6e8120fe9315bf0dec99e9dee188b8332c.zip | |
Merge branch 'boss-encounter-split' into master
| -rw-r--r-- | CHANGELOG.md | 13 | ||||
| -rw-r--r-- | src/analyzers/mod.rs | 56 | ||||
| -rw-r--r-- | src/analyzers/raids/mod.rs | 2 | ||||
| -rw-r--r-- | src/analyzers/raids/w6.rs | 16 | ||||
| -rw-r--r-- | src/gamedata.rs | 525 | ||||
| -rw-r--r-- | src/lib.rs | 33 | ||||
| -rw-r--r-- | tests/parsing.rs | 66 | 
7 files changed, 576 insertions, 135 deletions
| diff --git a/CHANGELOG.md b/CHANGELOG.md index 28666fd..c6820d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,19 @@ All notable changes to this project will be documented in this file.  - `analyzers::fractal::Ai` with logic to determine CM and outcome of the    Sunqua Peak CM fight. +### Changed +- `gamedata::Boss` has been split in `gamedata::Boss` and `gamedata::Encounter` +  - `Encounter::VoiceOfTheFallen` is now `Encounter::SuperKodanBrothers` +  - `Encounter::LargosTwins` is now `Encounter::TwinLargos` +  - `Boss::Xera2`, `Boss::Nikare`, `Boss::Kenut`, `Boss::ClawOfTheFallen` and +    `Boss::VoiceOfTheFallen` have been introduced +- `gamedata::Boss` is no longer re-exported as `evtclib::Boss`, instead +  `evtclib::Encounter` is exported. + +### Removed +- Various `*_ID` constants from `gamedata`: `XERA_PHASE2_ID`, `NIKARE_ID`, +  `KENUT_ID`, `VOICE_OF_THE_FALLEN_ID` and `CLAW_OF_THE_FALLEN_ID`. +  ## 0.4.3 - 2020-09-21  ### Added  - `gamedata::VOICE_OF_THE_FALLEN_ID` and `gamedata::CLAW_OF_THE_FALLEN_ID`. diff --git a/src/analyzers/mod.rs b/src/analyzers/mod.rs index 28724a2..ff04728 100644 --- a/src/analyzers/mod.rs +++ b/src/analyzers/mod.rs @@ -24,7 +24,7 @@  //! [`Log::analyzer`][Log::analyzer] (or [`for_log`][for_log]) and the methods defined in  //! [`Analyzer`][Analyzer]. -use crate::{Boss, Log}; +use crate::{Encounter, Log};  pub mod fractals;  pub mod helpers; @@ -82,41 +82,43 @@ pub fn for_log<'l>(log: &'l Log) -> Option<Box<dyn Analyzer + 'l>> {      let boss = log.encounter()?;      match boss { -        Boss::ValeGuardian | Boss::Gorseval | Boss::Sabetha => { +        Encounter::ValeGuardian | Encounter::Gorseval | Encounter::Sabetha => {              Some(Box::new(raids::GenericRaid::new(log)))          } -        Boss::Slothasor | Boss::Matthias => Some(Box::new(raids::GenericRaid::new(log))), +        Encounter::Slothasor | Encounter::Matthias => Some(Box::new(raids::GenericRaid::new(log))), -        Boss::KeepConstruct => Some(Box::new(raids::GenericRaid::new(log))), -        Boss::Xera => Some(Box::new(raids::Xera::new(log))), +        Encounter::KeepConstruct => Some(Box::new(raids::GenericRaid::new(log))), +        Encounter::Xera => Some(Box::new(raids::Xera::new(log))), -        Boss::Cairn => Some(Box::new(raids::Cairn::new(log))), -        Boss::MursaatOverseer => Some(Box::new(raids::MursaatOverseer::new(log))), -        Boss::Samarog => Some(Box::new(raids::Samarog::new(log))), -        Boss::Deimos => Some(Box::new(raids::Deimos::new(log))), +        Encounter::Cairn => Some(Box::new(raids::Cairn::new(log))), +        Encounter::MursaatOverseer => Some(Box::new(raids::MursaatOverseer::new(log))), +        Encounter::Samarog => Some(Box::new(raids::Samarog::new(log))), +        Encounter::Deimos => Some(Box::new(raids::Deimos::new(log))), -        Boss::SoullessHorror => Some(Box::new(raids::SoullessHorror::new(log))), -        Boss::Dhuum => Some(Box::new(raids::Dhuum::new(log))), +        Encounter::SoullessHorror => Some(Box::new(raids::SoullessHorror::new(log))), +        Encounter::VoiceInTheVoid => Some(Box::new(raids::Dhuum::new(log))), -        Boss::ConjuredAmalgamate => Some(Box::new(raids::ConjuredAmalgamate::new(log))), -        Boss::LargosTwins => Some(Box::new(raids::LargosTwins::new(log))), -        Boss::Qadim => Some(Box::new(raids::Qadim::new(log))), +        Encounter::ConjuredAmalgamate => Some(Box::new(raids::ConjuredAmalgamate::new(log))), +        Encounter::TwinLargos => Some(Box::new(raids::TwinLargos::new(log))), +        Encounter::Qadim => Some(Box::new(raids::Qadim::new(log))), -        Boss::CardinalAdina => Some(Box::new(raids::CardinalAdina::new(log))), -        Boss::CardinalSabir => Some(Box::new(raids::CardinalSabir::new(log))), -        Boss::QadimThePeerless => Some(Box::new(raids::QadimThePeerless::new(log))), +        Encounter::CardinalAdina => Some(Box::new(raids::CardinalAdina::new(log))), +        Encounter::CardinalSabir => Some(Box::new(raids::CardinalSabir::new(log))), +        Encounter::QadimThePeerless => Some(Box::new(raids::QadimThePeerless::new(log))), -        Boss::Ai => Some(Box::new(fractals::Ai::new(log))), -        Boss::Skorvald => Some(Box::new(fractals::Skorvald::new(log))), -        Boss::Artsariiv | Boss::Arkk | Boss::MAMA | Boss::Siax | Boss::Ensolyss => { -            Some(Box::new(fractals::GenericFractal::new(log))) -        } +        Encounter::Ai => Some(Box::new(fractals::Ai::new(log))), +        Encounter::Skorvald => Some(Box::new(fractals::Skorvald::new(log))), +        Encounter::Artsariiv +        | Encounter::Arkk +        | Encounter::MAMA +        | Encounter::Siax +        | Encounter::Ensolyss => Some(Box::new(fractals::GenericFractal::new(log))), -        Boss::IcebroodConstruct -        | Boss::VoiceOfTheFallen -        | Boss::FraenirOfJormag -        | Boss::Boneskinner -        | Boss::WhisperOfJormag => Some(Box::new(strikes::GenericStrike::new(log))), +        Encounter::IcebroodConstruct +        | Encounter::SuperKodanBrothers +        | Encounter::FraenirOfJormag +        | Encounter::Boneskinner +        | Encounter::WhisperOfJormag => Some(Box::new(strikes::GenericStrike::new(log))),      }  } diff --git a/src/analyzers/raids/mod.rs b/src/analyzers/raids/mod.rs index bb3824b..7b636a7 100644 --- a/src/analyzers/raids/mod.rs +++ b/src/analyzers/raids/mod.rs @@ -19,7 +19,7 @@ mod w5;  pub use w5::{Dhuum, SoullessHorror};  mod w6; -pub use w6::{ConjuredAmalgamate, LargosTwins, Qadim}; +pub use w6::{ConjuredAmalgamate, Qadim, TwinLargos};  mod w7;  pub use w7::{CardinalAdina, CardinalSabir, QadimThePeerless}; diff --git a/src/analyzers/raids/w6.rs b/src/analyzers/raids/w6.rs index 8701a63..97a2094 100644 --- a/src/analyzers/raids/w6.rs +++ b/src/analyzers/raids/w6.rs @@ -1,7 +1,7 @@  //! Boss fight analyzers for Wing 6 (Mythwright Gambit)  use crate::{      analyzers::{helpers, Analyzer, Outcome}, -    gamedata::{KENUT_ID, NIKARE_ID}, +    gamedata::Boss,      EventKind, Log,  }; @@ -59,21 +59,21 @@ pub const LARGOS_CM_HEALTH: u64 = 19_200_000;  ///  /// The CM is detected by the boss's health, which is higher in the challenge mote.  #[derive(Debug, Clone, Copy)] -pub struct LargosTwins<'log> { +pub struct TwinLargos<'log> {      log: &'log Log,  } -impl<'log> LargosTwins<'log> { -    /// Create a new [`LargosTwins`] analyzer for the given log. +impl<'log> TwinLargos<'log> { +    /// Create a new [`TwinLargos`] analyzer for the given log.      ///      /// **Do not** use this method unless you know what you are doing. Instead, rely on      /// [`Log::analyzer`]!      pub fn new(log: &'log Log) -> Self { -        LargosTwins { log } +        TwinLargos { log }      }  } -impl<'log> Analyzer for LargosTwins<'log> { +impl<'log> Analyzer for TwinLargos<'log> {      fn log(&self) -> &Log {          self.log      } @@ -100,9 +100,9 @@ impl<'log> Analyzer for LargosTwins<'log> {                      continue;                  }; -                if agent.id() == NIKARE_ID { +                if agent.id() == Boss::Nikare as u16 {                      nikare_dead = true; -                } else if agent.id() == KENUT_ID { +                } else if agent.id() == Boss::Kenut as u16 {                      kenut_dead = true;                  }              } diff --git a/src/gamedata.rs b/src/gamedata.rs index 392bd01..8d0bb7f 100644 --- a/src/gamedata.rs +++ b/src/gamedata.rs @@ -6,79 +6,407 @@ use std::{  };  use thiserror::Error; -/// Enum containing all bosses with their IDs. +/// Enum containing all encounters with their IDs. +/// +/// An encounter is a fight or event for which a log can exist. An encounter consists of no, one or +/// multiple bosses. Most encounters map 1:1 to a boss (like Vale Guardian), however there are some +/// encounters with multiple bosses (like Twin Largos), and even encounters without bosses (like +/// the River of Souls, currently not implemented.). +/// +/// This enum is non-exhaustive to ensure that future encounters can be added without +/// inducing a breaking change. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, FromPrimitive)] +#[non_exhaustive] +#[repr(u16)] +pub enum Encounter { +    // Wing 1 +    ValeGuardian = Boss::ValeGuardian as u16, +    Gorseval = Boss::Gorseval as u16, +    Sabetha = Boss::Sabetha as u16, + +    // Wing 2 +    Slothasor = Boss::Slothasor as u16, +    Matthias = Boss::Matthias as u16, + +    // Wing 3 +    KeepConstruct = Boss::KeepConstruct as u16, +    Xera = Boss::Xera as u16, + +    // Wing 4 +    Cairn = Boss::Cairn as u16, +    MursaatOverseer = Boss::MursaatOverseer as u16, +    Samarog = Boss::Samarog as u16, +    Deimos = Boss::Deimos as u16, + +    // Wing 5 +    SoullessHorror = Boss::SoullessHorror as u16, +    VoiceInTheVoid = Boss::Dhuum as u16, + +    // Wing 6 +    ConjuredAmalgamate = Boss::ConjuredAmalgamate as u16, +    TwinLargos = Boss::Nikare as u16, +    Qadim = Boss::Qadim as u16, + +    // Wing 7 +    CardinalAdina = Boss::CardinalAdina as u16, +    CardinalSabir = Boss::CardinalSabir as u16, +    QadimThePeerless = Boss::QadimThePeerless as u16, + +    // 100 CM (Sunqua Peak) +    Ai = Boss::Ai as u16, + +    // 99 CM (Shattered Observatory) +    Skorvald = Boss::Skorvald as u16, +    Artsariiv = Boss::Artsariiv as u16, +    Arkk = Boss::Arkk as u16, + +    // 98 CM (Nightmare) +    MAMA = Boss::MAMA as u16, +    Siax = Boss::Siax as u16, +    Ensolyss = Boss::Ensolyss as u16, + +    // Strike missions +    IcebroodConstruct = Boss::IcebroodConstruct as u16, +    /// Internal name for the "Voice of the Fallen and Claw of the Fallen" strike mission. +    SuperKodanBrothers = Boss::VoiceOfTheFallen as u16, +    FraenirOfJormag = Boss::FraenirOfJormag as u16, +    Boneskinner = Boss::Boneskinner as u16, +    WhisperOfJormag = Boss::WhisperOfJormag as u16, +} + +impl Encounter { +    /// Return all possible bosses that can appear in this encounter. +    /// +    /// This returns the possible boss IDs, not actual agents. For a similar function check +    /// [`Log::boss_agents`][crate::Log::boss_agents]. +    /// +    /// Note that not all of them have to be present in a log, for example if the encounter stopped +    /// before all of them spawned. +    pub fn bosses(self) -> &'static [Boss] { +        match self { +            Encounter::ValeGuardian => &[Boss::ValeGuardian], +            Encounter::Gorseval => &[Boss::Gorseval], +            Encounter::Sabetha => &[Boss::Sabetha], +            Encounter::Slothasor => &[Boss::Slothasor], +            Encounter::Matthias => &[Boss::Matthias], +            Encounter::KeepConstruct => &[Boss::KeepConstruct], +            Encounter::Xera => &[Boss::Xera, Boss::Xera2], +            Encounter::Cairn => &[Boss::Cairn], +            Encounter::MursaatOverseer => &[Boss::MursaatOverseer], +            Encounter::Samarog => &[Boss::Samarog], +            Encounter::Deimos => &[Boss::Deimos], +            Encounter::SoullessHorror => &[Boss::SoullessHorror], +            Encounter::VoiceInTheVoid => &[Boss::Dhuum], +            Encounter::ConjuredAmalgamate => &[Boss::ConjuredAmalgamate], +            Encounter::TwinLargos => &[Boss::Nikare, Boss::Kenut], +            Encounter::Qadim => &[Boss::Qadim], +            Encounter::CardinalAdina => &[Boss::CardinalAdina], +            Encounter::CardinalSabir => &[Boss::CardinalSabir], +            Encounter::QadimThePeerless => &[Boss::QadimThePeerless], +            Encounter::Ai => &[Boss::Ai], +            Encounter::Skorvald => &[Boss::Skorvald], +            Encounter::Artsariiv => &[Boss::Artsariiv], +            Encounter::Arkk => &[Boss::Arkk], +            Encounter::MAMA => &[Boss::MAMA], +            Encounter::Siax => &[Boss::Siax], +            Encounter::Ensolyss => &[Boss::Ensolyss], +            Encounter::IcebroodConstruct => &[Boss::IcebroodConstruct], +            Encounter::SuperKodanBrothers => &[Boss::VoiceOfTheFallen, Boss::ClawOfTheFallen], +            Encounter::FraenirOfJormag => &[Boss::FraenirOfJormag], +            Encounter::Boneskinner => &[Boss::Boneskinner], +            Encounter::WhisperOfJormag => &[Boss::WhisperOfJormag], +        } +    } +} + +/// Error for when converting a string to an encounter fails. +#[derive(Debug, Clone, Hash, PartialEq, Eq, Error)] +#[error("Invalid encounter identifier: {0}")] +pub struct ParseEncounterError(String); + +impl FromStr for Encounter { +    type Err = ParseEncounterError; + +    fn from_str(s: &str) -> Result<Self, Self::Err> { +        // Parsing an encounter is in most cases the same as parsing a boss, as the encounters map +        // 1:1 to a boss. For the special cases where the encounter as such has a specific name +        // (such as Twin Largos), this parses strictly more bosses (so "Kenut" would be parsed as +        // Encounter::TwinLargos, which is fine). The special cases are then added later (so that +        // "Twin Largos" also is parsed as Encounter::TwinLargos). +        if let Ok(boss) = Boss::from_str(s) { +            return Ok(boss.encounter()); +        } +        let lower = s.to_lowercase(); +        match &lower as &str { +            "largos" | "twins" | "largos twins" | "twin largos" => Ok(Encounter::TwinLargos), +            "kodans" | "super kodan brothers" => Ok(Encounter::SuperKodanBrothers), + +            _ => Err(ParseEncounterError(s.to_owned())), +        } +    } +} + +impl Display for Encounter { +    fn fmt(&self, f: &mut Formatter) -> fmt::Result { +        let name = match *self { +            Encounter::ValeGuardian => "Vale Guardian", +            Encounter::Gorseval => "Gorseval", +            Encounter::Sabetha => "Sabetha", +            Encounter::Slothasor => "Slothasor", +            Encounter::Matthias => "Matthias Gabrel", +            Encounter::KeepConstruct => "Keep Construct", +            Encounter::Xera => "Xera", +            Encounter::Cairn => "Cairn the Indomitable", +            Encounter::MursaatOverseer => "Mursaat Overseer", +            Encounter::Samarog => "Samarog", +            Encounter::Deimos => "Deimos", +            Encounter::SoullessHorror => "Soulless Horror", +            Encounter::VoiceInTheVoid => "Voice in the Void", +            Encounter::ConjuredAmalgamate => "Conjured Amalgamate", +            Encounter::TwinLargos => "Twin Largos", +            Encounter::Qadim => "Qadim", +            Encounter::CardinalAdina => "Cardinal Adina", +            Encounter::CardinalSabir => "Cardinal Sabir", +            Encounter::QadimThePeerless => "Qadim the Peerless", +            Encounter::Ai => "Ai Keeper of the Peak", +            Encounter::Skorvald => "Skorvald the Shattered", +            Encounter::Artsariiv => "Artsariiv", +            Encounter::Arkk => "Arkk", +            Encounter::MAMA => "MAMA", +            Encounter::Siax => "Siax the Corrupted", +            Encounter::Ensolyss => "Ensolyss of the Endless Torment", +            Encounter::IcebroodConstruct => "Icebrood Construct", +            Encounter::SuperKodanBrothers => "Super Kodan Brothers", +            Encounter::FraenirOfJormag => "Fraenir of Jormag", +            Encounter::Boneskinner => "Boneskinner", +            Encounter::WhisperOfJormag => "Whisper of Jormag", +        }; +        write!(f, "{}", name) +    } +} + +/// Enum containing all boss IDs. +/// +/// For a high-level event categorization, take a look at the [`Encounter`] enum. The IDs listed +/// here are for a more fine-grained control, e.g. if you specifically need to differentiate +/// between Nikare and Kenut in the Twin Largos encounter. +/// +/// This enum is non-exhaustive to ensure that future bosses can be added without +/// inducing a breaking change.  #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, FromPrimitive)] +#[non_exhaustive] +#[repr(u16)]  pub enum Boss {      // Wing 1 +    /// Vale Guardian, first boss of Spirit Vale. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Vale_Guardian)      ValeGuardian = 0x3C4E, +    /// Gorseval, second boss of Spirit Vale. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Gorseval_the_Multifarious)      Gorseval = 0x3C45, +    /// Sabetha, third boss of Spirit Vale. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Sabetha_the_Saboteur)      Sabetha = 0x3C0F,      // Wing 2 +    /// Slothasor, first boss of Salvation Pass. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Slothasor)      Slothasor = 0x3EFB, +    /// Matthias, third boss of Salvation Pass. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Matthias_Gabrel)      Matthias = 0x3EF3,      // Wing 3 +    /// Keep Construct, second boss of the Stronghold of the Faithful. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Keep_Construct)      KeepConstruct = 0x3F6B, -    /// Xera ID for phase 1. +    /// Xera, third boss of the Stronghold of the Faithful.      /// -    /// This is only half of Xera's ID, as there will be a second agent for the -    /// second phase. This agent will have another ID, see -    /// [`XERA_PHASE2_ID`](constant.XERA_PHASE2_ID.html). +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Xera)      Xera = 0x3F76, +    /// ID for Xera in the second phase. +    /// +    /// The original Xera will despawn, and after the tower phase, a separate spawn will take over. +    /// This new Xera will have [`Boss::Xera2`] as ID. Care needs to be taken when calculating boss +    /// damage on this encounter, as both Xeras have to be taken into account. +    Xera2 = 0x3F9E,      // Wing 4 +    /// Cairn, first boss of the Bastion of the Penitent. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Cairn_the_Indomitable)      Cairn = 0x432A, +    /// Mursaat Overseer, second boss of the Bastion of the Penitent. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Mursaat_Overseer)      MursaatOverseer = 0x4314, +    /// Samarog, third boss of the Bastion of the Penitent. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Samarog)      Samarog = 0x4324, +    /// Deimos, fourth boss of the Bastion of the Penitent. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Deimos)      Deimos = 0x4302,      // Wing 5 +    /// Soulless Horror, first boss of the Hall of Chains. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Soulless_Horror)      SoullessHorror = 0x4D37, +    /// Dhuum, second boss of the Hall of Chains. +    /// +    /// The encounter to this boss is called [Voice in the Void][Encounter::VoiceInTheVoid]. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Dhuum)      Dhuum = 0x4BFA,      // Wing 6 +    /// Conjured Amalgamate, first boss of Mythwright Gambit. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Conjured_Amalgamate)      ConjuredAmalgamate = 0xABC6, -    /// This is the ID of Nikare, as that is what the Twin Largos logs are identified by. +    /// Nikare, part of the [Twin Largos][Encounter::TwinLargos] encounter in Mythwright Gamit. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Nikare) +    Nikare = 0x5271, +    /// Kenut, part of the [Twin Largos][Encounter::TwinLargos] encounter in Mythwright Gamit. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Nikare) +    Kenut = 0x5261, +    /// Qadim, third boss in Mythwright Gambit.      /// -    /// If you want Nikare specifically, consider using [`NIKARE_ID`][NIKARE_ID], and similarly, if -    /// you need Kenut, you can use [`KENUT_ID`][KENUT_ID]. -    LargosTwins = 0x5271, +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Qadim)      Qadim = 0x51C6,      // Wing 7 +    /// Cardinal Adina, one of the first two bosses in the Key of Ahdashim. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Cardinal_Adina)      CardinalAdina = 0x55F6, +    /// Cardinal Sabir, one of the first two bosses in the Key of Ahdashim. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Cardinal_Sabir)      CardinalSabir = 0x55CC, +    /// Qadim the Peerless, third boss in the Key of Ahdashim. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Qadim_the_Peerless)      QadimThePeerless = 0x55F0,      // 100 CM (Sunqua Peak) +    /// Ai, Keeper of the Peak, boss of the Sunqua Peak CM fractal. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Ai,_Keeper_of_the_Peak)      Ai = 0x5AD6,      // 99 CM (Shattered Observatory) +    /// Skorvald the Shattered, first boss in the Shattered Observatory. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Skorvald_the_Shattered)      Skorvald = 0x44E0, +    /// Artsariiv, second boss in the Shattered Observatory CM. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Artsariiv)      Artsariiv = 0x461D, +    /// Arkk, third boss in the Shattered Observatory. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Arkk)      Arkk = 0x455F,      // 98 CM (Nightmare) +    /// MAMA, first boss in the Nightmare CM fractal. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/MAMA)      MAMA = 0x427D, +    /// Siax the Corrupted, second boss in the Nightmare CM fractal. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Siax_the_Corrupted)      Siax = 0x4284, +    /// Ensolyss of the Endless Torment, third boss in the Nightmare CM fractal. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Ensolyss_of_the_Endless_Torment)      Ensolyss = 0x4234,      // Strike missions +    /// Legendary Icebrood Construct, boss of the Shiverpeaks Pass strike mission. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Legendary_Icebrood_Construct)      IcebroodConstruct = 0x568A, -    /// This is the ID of the Voice of the Fallen. +    /// The Voice of the Fallen, part of the Voice of the Fallen and Claw of the Fallen strike +    /// mission.      /// -    /// The strike mission itself contains two bosses, the Voice of the Fallen and the Claw of the -    /// Fallen. Consider using either [`VOICE_OF_THE_FALLEN_ID`][VOICE_OF_THE_FALLEN_ID] or -    /// [`CLAW_OF_THE_FALLEN_ID`][CLAW_OF_THE_FALLEN_ID] if you refer to one of those bosses -    /// specifically. +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Voice_of_the_Fallen)      VoiceOfTheFallen = 0x5747, +    /// The Claw of the Fallen, part of the Voice of the Fallen and Claw of the Fallen strike +    /// mission. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Claw_of_the_Fallen) +    ClawOfTheFallen = 0x57D1, +    /// The Fraenir of Jormag, boss of the Fraenir of Jormag strike mission. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Fraenir_of_Jormag)      FraenirOfJormag = 0x57DC, +    /// The Boneskinner, boss of the Boneskinner strike mission. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Boneskinner)      Boneskinner = 0x57F9, +    /// The Whisper of Jormag, boss of the Whisper of Jormag strike mission. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Whisper_of_Jormag)      WhisperOfJormag = 0x58B7,  } -/// Error for when converting a string to the boss fails. +impl Boss { +    /// Get the encounter ID in which this boss can appear. +    /// +    /// This is the counterpart to [`Encounter::bosses`]. +    pub fn encounter(self) -> Encounter { +        match self { +            Boss::ValeGuardian => Encounter::ValeGuardian, +            Boss::Gorseval => Encounter::Gorseval, +            Boss::Sabetha => Encounter::Sabetha, +            Boss::Slothasor => Encounter::Slothasor, +            Boss::Matthias => Encounter::Matthias, +            Boss::KeepConstruct => Encounter::KeepConstruct, +            Boss::Xera => Encounter::Xera, +            Boss::Xera2 => Encounter::Xera, +            Boss::Cairn => Encounter::Cairn, +            Boss::MursaatOverseer => Encounter::MursaatOverseer, +            Boss::Samarog => Encounter::Samarog, +            Boss::Deimos => Encounter::Deimos, +            Boss::SoullessHorror => Encounter::SoullessHorror, +            Boss::Dhuum => Encounter::VoiceInTheVoid, +            Boss::ConjuredAmalgamate => Encounter::ConjuredAmalgamate, +            Boss::Nikare => Encounter::TwinLargos, +            Boss::Kenut => Encounter::TwinLargos, +            Boss::Qadim => Encounter::Qadim, +            Boss::CardinalAdina => Encounter::CardinalAdina, +            Boss::CardinalSabir => Encounter::CardinalSabir, +            Boss::QadimThePeerless => Encounter::QadimThePeerless, +            Boss::Ai => Encounter::Ai, +            Boss::Skorvald => Encounter::Skorvald, +            Boss::Artsariiv => Encounter::Artsariiv, +            Boss::Arkk => Encounter::Arkk, +            Boss::MAMA => Encounter::MAMA, +            Boss::Siax => Encounter::Siax, +            Boss::Ensolyss => Encounter::Ensolyss, +            Boss::IcebroodConstruct => Encounter::IcebroodConstruct, +            Boss::VoiceOfTheFallen => Encounter::SuperKodanBrothers, +            Boss::ClawOfTheFallen => Encounter::SuperKodanBrothers, +            Boss::FraenirOfJormag => Encounter::FraenirOfJormag, +            Boss::Boneskinner => Encounter::Boneskinner, +            Boss::WhisperOfJormag => Encounter::WhisperOfJormag, +        } +    } +} + +/// Error for when converting a string to an encounter fails.  #[derive(Debug, Clone, Hash, PartialEq, Eq, Error)]  #[error("Invalid boss identifier: {0}")]  pub struct ParseBossError(String); @@ -105,10 +433,11 @@ impl FromStr for Boss {              "deimos" => Ok(Boss::Deimos),              "desmina" | "sh" | "soulless horror" => Ok(Boss::SoullessHorror), -            "dhuum" => Ok(Boss::Dhuum), +            "dhuum" | "voice in the void" => Ok(Boss::Dhuum),              "ca" | "conjured amalgamate" => Ok(Boss::ConjuredAmalgamate), -            "largos" | "twins" | "largos twins" => Ok(Boss::LargosTwins), +            "nikare" => Ok(Boss::Nikare), +            "kenut" => Ok(Boss::Kenut),              "qadim" => Ok(Boss::Qadim),              "adina" | "cardinal adina" => Ok(Boss::CardinalAdina), @@ -126,7 +455,8 @@ impl FromStr for Boss {              "ensolyss" | "ensolyss of the endless torment" => Ok(Boss::Ensolyss),              "icebrood" | "icebrood construct" => Ok(Boss::IcebroodConstruct), -            "kodans" | "super kodan brothers" => Ok(Boss::VoiceOfTheFallen), +            "voice" | "voice of the fallen" => Ok(Boss::VoiceOfTheFallen), +            "claw" | "claw of the fallen" => Ok(Boss::ClawOfTheFallen),              "fraenir" | "fraenir of jormag" => Ok(Boss::FraenirOfJormag),              "boneskinner" => Ok(Boss::Boneskinner),              "whisper" | "whisper of jormag" => Ok(Boss::WhisperOfJormag), @@ -146,6 +476,7 @@ impl Display for Boss {              Boss::Matthias => "Matthias Gabrel",              Boss::KeepConstruct => "Keep Construct",              Boss::Xera => "Xera", +            Boss::Xera2 => "Xera",              Boss::Cairn => "Cairn the Indomitable",              Boss::MursaatOverseer => "Mursaat Overseer",              Boss::Samarog => "Samarog", @@ -153,7 +484,8 @@ impl Display for Boss {              Boss::SoullessHorror => "Soulless Horror",              Boss::Dhuum => "Dhuum",              Boss::ConjuredAmalgamate => "Conjured Amalgamate", -            Boss::LargosTwins => "Twin Largos", +            Boss::Nikare => "Nikare", +            Boss::Kenut => "Kenut",              Boss::Qadim => "Qadim",              Boss::CardinalAdina => "Cardinal Adina",              Boss::CardinalSabir => "Cardinal Sabir", @@ -166,7 +498,8 @@ impl Display for Boss {              Boss::Siax => "Siax the Corrupted",              Boss::Ensolyss => "Ensolyss of the Endless Torment",              Boss::IcebroodConstruct => "Icebrood Construct", -            Boss::VoiceOfTheFallen => "Super Kodan Brothers", +            Boss::VoiceOfTheFallen => "Voice of the Fallen", +            Boss::ClawOfTheFallen => "Claw of the Fallen",              Boss::FraenirOfJormag => "Fraenir of Jormag",              Boss::Boneskinner => "Boneskinner",              Boss::WhisperOfJormag => "Whisper of Jormag", @@ -174,25 +507,6 @@ impl Display for Boss {          write!(f, "{}", name)      }  } - -/// ID for Xera in the second phase. -/// -/// The original Xera will despawn, and after the tower phase, a separate spawn -/// will take over. This new Xera will have this ID. Care needs to be taken when -/// calculating boss damage on this encounter, as both Xeras have to be taken -/// into account. -pub const XERA_PHASE2_ID: u16 = 0x3F9E; - -/// The ID of Nikare in the Twin Largos fight. -pub const NIKARE_ID: u16 = Boss::LargosTwins as u16; -/// The ID of Kenut in the Twin Largos fight. -pub const KENUT_ID: u16 = 21089; - -/// The ID of the Voice of the Fallen. -pub const VOICE_OF_THE_FALLEN_ID: u16 = Boss::VoiceOfTheFallen as u16; -/// The ID of the Claw of the Fallen. -pub const CLAW_OF_THE_FALLEN_ID: u16 = 22481; -  /// Error for when converting a string to a profession fails.  #[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]  #[error("Invalid profession identifier: {0}")] @@ -369,6 +683,119 @@ mod tests {      use super::*;      #[test] +    fn test_encounter_parsing_ok() { +        use Encounter::*; +        let tests: &[(&'static str, Encounter)] = &[ +            ("vg", ValeGuardian), +            ("VG", ValeGuardian), +            ("vale guardian", ValeGuardian), +            ("Vale Guardian", ValeGuardian), +            ("gorse", Gorseval), +            ("Gorse", Gorseval), +            ("gorseval", Gorseval), +            ("Gorseval", Gorseval), +            ("sab", Sabetha), +            ("sabetha", Sabetha), +            ("Sabetha", Sabetha), +            ("sloth", Slothasor), +            ("slothasor", Slothasor), +            ("Slothasor", Slothasor), +            ("matthias", Matthias), +            ("Matthias", Matthias), +            ("kc", KeepConstruct), +            ("KC", KeepConstruct), +            ("keep construct", KeepConstruct), +            ("Keep Construct", KeepConstruct), +            ("xera", Xera), +            ("Xera", Xera), +            ("cairn", Cairn), +            ("Cairn", Cairn), +            ("mo", MursaatOverseer), +            ("MO", MursaatOverseer), +            ("mursaat overseer", MursaatOverseer), +            ("Mursaat Overseer", MursaatOverseer), +            ("samarog", Samarog), +            ("Samarog", Samarog), +            ("deimos", Deimos), +            ("Deimos", Deimos), +            ("sh", SoullessHorror), +            ("soulless horror", SoullessHorror), +            ("desmina", SoullessHorror), +            ("Desmina", SoullessHorror), +            ("dhuum", VoiceInTheVoid), +            ("Dhuum", VoiceInTheVoid), +            ("ca", ConjuredAmalgamate), +            ("conjured amalgamate", ConjuredAmalgamate), +            ("Conjured Amalgamate", ConjuredAmalgamate), +            ("largos", TwinLargos), +            ("twins", TwinLargos), +            ("largos twins", TwinLargos), +            ("qadim", Qadim), +            ("Qadim", Qadim), +            ("adina", CardinalAdina), +            ("cardinal adina", CardinalAdina), +            ("Cardinal Adina", CardinalAdina), +            ("sabir", CardinalSabir), +            ("cardinal sabir", CardinalSabir), +            ("Cardinal Sabir", CardinalSabir), +            ("qadimp", QadimThePeerless), +            ("qadim the peerless", QadimThePeerless), +            ("Qadim The Peerless", QadimThePeerless), +            ("Ai", Ai), +            ("ai", Ai), +            ("skorvald", Skorvald), +            ("Skorvald", Skorvald), +            ("artsariiv", Artsariiv), +            ("Artsariiv", Artsariiv), +            ("arkk", Arkk), +            ("Arkk", Arkk), +            ("mama", MAMA), +            ("MAMA", MAMA), +            ("siax", Siax), +            ("SIAX", Siax), +            ("ensolyss", Ensolyss), +            ("Ensolyss", Ensolyss), +            ("Ensolyss of the Endless Torment", Ensolyss), +            ("icebrood", IcebroodConstruct), +            ("Icebrood Construct", IcebroodConstruct), +            ("fraenir", FraenirOfJormag), +            ("Fraenir of Jormag", FraenirOfJormag), +            ("boneskinner", Boneskinner), +            ("kodans", SuperKodanBrothers), +            ("whisper", WhisperOfJormag), +            ("Whisper of Jormag", WhisperOfJormag), +        ]; + +        for (input, expected) in tests { +            assert_eq!( +                input.parse(), +                Ok(*expected), +                "parsing input {:?} failed", +                input +            ); +        } +    } + +    #[test] +    fn test_encounter_parsing_err() { +        let tests = &[ +            "", +            "vga", +            "VGA", +            "foovg", +            "valeguardian", +            "ValeGuardian", +            "slotha", +            "slot", +            "slothasora", +            "cardinal", +        ]; +        for test in tests { +            assert!(test.parse::<Encounter>().is_err()); +        } +    } + +    #[test]      fn test_boss_parsing_ok() {          use Boss::*;          let tests: &[(&'static str, Boss)] = &[ @@ -413,9 +840,10 @@ mod tests {              ("ca", ConjuredAmalgamate),              ("conjured amalgamate", ConjuredAmalgamate),              ("Conjured Amalgamate", ConjuredAmalgamate), -            ("largos", LargosTwins), -            ("twins", LargosTwins), -            ("largos twins", LargosTwins), +            ("kenut", Kenut), +            ("Kenut", Kenut), +            ("nikare", Nikare), +            ("Nikare", Nikare),              ("qadim", Qadim),              ("Qadim", Qadim),              ("adina", CardinalAdina), @@ -427,6 +855,8 @@ mod tests {              ("qadimp", QadimThePeerless),              ("qadim the peerless", QadimThePeerless),              ("Qadim The Peerless", QadimThePeerless), +            ("Ai", Ai), +            ("ai", Ai),              ("skorvald", Skorvald),              ("Skorvald", Skorvald),              ("artsariiv", Artsariiv), @@ -445,7 +875,12 @@ mod tests {              ("fraenir", FraenirOfJormag),              ("Fraenir of Jormag", FraenirOfJormag),              ("boneskinner", Boneskinner), -            ("kodans", VoiceOfTheFallen), +            ("claw", ClawOfTheFallen), +            ("Claw", ClawOfTheFallen), +            ("Claw of the Fallen", ClawOfTheFallen), +            ("voice", VoiceOfTheFallen), +            ("Voice", VoiceOfTheFallen), +            ("Voice of the Fallen", VoiceOfTheFallen),              ("whisper", WhisperOfJormag),              ("Whisper of Jormag", WhisperOfJormag),          ]; @@ -473,6 +908,10 @@ mod tests {              "slot",              "slothasora",              "cardinal", +            // The following are encounters, make sure we don't parse them as bosses. +            "twins", +            "kodans", +            "twin largos",          ];          for test in tests {              assert!(test.parse::<Boss>().is_err()); @@ -104,7 +104,8 @@ mod processing;  pub use processing::{process, process_file, process_stream, Compression};  pub mod gamedata; -pub use gamedata::{Boss, EliteSpec, Profession}; +use gamedata::Boss; +pub use gamedata::{EliteSpec, Encounter, Profession};  pub mod analyzers;  pub use analyzers::{Analyzer, Outcome}; @@ -758,20 +759,12 @@ impl Log {      /// This correctly returns multiple agents on encounters where multiple      /// agents are needed.      pub fn boss_agents(&self) -> Vec<&Agent> { -        let boss_ids = if self.boss_id == Boss::Xera as u16 { -            vec![self.boss_id, gamedata::XERA_PHASE2_ID] -        } else if self.boss_id == Boss::LargosTwins as u16 { -            vec![gamedata::NIKARE_ID, gamedata::KENUT_ID] -        } else if self.encounter() == Some(Boss::VoiceOfTheFallen) { -            vec![ -                gamedata::VOICE_OF_THE_FALLEN_ID, -                gamedata::CLAW_OF_THE_FALLEN_ID, -            ] -        } else { -            vec![self.boss_id] -        }; +        let bosses = self +            .encounter() +            .map(Encounter::bosses) +            .unwrap_or(&[] as &[_]);          self.npcs() -            .filter(|c| boss_ids.contains(&c.character().id)) +            .filter(|c| bosses.iter().any(|boss| *boss as u16 == c.character().id))              .map(Agent::erase)              .collect()      } @@ -791,16 +784,10 @@ impl Log {      ///      /// Some logs don't have an encounter set or have an ID that is unknown to us (for example, if      /// people set up arcdps with custom IDs). Therefore, this method can only return the encounter -    /// if we know about it in [`Boss`][Boss]. +    /// if we know about it in [`Encounter`].      #[inline] -    pub fn encounter(&self) -> Option<Boss> { -        // Sometimes, encounters of the strike mission "Voice of the Fallen and Claw of the Fallen" -        // are saved with the ID of the Claw and sometimes with the Voice. Therefore, we need to -        // unify those cases. -        if self.boss_id == gamedata::CLAW_OF_THE_FALLEN_ID { -            return Some(Boss::VoiceOfTheFallen); -        } -        Boss::from_u16(self.boss_id) +    pub fn encounter(&self) -> Option<Encounter> { +        Boss::from_u16(self.boss_id).map(Boss::encounter)      }      /// Return an analyzer suitable to analyze the given log. diff --git a/tests/parsing.rs b/tests/parsing.rs index 5da7a1a..08922c9 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -3,7 +3,7 @@  use std::fs::File;  use std::io::BufReader; -use evtclib::{Boss, EliteSpec::*, Profession::*}; +use evtclib::{EliteSpec::*, Encounter, Profession::*};  macro_rules! test {      (name: $name:ident, log: $log:literal, boss: $boss:expr, players: $players:expr,) => { @@ -42,7 +42,7 @@ macro_rules! test {  test! {      name: parse_vale_guardian,      log: "logs/vg-20200421.zevtc", -    boss: Boss::ValeGuardian, +    boss: Encounter::ValeGuardian,      players: &[          (4, ":AliceWindwalker.6238", "Fafnarin Sunseeker", Warrior, Some(Berserker)),          (4, ":Gellalli.6580", "Germi N", Revenant, Some(Renegade)), @@ -60,7 +60,7 @@ test! {  test! {      name: parse_gorseval,      log: "logs/gorseval-20200421.zevtc", -    boss: Boss::Gorseval, +    boss: Encounter::Gorseval,      players: &[          (4, ":AliceWindwalker.6238", "Fafnarin Sunseeker", Warrior, Some(Berserker)),          (4, ":Gellalli.6580", "Germi N", Revenant, Some(Renegade)), @@ -78,7 +78,7 @@ test! {  test! {      name: parse_sabetha,      log: "logs/sabetha-20200421.zevtc", -    boss: Boss::Sabetha, +    boss: Encounter::Sabetha,      players: &[          (4, ":AliceWindwalker.6238", "Fafnarin Sunseeker", Warrior, Some(Berserker)),          (4, ":Gellalli.6580", "Germi N", Revenant, Some(Renegade)), @@ -98,7 +98,7 @@ test! {  test! {      name: parse_slothasor,      log: "logs/slothasor-20200420.zevtc", -    boss: Boss::Slothasor, +    boss: Encounter::Slothasor,      players: &[          (2, ":Basafrass.4138", "Miss Mary J", Guardian, Some(Dragonhunter)),          (2, ":Gellalli.6580", "Gellalli V", Guardian, Some(Dragonhunter)), @@ -116,7 +116,7 @@ test! {  test! {      name: parse_matthias,      log: "logs/matthias-20200421.zevtc", -    boss: Boss::Matthias, +    boss: Encounter::Matthias,      players: &[          (2, ":Basafrass.4138", "Miss Mary J", Guardian, Some(Dragonhunter)),          (2, ":Gellalli.6580", "Germi N", Revenant, Some(Renegade)), @@ -136,7 +136,7 @@ test! {  test! {      name: parse_keep_construct,      log: "logs/kc-20200426.zevtc", -    boss: Boss::KeepConstruct, +    boss: Encounter::KeepConstruct,      players: &[          (3, ":Bomaga.2106", "Krupniczek", Guardian, Some(Dragonhunter)),          (3, ":Buddy Christ.1758", "Block Buddy", Guardian, Some(Dragonhunter)), @@ -154,7 +154,7 @@ test! {  test! {      name: parse_xera,      log: "logs/xera-20200415.zevtc", -    boss: Boss::Xera, +    boss: Encounter::Xera,      players: &[          (2, ":Marcoliveira.7526", "Flamed Horns", Guardian, Some(Dragonhunter)),          (2, ":Marvin.4612", "Necro Rp", Necromancer, Some(Reaper)), @@ -174,7 +174,7 @@ test! {  test! {      name: parse_cairn,      log: "logs/cairn-20200426.zevtc", -    boss: Boss::Cairn, +    boss: Encounter::Cairn,      players: &[          (3, ":Bomaga.2106", "Krupniczek", Guardian, Some(Dragonhunter)),          (3, ":Buddy Christ.1758", "Block Buddy", Guardian, Some(Firebrand)), @@ -192,7 +192,7 @@ test! {  test! {      name: parse_mursaat_overseer,      log: "logs/mo-20200426.zevtc", -    boss: Boss::MursaatOverseer, +    boss: Encounter::MursaatOverseer,      players: &[          (3, ":Bomaga.2106", "Krupniczek", Guardian, Some(Dragonhunter)),          (3, ":Buddy Christ.1758", "Block Buddy", Guardian, Some(Dragonhunter)), @@ -210,7 +210,7 @@ test! {  test! {      name: parse_samarog,      log: "logs/samarog-20200426.zevtc", -    boss: Boss::Samarog, +    boss: Encounter::Samarog,      players: &[          (3, ":Bomaga.2106", "Krupniczek", Guardian, Some(Dragonhunter)),          (3, ":Buddy Christ.1758", "Block Buddy", Guardian, Some(Dragonhunter)), @@ -228,7 +228,7 @@ test! {  test! {      name: parse_deimos,      log: "logs/deimos-20200428.zevtc", -    boss: Boss::Deimos, +    boss: Encounter::Deimos,      players: &[          (2, ":CrusaderCody.6935", "Cody Quickfire", Guardian, Some(Firebrand)),          (2, ":Mrperfect.5213", "Hanna Kowalski", Revenant, Some(Renegade)), @@ -248,7 +248,7 @@ test! {  test! {      name: parse_desmina,      log: "logs/desmina-20200425.zevtc", -    boss: Boss::SoullessHorror, +    boss: Encounter::SoullessHorror,      players: &[          (3, ":AliceWindwalker.6238", "Fafnarin Sunseeker", Warrior, Some(Berserker)),          (3, ":Dunje.4863", "Godric Gobbledygook", Mesmer, Some(Chronomancer)), @@ -266,7 +266,7 @@ test! {  test! {      name: parse_dhuum,      log: "logs/dhuum-20200428.zevtc", -    boss: Boss::Dhuum, +    boss: Encounter::VoiceInTheVoid,      players: &[          (1, ":DaZzius.4753", "Amestye Aëther", Mesmer, Some(Chronomancer)),          (1, ":Dunje.4863", "Maho Shiina", Revenant, Some(Renegade)), @@ -286,7 +286,7 @@ test! {  test! {      name: parse_conjured_amalgamate,      log: "logs/ca-20200426.zevtc", -    boss: Boss::ConjuredAmalgamate, +    boss: Encounter::ConjuredAmalgamate,      players: &[          (3, ":Admiral Aka Inu.4962", "Großadmiral Aka Inu", Warrior, Some(Berserker)),          (3, ":Dunje.4863", "Irodo", Elementalist, Some(Weaver)), @@ -304,7 +304,7 @@ test! {  test! {      name: parse_largos_twins,      log: "logs/largos-20200426.zevtc", -    boss: Boss::LargosTwins, +    boss: Encounter::TwinLargos,      players: &[          (3, ":Cyen Lazarus.4170", "Cyen Blackarrow", Ranger, Some(Druid)),          (3, ":Dunje.4863", "Godric Gobbledygook", Mesmer, Some(Mirage)), @@ -322,7 +322,7 @@ test! {  test! {      name: parse_qadim,      log: "logs/qadim-20200427.zevtc", -    boss: Boss::Qadim, +    boss: Encounter::Qadim,      players: &[          (3, ":Cyen Lazarus.4170", "Cyen Blackarrow", Ranger, Some(Druid)),          (3, ":Lopoeo.1594", "Glücklich Und Satt", Mesmer, Some(Chronomancer)), @@ -342,7 +342,7 @@ test! {  test! {      name: parse_adina,      log: "logs/adina-20200427.zevtc", -    boss: Boss::CardinalAdina, +    boss: Encounter::CardinalAdina,      players: &[          (3, ":Arkady.3768", "Just Pakly", Engineer, Some(Holosmith)),          (3, ":Dunje.4863", "Peter Party", Ranger, Some(Soulbeast)), @@ -360,7 +360,7 @@ test! {  test! {      name: parse_sabir,      log: "logs/sabir-20200427.zevtc", -    boss: Boss::CardinalSabir, +    boss: Encounter::CardinalSabir,      players: &[          (3, ":Arkady.3768", "Just Pakly", Engineer, Some(Holosmith)),          (3, ":Dunje.4863", "Emma Hydes", Elementalist, Some(Weaver)), @@ -378,7 +378,7 @@ test! {  test! {      name: parse_qadim_the_peerless,      log: "logs/qadimp-20200427.zevtc", -    boss: Boss::QadimThePeerless, +    boss: Encounter::QadimThePeerless,      players: &[          (3, ":AliceWindwalker.6238", "Fafnarin Sunseeker", Warrior, Some(Berserker)),          (3, ":Arkady.3768", "Just Pakly", Engineer, Some(Holosmith)), @@ -398,7 +398,7 @@ test! {  test! {      name: parse_ai,      log: "logs/ai-20200922.zevtc", -    boss: Boss::Ai, +    boss: Encounter::Ai,      players: &[          (0, ":Dunje.4863", "Padme Amidada", Guardian, Some(Firebrand)),          (0, ":Speeaaakmaan.8974", "Damage Modifiers", Guardian, Some(Firebrand)), @@ -413,7 +413,7 @@ test! {  test! {      name: parse_skorvald,      log: "logs/skorvald-20200427.zevtc", -    boss: Boss::Skorvald, +    boss: Encounter::Skorvald,      players: &[          (0, ":Dunje.4863", "Jane Whiskerlisp", Revenant, Some(Renegade)),          (0, ":Gellalli.6580", "Germi X", Ranger, Some(Soulbeast)), @@ -426,7 +426,7 @@ test! {  test! {      name: parse_artsariiv,      log: "logs/artsariiv-20200427.zevtc", -    boss: Boss::Artsariiv, +    boss: Encounter::Artsariiv,      players: &[          (0, ":Dunje.4863", "Jane Whiskerlisp", Revenant, Some(Renegade)),          (0, ":Gellalli.6580", "Germi X", Ranger, Some(Soulbeast)), @@ -439,7 +439,7 @@ test! {  test! {      name: parse_arkk,      log: "logs/arkk-20200427.zevtc", -    boss: Boss::Arkk, +    boss: Encounter::Arkk,      players: &[          (0, ":Dunje.4863", "Jane Whiskerlisp", Revenant, Some(Renegade)),          (0, ":Gellalli.6580", "Germi X", Ranger, Some(Soulbeast)), @@ -454,7 +454,7 @@ test! {  test! {      name: parse_mama,      log: "logs/mama-20200427.zevtc", -    boss: Boss::MAMA, +    boss: Encounter::MAMA,      players: &[          (0, ":Dunje.4863", "Jane Whiskerlisp", Revenant, Some(Renegade)),          (0, ":Gellalli.6580", "Germi X", Ranger, Some(Soulbeast)), @@ -467,7 +467,7 @@ test! {  test! {      name: parse_siax,      log: "logs/siax-20200427.zevtc", -    boss: Boss::Siax, +    boss: Encounter::Siax,      players: &[          (0, ":Dunje.4863", "Jane Whiskerlisp", Revenant, Some(Renegade)),          (0, ":Gellalli.6580", "Germi X", Ranger, Some(Soulbeast)), @@ -480,7 +480,7 @@ test! {  test! {      name: parse_ensolyss,      log: "logs/ensolyss-20200427.zevtc", -    boss: Boss::Ensolyss, +    boss: Encounter::Ensolyss,      players: &[          (0, ":Dunje.4863", "Jane Whiskerlisp", Revenant, Some(Renegade)),          (0, ":Gellalli.6580", "Germi X", Ranger, Some(Soulbeast)), @@ -495,7 +495,7 @@ test! {  test! {      name: parse_icebrood,      log: "logs/icebrood-20200424.zevtc", -    boss: Boss::IcebroodConstruct, +    boss: Encounter::IcebroodConstruct,      players: &[          (3, ":Dunje.4863", "Thank You Exorcist", Necromancer, Some(Reaper)),          (3, ":Speeaaakmaan.8974", "Damage Modifiers", Guardian, Some(Firebrand)), @@ -513,7 +513,7 @@ test! {  test! {      name: parse_kodan_brothers,      log: "logs/kodans-20200424.zevtc", -    boss: Boss::VoiceOfTheFallen, +    boss: Encounter::SuperKodanBrothers,      players: &[          (3, ":Gellalli.6580", "Germi J", Necromancer, Some(Scourge)),          (3, ":Speeaaakmaan.8974", "Damage Modifiers", Guardian, Some(Firebrand)), @@ -531,7 +531,7 @@ test! {  test! {      name: parse_fraenir_of_jormag,      log: "logs/fraenir-20200424.zevtc", -    boss: Boss::FraenirOfJormag, +    boss: Encounter::FraenirOfJormag,      players: &[          (3, ":Dunje.4863", "Thank You Exorcist", Necromancer, Some(Reaper)),          (3, ":Speeaaakmaan.8974", "Damage Modifiers", Guardian, Some(Firebrand)), @@ -549,7 +549,7 @@ test! {  test! {      name: parse_boneskinner,      log: "logs/boneskinner-20200424.zevtc", -    boss: Boss::Boneskinner, +    boss: Encounter::Boneskinner,      players: &[          (3, ":Gellalli.6580", "Germi J", Necromancer, Some(Scourge)),          (3, ":Speeaaakmaan.8974", "Damage Modifiers", Guardian, Some(Firebrand)), @@ -567,7 +567,7 @@ test! {  test! {      name: parse_whisper_of_jormag,      log: "logs/whisper-20200424.zevtc", -    boss: Boss::WhisperOfJormag, +    boss: Encounter::WhisperOfJormag,      players: &[          (3, ":Gellalli.6580", "Germi J", Necromancer, Some(Scourge)),          (3, ":Speeaaakmaan.8974", "Damage Modifiers", Guardian, Some(Firebrand)), @@ -587,7 +587,7 @@ test! {  test! {      name: parse_old_cairn_log,      log: "logs/old-cairn-20180321.evtc.zip", -    boss: Boss::Cairn, +    boss: Encounter::Cairn,      players: &[          (1, ":Medejz.1679", "Nuerha", Guardian, Some(Firebrand)),          (1, ":ONEVA.5860", "Berserkoala", Revenant, Some(Renegade)), | 
