diff options
| -rw-r--r-- | src/lib.rs | 33 | ||||
| -rw-r--r-- | src/main.rs | 44 | ||||
| -rw-r--r-- | src/statistics.rs | 170 | 
3 files changed, 239 insertions, 8 deletions
| @@ -29,6 +29,8 @@ pub mod raw;  mod event;  pub use event::{Event, EventKind}; +pub mod statistics; +  /// A macro that returns `true` when the given expression matches the pattern.  ///  /// ```rust @@ -41,7 +43,13 @@ macro_rules! matches {              $($pattern)|+ => true,              _ => false,          } -    ) +    ); +    ($expression:expr, $pattern:pat if $condi:expr) => ( +        match $expression { +            $pattern if $condi => true, +            _ => false, +        } +    );  }  quick_error! { @@ -99,6 +107,7 @@ pub struct Agent {  pub struct Log {      agents: Vec<Agent>,      events: Vec<Event>, +    boss_id: u16,  }  impl Log { @@ -117,6 +126,15 @@ impl Log {          self.agents.iter().find(|a| a.instance_id == instance_id)      } +    /// Return the master agent of the given agent. +    /// +    /// * `addr` - The address of the agent which to get the master for. +    pub fn master_agent(&self, addr: u64) -> Option<&Agent> { +        self.agent_by_addr(addr) +            .and_then(|a| a.master_agent) +            .and_then(|a| self.agent_by_addr(a)) +    } +      /// Return an iterator over all agents that represent player characters.      pub fn players(&self) -> impl Iterator<Item = &Agent> {          self.agents @@ -131,6 +149,13 @@ impl Log {              .filter(|a| matches!(a.kind, AgentKind::Character(_)))      } +    /// Return the boss agent. +    pub fn boss(&self) -> &Agent { +        self.npcs() +            .find(|n| matches!(n.kind, AgentKind::Character(x) if x == self.boss_id)) +            .expect("Boss has no agent!") +    } +      /// Return all events present in this log.      pub fn events(&self) -> &[Event] {          &self.events @@ -148,7 +173,11 @@ pub fn process(data: &raw::Evtc) -> Result<Log, EvtcError> {      let events = data.events.iter().filter_map(Event::from_raw).collect(); -    Ok(Log { agents, events }) +    Ok(Log { +        agents, +        events, +        boss_id: data.header.combat_id, +    })  }  fn setup_agents(data: &raw::Evtc) -> Result<Vec<Agent>, EvtcError> { diff --git a/src/main.rs b/src/main.rs index 94a8abf..c560143 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,8 @@ extern crate evtclib;  use byteorder::{ReadBytesExt, BE, LE};  use std::fs::File; -use std::io::BufReader;  use std::collections::HashMap; +use std::io::BufReader;  // My addr: 5480786193115521456  // My instid: 624 @@ -15,7 +15,7 @@ pub fn main() -> Result<(), evtclib::raw::parser::ParseError> {      let mut f = BufReader::new(File::open("material/Samarog.evtc")?);      let result = evtclib::raw::parse_file(&mut f)?; -/* +    /*      for agent in result.agents.iter().filter(|a| a.is_player()) {          println!("Agent: {:?}", agent);      } @@ -75,16 +75,48 @@ pub fn main() -> Result<(), evtclib::raw::parser::ParseError> {          } else {              println!("Failed: {:#?}", event);              failed += 1; -            continue +            continue;          }; -        match &shiny.kind { -            &EventKind::Physical { source_agent_addr: src, damage: dmg, .. } if src == 5480786193115521456 => { count += 1; damage += dmg as u64; }, -            &EventKind::ConditionTick { source_agent_addr: src, damage: dmg, .. } if src == 5480786193115521456 => { count += 1; damage += dmg as u64; }, +        match shiny.kind { +            EventKind::Physical { +                source_agent_addr: src, +                damage: dmg, +                .. +            } if src == 5480786193115521456 => +            { +                count += 1; +                damage += dmg as u64; +            } +            EventKind::ConditionTick { +                source_agent_addr: src, +                damage: dmg, +                .. +            } if src == 5480786193115521456 => +            { +                count += 1; +                damage += dmg as u64; +            } +              _ => (),          }      }      println!("Count: {}, Damage: {}", count, damage);      println!("Failed events: {}", failed); +    let processed = evtclib::process(&result).unwrap(); +    //println!("Me: {:#?}", processed.agent_by_addr(5480786193115521456)); +    let stats = evtclib::statistics::calculate(&processed).unwrap(); +    println!("{:#?}", stats); +    let mine = stats.agent_stats.get(&5480786193115521456).unwrap(); +    println!("Mine: {:#?}", mine); +    println!( +        "DPS: {:#?}", +        mine.total_damage.total_damage / (mine.combat_time() / 1000) +    ); +    println!( +        "Boss DPS: {:#?}", +        mine.boss_damage.total_damage / (mine.combat_time() / 1000) +    ); +      Ok(())  } diff --git a/src/statistics.rs b/src/statistics.rs new file mode 100644 index 0000000..af208ce --- /dev/null +++ b/src/statistics.rs @@ -0,0 +1,170 @@ +//! This module aids in the creation of actual boss statistics. +use super::*; +use std::collections::HashMap; + +pub type StatResult<T> = Result<T, StatError>; + +quick_error! { +    #[derive(Clone, Debug)] +    pub enum StatError { +    } +} + +/// A struct containing the calculated statistics for the log. +#[derive(Clone, Debug)] +pub struct Statistics { +    /// 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 { +    /// Damage done per target during the fight. +    /// +    /// Maps from target address to the damage done to this target. +    pub per_target_damage: HashMap<u64, DamageStats>, +    /// Total damage dealt during the fight. +    pub total_damage: DamageStats, +    /// Damage directed to the boss. +    pub boss_damage: DamageStats, +    /// 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 +    } +} + +/// Damage statistics for a given target. +#[derive(Debug, Clone, Copy, Default)] +pub struct DamageStats { +    /// The total damage of the player, including all minions/pets/... +    pub total_damage: u64, +    /// The condition damage that the player dealt. +    pub condition_damage: u64, +    /// The power damage that the player dealt. +    pub power_damage: u64, +    /// The damage that was done by minions/pets/... +    pub add_damage: u64, +} + +// A support macro to introduce a new block. +// +// Doesn't really require a macro, but it's nicer to look at +//   with! { foo = bar } +// rather than +//   { let foo = bar; ... } +macro_rules! with { +    ($name:ident = $expr:expr => $bl:block) => {{ +        let $name = $expr; +        $bl +    }}; +} + +/// Calculate the statistics for the given log. +pub fn calculate(log: &Log) -> StatResult<Statistics> { +    use super::EventKind::*; + +    let mut agent_stats = HashMap::<u64, AgentStats>::new(); +    let mut log_start_time = 0; + +    fn get_stats(map: &mut HashMap<u64, AgentStats>, source: u64, target: u64) -> &mut DamageStats { +        map.entry(source) +            .or_insert_with(Default::default) +            .per_target_damage +            .entry(target) +            .or_insert_with(Default::default) +    } + +    for event in log.events() { +        match event.kind { +            LogStart { .. } => { +                log_start_time = event.time; +            } + +            EnterCombat { agent_addr, .. } => { +                agent_stats +                    .entry(agent_addr) +                    .or_insert_with(Default::default) +                    .enter_combat = event.time - log_start_time; +            } + +            ExitCombat { agent_addr } => { +                agent_stats +                    .entry(agent_addr) +                    .or_insert_with(Default::default) +                    .exit_combat = event.time - log_start_time; +            } + +            Physical { +                source_agent_addr, +                destination_agent_addr, +                damage, +                .. +            } => { +                with! { stats = get_stats(&mut agent_stats, source_agent_addr, destination_agent_addr) => { +                    stats.total_damage += damage as u64; +                    stats.power_damage += damage as u64; +                }} + +                if let Some(master) = log.master_agent(source_agent_addr) { +                    let master_stats = +                        get_stats(&mut agent_stats, master.addr, destination_agent_addr); +                    master_stats.total_damage += damage as u64; +                    master_stats.add_damage += damage as u64; +                } +            } + +            ConditionTick { +                source_agent_addr, +                destination_agent_addr, +                damage, +                .. +            } => { +                with! { stats = get_stats(&mut agent_stats, source_agent_addr, destination_agent_addr) => { +                    stats.total_damage += damage as u64; +                    stats.condition_damage += damage as u64; +                }} + +                if let Some(master) = log.master_agent(source_agent_addr) { +                    let master_stats = +                        get_stats(&mut agent_stats, master.addr, destination_agent_addr); +                    master_stats.total_damage += damage as u64; +                    master_stats.add_damage += damage as u64; +                } +            } + +            _ => (), +        } +    } + +    let boss = log.boss(); + +    for agent_stat in agent_stats.values_mut() { +        tally_damage(agent_stat); +        agent_stat.boss_damage = agent_stat +            .per_target_damage +            .get(&boss.addr) +            .cloned() +            .unwrap_or_else(Default::default); +    } + +    Ok(Statistics { agent_stats }) +} + +/// Takes the per target damage stats and tallies them up into the total damage +/// stats. +fn tally_damage(stats: &mut AgentStats) { +    for damage in stats.per_target_damage.values() { +        stats.total_damage.total_damage += damage.total_damage; +        stats.total_damage.power_damage += damage.power_damage; +        stats.total_damage.condition_damage += damage.condition_damage; +        stats.total_damage.add_damage += damage.add_damage; +    } +} | 
