diff options
| -rw-r--r-- | src/analyzers/mod.rs | 1 | ||||
| -rw-r--r-- | src/analyzers/raids/mod.rs | 2 | ||||
| -rw-r--r-- | src/analyzers/raids/w5.rs | 88 | ||||
| -rw-r--r-- | src/gamedata.rs | 19 | ||||
| -rw-r--r-- | tests/logs/river-20210412.zevtc | bin | 0 -> 1057561 bytes | |||
| -rw-r--r-- | tests/parsing.rs | 18 | 
6 files changed, 126 insertions, 2 deletions
| diff --git a/src/analyzers/mod.rs b/src/analyzers/mod.rs index 6234a9a..a5f3cbf 100644 --- a/src/analyzers/mod.rs +++ b/src/analyzers/mod.rs @@ -100,6 +100,7 @@ pub fn for_log<'l>(log: &'l Log) -> Option<Box<dyn Analyzer + 'l>> {          Encounter::Deimos => Some(Box::new(raids::Deimos::new(log))),          Encounter::SoullessHorror => Some(Box::new(raids::SoullessHorror::new(log))), +        Encounter::RiverOfSouls => Some(Box::new(raids::RiverOfSouls::new(log))),          Encounter::VoiceInTheVoid => Some(Box::new(raids::Dhuum::new(log))),          Encounter::ConjuredAmalgamate => Some(Box::new(raids::ConjuredAmalgamate::new(log))), diff --git a/src/analyzers/raids/mod.rs b/src/analyzers/raids/mod.rs index 7b636a7..824b957 100644 --- a/src/analyzers/raids/mod.rs +++ b/src/analyzers/raids/mod.rs @@ -16,7 +16,7 @@ mod w4;  pub use w4::{Cairn, Deimos, MursaatOverseer, Samarog};  mod w5; -pub use w5::{Dhuum, SoullessHorror}; +pub use w5::{Dhuum, RiverOfSouls, SoullessHorror};  mod w6;  pub use w6::{ConjuredAmalgamate, Qadim, TwinLargos}; diff --git a/src/analyzers/raids/w5.rs b/src/analyzers/raids/w5.rs index f914031..747bda2 100644 --- a/src/analyzers/raids/w5.rs +++ b/src/analyzers/raids/w5.rs @@ -1,7 +1,7 @@  //! Boss fight analyzers for Wing 5 (Hall of Chains)  use crate::{      analyzers::{helpers, Analyzer, Outcome}, -    EventKind, Log, +    Encounter, EventKind, Log,  };  pub const DESMINA_BUFF_ID: u32 = 47414; @@ -54,6 +54,92 @@ impl<'log> Analyzer for SoullessHorror<'log> {      }  } +/// Analyzer for the River of Souls escort event in Wing 5. +#[derive(Debug, Clone, Copy)] +pub struct RiverOfSouls<'log> { +    log: &'log Log, +} + +impl<'log> RiverOfSouls<'log> { +    pub fn new(log: &'log Log) -> Self { +        RiverOfSouls { log } +    } +} + +impl<'log> Analyzer for RiverOfSouls<'log> { +    fn log(&self) -> &'log Log { +        self.log +    } + +    fn is_cm(&self) -> bool { +        false +    } + +    fn outcome(&self) -> Option<Outcome> { +        const TRASH_IDS: &[u16] = &[0x4d97, 0x4bc7, 0x4d75, 0x4c05, 0x4bc8, 0x4cec]; +        check_reward!(self.log); + +        // First, let's get the Desmina NPC +        let desmina = self +            .log +            .characters() +            .find(|npc| npc.id() == Encounter::RiverOfSouls as u16)?; + +        // We need to see when our friendly Desmina exited combat, because if she didn't, the event +        // failed. +        let exit_combat = self +            .log +            .events() +            .iter() +            .find(|e| matches!(e.kind(), &EventKind::ExitCombat { agent_addr } if agent_addr == desmina.addr())); +        if exit_combat.is_none() { +            return Some(Outcome::Failure); +        } + +        let trash_aware = self +            .log +            .characters() +            .filter(|npc| TRASH_IDS.contains(&npc.id())) +            .map(|npc| npc.last_aware()) +            .filter(|&i| i != u64::MAX) +            .max() +            .unwrap_or(0); + +        let desmina_despawn = self +            .log() +            .events() +            .iter() +            .find(|e| matches!(e.kind(), &EventKind::Despawn { agent_addr } if agent_addr == desmina.addr())); + +        Outcome::from_bool( +            trash_aware != 0 +                && desmina_despawn.is_none() +                // Add some leeway and see if we saw Desmina after all the trash was gone +                && trash_aware + 500 <= desmina.last_aware() +                && some_player_alive(self.log), +        ) +    } +} + +fn some_player_alive(log: &Log) -> bool { +    let deaths_and_dcs = log +        .events() +        .iter() +        .filter_map(|e| match *e.kind() { +            EventKind::Despawn { agent_addr } => Some(agent_addr), +            EventKind::ChangeDead { agent_addr } => Some(agent_addr), +            _ => None, +        }) +        .filter(|&addr| { +            log.agent_by_addr(addr) +                .map(|a| a.kind().is_player()) +                .unwrap_or(false) +        }) +        .count(); + +    deaths_and_dcs < log.players().count() +} +  pub const DHUUM_CM_HEALTH: u64 = 40_000_000;  /// Analyzer for the second fight of Wing 5, Dhuum. diff --git a/src/gamedata.rs b/src/gamedata.rs index 06d3803..3c48b71 100644 --- a/src/gamedata.rs +++ b/src/gamedata.rs @@ -28,6 +28,11 @@ pub enum Encounter {      // Wing 2      Slothasor = Boss::Slothasor as u16, +    /// The "Protect the caged prisoners" event in Salvation Pass. +    /// +    /// Consists of [`Boss::Berg`], [`Boss::Zane`] and [`Boss::Narella`]. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Protect_the_caged_prisoners)      // Berg is the first encounter, which is why the logs are saved as "Berg".      BanditTrio = Boss::Berg as u16,      Matthias = Boss::Matthias as u16, @@ -44,6 +49,10 @@ pub enum Encounter {      // Wing 5      SoullessHorror = Boss::SoullessHorror as u16, +    /// The River of Souls is the Desmina escort event and an encounter that does not have a boss. +    /// +    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Traverse_the_River_of_Souls) +    RiverOfSouls = 0x4D74,      VoiceInTheVoid = Boss::Dhuum as u16,      // Wing 6 @@ -106,6 +115,7 @@ impl Encounter {              Encounter::Samarog => &[Boss::Samarog],              Encounter::Deimos => &[Boss::Deimos],              Encounter::SoullessHorror => &[Boss::SoullessHorror], +            Encounter::RiverOfSouls => &[],              Encounter::VoiceInTheVoid => &[Boss::Dhuum],              Encounter::ConjuredAmalgamate => &[Boss::ConjuredAmalgamate],              Encounter::TwinLargos => &[Boss::Nikare, Boss::Kenut], @@ -143,6 +153,10 @@ impl Encounter {      /// ```      #[inline]      pub fn from_header_id(id: u16) -> Option<Encounter> { +        // For the encounter without boss, we do it manually. +        if id == Encounter::RiverOfSouls as u16 { +            return Some(Encounter::RiverOfSouls); +        }          Boss::from_u16(id).map(Boss::encounter)      }  } @@ -167,6 +181,7 @@ impl FromStr for Encounter {          let lower = s.to_lowercase();          match &lower as &str {              "trio" | "bandit trio" => Ok(Encounter::BanditTrio), +            "river" | "river of souls" => Ok(Encounter::RiverOfSouls),              "largos" | "twins" | "largos twins" | "twin largos" => Ok(Encounter::TwinLargos),              "kodans" | "super kodan brothers" => Ok(Encounter::SuperKodanBrothers), @@ -191,6 +206,7 @@ impl Display for Encounter {              Encounter::Samarog => "Samarog",              Encounter::Deimos => "Deimos",              Encounter::SoullessHorror => "Soulless Horror", +            Encounter::RiverOfSouls => "River of Souls",              Encounter::VoiceInTheVoid => "Voice in the Void",              Encounter::ConjuredAmalgamate => "Conjured Amalgamate",              Encounter::TwinLargos => "Twin Largos", @@ -807,6 +823,9 @@ mod tests {              ("soulless horror", SoullessHorror),              ("desmina", SoullessHorror),              ("Desmina", SoullessHorror), +            ("river", RiverOfSouls), +            ("River", RiverOfSouls), +            ("river of souls", RiverOfSouls),              ("dhuum", VoiceInTheVoid),              ("Dhuum", VoiceInTheVoid),              ("ca", ConjuredAmalgamate), diff --git a/tests/logs/river-20210412.zevtc b/tests/logs/river-20210412.zevtcBinary files differ new file mode 100644 index 0000000..343785b --- /dev/null +++ b/tests/logs/river-20210412.zevtc diff --git a/tests/parsing.rs b/tests/parsing.rs index 415c3e6..d69ef86 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -283,6 +283,24 @@ test! {  }  test! { +    name: parse_river, +    log: "logs/river-20210412.zevtc", +    boss: Encounter::RiverOfSouls, +    players: &[ +        (1, ":Baragos.2384", "Cicadania", Mesmer, Some(Chronomancer)), +        (1, ":Jupp.4570", "Aldwor", Guardian, Some(Firebrand)), +        (2, ":Dunje.4863", "Pallida Howhite", Warrior, Some(Berserker)), +        (2, ":Taniniver BlindDragon.9503", "Dragon Kills You", Necromancer, Some(Scourge)), +        (2, ":neko.9741", "Mordrem Cat", Ranger, Some(Druid)), +        (2, ":Ricola.5183", "Glühstrumpf", Mesmer, Some(Chronomancer)), +        (2, ":Faboss.2534", "Faboss Sensei", Revenant, Some(Renegade)), +        (3, ":Glahs.2549", "Nala", Ranger, Some(Druid)), +        (3, ":xyoz.6710", "Xaphwen", Mesmer, Some(Chronomancer)), +        (3, ":Straimer.1093", "Deepfreeze Myself", Elementalist, Some(Tempest)), +    ], +} + +test! {      name: parse_dhuum,      log: "logs/dhuum-20200428.zevtc",      boss: Encounter::VoiceInTheVoid, | 
