diff options
Diffstat (limited to 'src/statistics')
-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) + } +} |