diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2020-05-08 14:22:22 +0200 | 
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2020-05-08 14:25:29 +0200 | 
| commit | a14ae10762a0dd1b7ef4c6e6293eced7c03d007f (patch) | |
| tree | c22387ffc05a069b54eaeeaf53668eb3bba6bd46 | |
| parent | d8da3812b1884be490333bcc62632ddaec41fd56 (diff) | |
| download | evtclib-a14ae10762a0dd1b7ef4c6e6293eced7c03d007f.tar.gz evtclib-a14ae10762a0dd1b7ef4c6e6293eced7c03d007f.tar.bz2 evtclib-a14ae10762a0dd1b7ef4c6e6293eced7c03d007f.zip | |
add first support for determining CMs
This still needs a bit of work, as some of them are untested (Conjured
Amalgamate, Fractal CMs).
| -rw-r--r-- | src/gamedata.rs | 58 | ||||
| -rw-r--r-- | src/lib.rs | 84 | 
2 files changed, 142 insertions, 0 deletions
| diff --git a/src/gamedata.rs b/src/gamedata.rs index 03942b6..cac6518 100644 --- a/src/gamedata.rs +++ b/src/gamedata.rs @@ -62,6 +62,41 @@ pub enum Boss {      WhisperOfJormag = 0x58B7,  } +impl Boss { +    /// Returns the CM trigger for this boss. +    pub fn cm_trigger(self) -> CmTrigger { +        match self { +            Boss::KeepConstruct => CmTrigger::Unknown, + +            Boss::MursaatOverseer => CmTrigger::HpThreshold(30_000_000), +            Boss::Samarog => CmTrigger::HpThreshold(40_000_000), +            Boss::Deimos => CmTrigger::HpThreshold(42_000_000), + +            Boss::SoullessHorror => CmTrigger::TimeBetweenBuffs(47414, 11_000), +            Boss::Dhuum => CmTrigger::HpThreshold(40_000_000), + +            Boss::ConjuredAmalgamate => CmTrigger::BuffPresent(53_075), +            // This is Nikare's health, as the log is saved with his ID +            Boss::LargosTwins => CmTrigger::HpThreshold(19_200_000), +            Boss::Qadim => CmTrigger::HpThreshold(21_100_000), + +            Boss::CardinalAdina => CmTrigger::HpThreshold(24_800_000), +            Boss::CardinalSabir => CmTrigger::HpThreshold(32_400_000), +            Boss::QadimThePeerless => CmTrigger::HpThreshold(51_000_000), + +            Boss::Skorvald => CmTrigger::HpThreshold(5_551_340), +            Boss::Artsariiv => CmTrigger::Always, +            Boss::Arkk => CmTrigger::Always, + +            Boss::MAMA => CmTrigger::Always, +            Boss::Siax => CmTrigger::Always, +            Boss::Ensolyss => CmTrigger::Always, + +            _ => CmTrigger::None, +        } +    } +} +  /// Error for when converting a string to the boss fails.  #[derive(Debug, Clone, Hash, PartialEq, Eq, Error)]  #[error("Invalid boss identifier: {0}")] @@ -126,6 +161,29 @@ impl FromStr for Boss {  /// into account.  pub const XERA_PHASE2_ID: u16 = 0x3F9E; +/// The trigger of how a boss challenge mote (CM) is determined. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum CmTrigger { +    /// The boss does not have a CM available. +    None, +    /// The boss has a CM available but we cannot determine if it has been activated. +    Unknown, +    /// Logs from this boss always count as having the CM active. +    Always, +    /// The CM is determined by the boss's health being at or above the given threshold. +    /// +    /// This works since most bosses increase their HP pool in the CM variant. +    HpThreshold(u32), +    /// The CM is active if the given buff is present in the log. +    /// +    /// The buff can be either on player or the enemy. +    BuffPresent(u32), +    /// The time between buff applications falls below the given threshold. +    /// +    /// The first number is the buff id, the second number is the time threshold. +    TimeBetweenBuffs(u32, u64), +} +  /// Error for when converting a string to a profession fails.  #[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]  #[error("Invalid profession identifier: {0}")] @@ -66,6 +66,7 @@  //! While there are legitimate use cases for writing/modification support, they are currently not  //! implemented (but might be in a future version). +use std::collections::HashMap;  use std::convert::TryFrom;  use std::marker::PhantomData; @@ -79,6 +80,7 @@ pub mod event;  pub use event::{Event, EventKind};  pub mod gamedata; +use gamedata::CmTrigger;  pub use gamedata::{Boss, EliteSpec, Profession};  /// Any error that can occur during the processing of evtc files. @@ -775,6 +777,56 @@ impl Log {  ///  /// Use those functions only if necessary, and prefer to cache the result if it will be reused!  impl Log { +    /// Check whether the fight was done with challenge mote activated. +    /// +    /// This function always returns `false` if +    /// * The fight was done without CM +    /// * The fight does not have a CM +    /// * We cannot determine whether the CM was active +    /// * The boss is not known +    pub fn is_cm(&self) -> bool { +        let trigger = self +            .encounter() +            .map(Boss::cm_trigger) +            .unwrap_or(CmTrigger::Unknown); +        match trigger { +            CmTrigger::HpThreshold(hp_threshold) => { +                for event in self.events() { +                    if let EventKind::MaxHealthUpdate { +                        agent_addr, +                        max_health, +                    } = *event.kind() +                    { +                        if self.is_boss(agent_addr) && max_health >= hp_threshold as u64 { +                            return true; +                        } +                    } +                } +                false +            } + +            CmTrigger::BuffPresent(wanted_buff_id) => { +                for event in self.events() { +                    if let EventKind::BuffApplication { buff_id, .. } = *event.kind() { +                        if buff_id == wanted_buff_id { +                            return true; +                        } +                    } +                } +                false +            } + +            CmTrigger::TimeBetweenBuffs(buff_id, threshold) => { +                let tbb = time_between_buffs(&self.events, buff_id); +                tbb != 0 && tbb <= threshold +            } + +            CmTrigger::Always => true, + +            CmTrigger::None | CmTrigger::Unknown => false, +        } +    } +      /// Get the timestamp of when the log was started.      ///      /// The returned value is a unix timestamp in the local time zone. @@ -915,3 +967,35 @@ fn set_agent_masters(data: &raw::Evtc, agents: &mut [Agent]) -> Result<(), EvtcE      }      Ok(())  } + +fn time_between_buffs(events: &[Event], wanted_buff_id: u32) -> u64 { +    let mut time_maps: HashMap<u64, Vec<u64>> = HashMap::new(); +    for event in events { +        if let EventKind::BuffApplication { +            destination_agent_addr, +            buff_id, +            .. +        } = event.kind() +        { +            if *buff_id == wanted_buff_id { +                time_maps +                    .entry(*destination_agent_addr) +                    .or_default() +                    .push(event.time()); +            } +        } +    } +    let timestamps = if let Some(ts) = time_maps.values().max_by_key(|v| v.len()) { +        ts +    } else { +        return 0; +    }; +    timestamps +        .iter() +        .zip(timestamps.iter().skip(1)) +        .map(|(a, b)| b - a) +        // Arbitrary limit to filter out duplicated buff application events +        .filter(|x| *x > 50) +        .min() +        .unwrap_or(0) +} | 
