aboutsummaryrefslogtreecommitdiff
path: root/src/statistics/mod.rs
blob: f46f7780aea6fc36e68c7b267038ca7aa289b21f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! This module aids in the creation of actual boss statistics.
use super::*;
use std::collections::HashMap;
use std::error::Error;

pub mod boon;
pub mod damage;
pub mod gamedata;
pub mod math;
pub mod mechanics;
pub mod trackers;

use self::boon::BoonLog;
use self::damage::DamageLog;
use self::mechanics::MechanicLog;
use self::trackers::{RunnableTracker, Tracker};

pub type StatResult<T> = Result<T, StatError>;

quick_error! {
    #[derive(Debug)]
    pub enum StatError {
        TrackerError(err: Box<Error>) {
            description("tracker error")
            display("tracker returned an error: {}", err)
            cause(&**err)
        }
    }
}

macro_rules! try_tracker {
    ($expr:expr) => {
        #[allow(unreachable_code)]
        match $expr {
            Ok(e) => e,
            Err(e) => return Err(StatError::TrackerError(e)),
        }
    };
}

/// A struct containing the calculated statistics for the log.
#[derive(Clone, Debug)]
pub struct Statistics {
    /// The complete damage log.
    pub damage_log: DamageLog,
    /// The complete mechanics log.
    pub mechanic_log: MechanicLog,
    /// A map mapping agent addresses to their stats.
    pub agent_stats: HashMap<u64, AgentStats>,
}

/// A struct describing the agent statistics.
#[derive(Clone, Debug, Default)]
pub struct AgentStats {
    /// 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_log: BoonLog,
    /// 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).
    pub exit_combat: u64,
}

impl AgentStats {
    /// Returns the combat time of this agent in milliseconds.
    pub fn combat_time(&self) -> u64 {
        self.exit_combat - self.enter_combat
    }
}

/// Takes a bunch of trackers and runs them on the given log.
///
/// This method returns "nothing", as the statistics are saved in the trackers.
/// It's the job of the caller to extract them appropriately.
pub fn run_trackers(log: &Log, trackers: &mut [&mut RunnableTracker]) -> StatResult<()> {
    for event in log.events() {
        for mut tracker in trackers.iter_mut() {
            try_tracker!((*tracker).run_feed(event));
        }
    }
    Ok(())
}

/// Calculate the statistics for the given log.
pub fn calculate(log: &Log) -> StatResult<Statistics> {
    let mut agent_stats = HashMap::<u64, AgentStats>::new();

    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();

    let mechanics = gamedata::get_mechanics(log.boss_id);
    let boss_addr = log.boss_agents().into_iter().map(|x| *x.addr()).collect();
    let mut mechanic_tracker = trackers::MechanicTracker::new(boss_addr, mechanics);

    run_trackers(
        log,
        &mut [
            &mut damage_tracker,
            &mut log_start_tracker,
            &mut combat_time_tracker,
            &mut boon_tracker,
            &mut mechanic_tracker,
        ],
    )?;

    let log_start_time = try_tracker!(log_start_tracker.finalize());

    let combat_times = try_tracker!(combat_time_tracker.finalize());
    for (agent_addr, &(enter_time, exit_time)) in &combat_times {
        let agent = agent_stats
            .entry(*agent_addr)
            .or_insert_with(Default::default);
        // XXX: This used to be enter_time - log_start_time, as it makes more
        // sense to have the time relative to the log start instead of the
        // Windows boot time. However, this also means that we need to modify
        // all event times before we do any tracking, as many trackers rely on
        // event.time to track information related to time.
        if enter_time != 0 {
            agent.enter_combat = enter_time;
        } else {
            agent.enter_combat = log_start_time;
        }
        if exit_time != 0 {
            agent.exit_combat = exit_time;
        }
    }

    let boon_logs = try_tracker!(boon_tracker.finalize());
    for (agent_addr, boon_log) in boon_logs {
        let agent = agent_stats
            .entry(agent_addr)
            .or_insert_with(Default::default);
        agent.boon_log = boon_log;
    }

    let damage_log = try_tracker!(damage_tracker.finalize());
    let mechanic_log = try_tracker!(mechanic_tracker.finalize());

    Ok(Statistics {
        damage_log,
        mechanic_log,
        agent_stats,
    })
}