aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib.rs33
-rw-r--r--src/main.rs44
-rw-r--r--src/statistics.rs170
3 files changed, 239 insertions, 8 deletions
diff --git a/src/lib.rs b/src/lib.rs
index ec57570..9d3c150 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;
+ }
+}