diff options
| -rw-r--r-- | src/statistics/boon.rs | 8 | ||||
| -rw-r--r-- | src/statistics/mod.rs | 22 | ||||
| -rw-r--r-- | src/statistics/trackers.rs | 119 | 
3 files changed, 146 insertions, 3 deletions
| diff --git a/src/statistics/boon.rs b/src/statistics/boon.rs index 4d0348e..912fdb6 100644 --- a/src/statistics/boon.rs +++ b/src/statistics/boon.rs @@ -107,6 +107,9 @@ impl BoonQueue {      ///      /// * `duration` - The amount of time (in milliseconds) to simulate.      pub fn simulate(&mut self, duration: u64) { +        if duration == 0 { +            return; +        }          let mut remaining = duration;          match self.boon_type {              BoonType::Duration => { @@ -138,6 +141,11 @@ impl BoonQueue {          self.queue.clear();      } +    /// Checks if any stacks are left. +    pub fn is_empty(&self) -> bool { +        self.queue.is_empty() +    } +      /// Calculate when the stacks will have the next visible change.      ///      /// This assumes that the stacks will not be modified during this time. diff --git a/src/statistics/mod.rs b/src/statistics/mod.rs index 1ebb517..efb934f 100644 --- a/src/statistics/mod.rs +++ b/src/statistics/mod.rs @@ -49,6 +49,13 @@ pub struct AgentStats {      pub total_damage: DamageStats,      /// Damage directed to the boss.      pub boss_damage: DamageStats, +    /// Average stacks of boons. +    /// +    /// This also includes conditions. +    /// +    /// For duration-based boons, the average amount of stacks is the same as +    /// the uptime. +    pub boon_averages: HashMap<u16, f64>,      /// Time when the agent has entered combat (millseconds since log start).      pub enter_combat: u64,      /// Time when the agent has left combat (millseconds since log start). @@ -95,6 +102,7 @@ pub fn calculate(log: &Log) -> StatResult<Statistics> {      let mut damage_tracker = trackers::DamageTracker::new(log);      let mut log_start_tracker = trackers::LogStartTracker::new();      let mut combat_time_tracker = trackers::CombatTimeTracker::new(); +    let mut boon_tracker = trackers::BoonTracker::new();      run_trackers(          log, @@ -102,6 +110,7 @@ pub fn calculate(log: &Log) -> StatResult<Statistics> {              &mut damage_tracker,              &mut log_start_tracker,              &mut combat_time_tracker, +            &mut boon_tracker,          ],      )?; @@ -139,6 +148,19 @@ pub fn calculate(log: &Log) -> StatResult<Statistics> {              .unwrap_or_else(Default::default);      } +    let boons = try_tracker!(boon_tracker.finalize()); +    for (agent, boon_map) in &boons { +        let agent = agent_stats.entry(*agent).or_insert_with(Default::default); +        let combat_time = agent.combat_time() as f64; +        if combat_time == 0. { +            continue; +        } +        agent.boon_averages = boon_map +            .iter() +            .map(|(id, area)| (*id, *area as f64 / combat_time)) +            .collect(); +    } +      Ok(Statistics { agent_stats })  } diff --git a/src/statistics/trackers.rs b/src/statistics/trackers.rs index 4f2c871..d80c5ee 100644 --- a/src/statistics/trackers.rs +++ b/src/statistics/trackers.rs @@ -15,6 +15,7 @@ use std::collections::HashMap;  use std::error::Error;  use super::super::{Event, EventKind, Log}; +use super::boon::{BoonQueue, BoonType};  use super::DamageStats;  // A support macro to introduce a new block. @@ -189,9 +190,7 @@ pub struct CombatTimeTracker {  impl CombatTimeTracker {      /// Create a new combat time tracker.      pub fn new() -> CombatTimeTracker { -        CombatTimeTracker { -            times: HashMap::new(), -        } +        Default::default()      }  } @@ -219,3 +218,117 @@ impl Tracker for CombatTimeTracker {          Ok(self.times)      }  } + +/// A tracker that tracks the total "boon area" per agent. +/// +/// The boon area is defined as the amount of stacks multiplied by the time. So +/// 1 stack of Might for 1000 milliseconds equals 1000 "stackmilliseconds" of +/// Might. You can use this boon area to calculate the average amount of stacks +/// by taking the boon area and dividing it by the combat time. +/// +/// Note that this also tracks conditions, because internally, they're handled +/// the same way. +#[derive(Default)] +pub struct BoonTracker { +    boon_areas: HashMap<u64, HashMap<u16, u64>>, +    boon_queues: HashMap<u64, HashMap<u16, BoonQueue>>, +    last_time: u64, +} + +impl BoonTracker { +    const MAX_STACKS: u32 = 25; + +    /// Creates a new boon tracker. +    pub fn new() -> BoonTracker { +        Default::default() +    } + +    /// Updates the internal boon queues by the given amount of milliseconds. +    /// +    /// * `delta_t` - Amount of milliseconds to update. +    fn update_queues(&mut self, delta_t: u64) { +        self.boon_queues +            .values_mut() +            .flat_map(HashMap::values_mut) +            .for_each(|queue| queue.simulate(delta_t)); + +        // Throw away empty boon queues or agents without any boon queue to +        // improve performance +        self.boon_queues +            .values_mut() +            .for_each(|q| q.retain(|_, v| !v.is_empty())); +        self.boon_queues.retain(|_, q| !q.is_empty()); +    } + +    /// Update the internal tracking areas. +    /// +    /// Does not update the boon queues. +    /// +    /// * `delta_t` - Amount of milliseconds that passed. +    fn update_areas(&mut self, delta_t: u64) { +        for (agent, queues) in &self.boon_queues { +            for (buff_id, queue) in queues { +                let current_stacks = queue.current_stacks(); +                let area = self.boon_areas +                    .entry(*agent) +                    .or_insert_with(Default::default) +                    .entry(*buff_id) +                    .or_insert(0); +                *area += current_stacks as u64 * delta_t; +            } +        } +    } + +    /// Get the boon queue for the given agent and buff_id. +    /// +    /// If the queue does not yet exist, create it. +    /// +    /// * `agent` - The agent. +    /// * `buff_id` - The buff (or condition) id. +    fn get_queue(&mut self, agent: u64, buff_id: u16) -> &mut BoonQueue { +        // XXX: Properly differentiate between intensity and duration based +        // boons, otherwise the results will be off. +        self.boon_queues +            .entry(agent) +            .or_insert_with(Default::default) +            .entry(buff_id) +            .or_insert_with(|| BoonQueue::new(Self::MAX_STACKS, BoonType::Intensity)) +    } +} + +impl Tracker for BoonTracker { +    type Stat = HashMap<u64, HashMap<u16, u64>>; +    type Error = !; + +    fn feed(&mut self, event: &Event) -> Result<(), Self::Error> { +        let delta_t = event.time - self.last_time; +        self.update_queues(delta_t); +        self.update_areas(delta_t); +        self.last_time = event.time; + +        match event.kind { +            EventKind::BuffApplication { +                destination_agent_addr, +                buff_id, +                duration, +                .. +            } => { +                self.get_queue(destination_agent_addr, buff_id) +                    .add_stack(duration as u64); +            } + +            _ => (), +        } + +        Ok(()) +    } + +    fn finalize(self) -> Result<Self::Stat, Self::Error> { +        println!("Number of agents: {}", self.boon_queues.len()); +        println!( +            "Number of boon queues: {}", +            self.boon_queues.values().flat_map(|qs| qs.values()).count() +        ); +        Ok(self.boon_areas) +    } +} | 
