diff options
author | Daniel Schadt <kingdread@gmx.de> | 2018-04-24 20:44:32 +0200 |
---|---|---|
committer | Daniel Schadt <kingdread@gmx.de> | 2018-04-24 20:44:32 +0200 |
commit | 4ea20d5a7d75c77a36bd4baf768226e3089b48ae (patch) | |
tree | 22426e22d9e0a3346ddbaab37a356f14ef7d86c7 /src | |
parent | c6cd5dfef5ca037862c79f7abe460e1b88c8dab4 (diff) | |
download | evtclib-4ea20d5a7d75c77a36bd4baf768226e3089b48ae.tar.gz evtclib-4ea20d5a7d75c77a36bd4baf768226e3089b48ae.tar.bz2 evtclib-4ea20d5a7d75c77a36bd4baf768226e3089b48ae.zip |
basic work on statistics calculation
Diffstat (limited to 'src')
-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; + } +} |