From 786f375dd7cdcc6a92e3c7f4b027826629cd9baf Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Wed, 10 Oct 2018 17:52:46 +0200 Subject: update to latest arcdps This comes with several changes: First, the revision header field is now properly parsed and saved, instead of just being hardcoded to zero. This is the first step in allowing newer log files to be parsed. To accommodate this, the Header struct has been extended with the "revision: u8" field. To be able to parse both formats, the CbtEvent struct has been changed. It is now the unification of both the new struct and the old struct, as the changes are pretty minor and mostly concern the parsing itself. The data types have been adjusted, and two fields have been added. Moving fields around does not concern CbtEvent at all. If the struct diverges more from this in the future, some splitting might be introduced, but for now, the change is pretty transparent to other code. During this process, the structs have lost their [repr(C]) property. It was never used, as the structs were not directly involved in C FFI. It has been a relic of the past from earlier iterations. Finally, the parsing function has been changed from parse_event to parse_event_rev0, with the addition of a parse_event_rev1. parse_events now takes the event parsing function as an additional parameter, and parse_file correctly choses the implementation based on the revision number. --- src/event.rs | 14 +++---- src/raw/parser.rs | 101 +++++++++++++++++++++++++++++++++++++++------ src/raw/types.rs | 24 +++++------ src/statistics/boon.rs | 16 +++++-- src/statistics/damage.rs | 4 +- src/statistics/gamedata.rs | 16 +++---- src/statistics/trackers.rs | 4 +- 7 files changed, 131 insertions(+), 48 deletions(-) diff --git a/src/event.rs b/src/event.rs index f24df69..14365b7 100644 --- a/src/event.rs +++ b/src/event.rs @@ -56,7 +56,7 @@ pub enum EventKind { /// A skill has been used. SkillUse { source_agent_addr: u64, - skill_id: u16, + skill_id: u32, activation: Activation, }, @@ -64,7 +64,7 @@ pub enum EventKind { ConditionTick { source_agent_addr: u64, destination_agent_addr: u64, - condition_id: u16, + condition_id: u32, damage: i32, }, @@ -72,14 +72,14 @@ pub enum EventKind { InvulnTick { source_agent_addr: u64, destination_agent_addr: u64, - condition_id: u16, + condition_id: u32, }, /// Physical damage. Physical { source_agent_addr: u64, destination_agent_addr: u64, - skill_id: u16, + skill_id: u32, damage: i32, result: raw::CbtResult, }, @@ -88,16 +88,16 @@ pub enum EventKind { BuffApplication { source_agent_addr: u64, destination_agent_addr: u64, - buff_id: u16, + buff_id: u32, duration: i32, - overstack: u16, + overstack: u32, }, /// Buff removed. BuffRemove { source_agent_addr: u64, destination_agent_addr: u64, - buff_id: u16, + buff_id: u32, total_duration: i32, longest_stack: i32, removal: raw::CbtBuffRemove, diff --git a/src/raw/parser.rs b/src/raw/parser.rs index 2de867b..a3a1cd5 100644 --- a/src/raw/parser.rs +++ b/src/raw/parser.rs @@ -78,6 +78,8 @@ pub struct Header { pub combat_id: u16, /// Agent count. pub agent_count: u32, + /// Evtc revision + pub revision: u8, } /// A completely parsed (raw) EVTC file. @@ -117,6 +119,10 @@ quick_error! { MalformedHeader { description("malformed header") } + UnknownRevision(rev: u8) { + description("unknown revision") + display("revision number not known: {}", rev) + } InvalidZip(err: ::zip::result::ZipError) { from() description("zip error") @@ -147,17 +153,16 @@ pub fn parse_header(input: &mut T) -> ParseResult
{ input.read_exact(&mut arcdps_build)?; let build_string = String::from_utf8(arcdps_build)?; - // Read zero delimiter - let mut zero = [0]; - input.read_exact(&mut zero)?; - if zero != [0] { - return Err(ParseError::MalformedHeader); - } + // Read revision byte + let mut revision = [0]; + input.read_exact(&mut revision)?; + let revision = revision[0]; // Read combat id. let combat_id = input.read_u16::()?; - // Read zero delimiter again. + // Read zero delimiter. + let mut zero = [0]; input.read_exact(&mut zero)?; if zero != [0] { return Err(ParseError::MalformedHeader); @@ -170,6 +175,7 @@ pub fn parse_header(input: &mut T) -> ParseResult
{ arcdps_build: build_string, combat_id, agent_count, + revision, }) } @@ -249,10 +255,11 @@ pub fn parse_skill(input: &mut T) -> ParseResult { /// Parse all combat events. /// /// * `input` - Input stream. -pub fn parse_events(input: &mut T) -> ParseResult> { +/// * `parser` - The parse function to use. +pub fn parse_events(input: &mut T, parser: fn(&mut T) -> ParseResult) -> ParseResult> { let mut result = Vec::new(); loop { - let event = parse_event(input); + let event = parser(input); match event { Ok(x) => result.push(x), Err(ParseError::Io(ref e)) if e.kind() == ErrorKind::UnexpectedEof => return Ok(result), @@ -263,15 +270,17 @@ pub fn parse_events(input: &mut T) -> ParseResult> { /// Parse a single combat event. /// +/// This works for old combat events, i.e. files with revision == 0. +/// /// * `input` - Input stream. -pub fn parse_event(input: &mut T) -> ParseResult { +pub fn parse_event_rev0(input: &mut T) -> ParseResult { let time = input.read_u64::()?; let src_agent = input.read_u64::()?; let dst_agent = input.read_u64::()?; let value = input.read_i32::()?; let buff_dmg = input.read_i32::()?; - let overstack_value = input.read_u16::()?; - let skillid = input.read_u16::()?; + let overstack_value = input.read_u16::()? as u32; + let skillid = input.read_u16::()? as u32; let src_instid = input.read_u16::()?; let dst_instid = input.read_u16::()?; let src_master_instid = input.read_u16::()?; @@ -306,6 +315,7 @@ pub fn parse_event(input: &mut T) -> ParseResult { src_instid, dst_instid, src_master_instid, + dst_master_instid: 0, iff, buff, result, @@ -317,9 +327,70 @@ pub fn parse_event(input: &mut T) -> ParseResult { is_statechange, is_flanking, is_shields, + is_offcycle: false, }) } +/// Parse a single combat event. +/// +/// This works for new combat events, i.e. files with revision == 1. +/// +/// * `input` - Input stream. +pub fn parse_event_rev1(input: &mut T) -> ParseResult { + let time = input.read_u64::()?; + let src_agent = input.read_u64::()?; + let dst_agent = input.read_u64::()?; + let value = input.read_i32::()?; + let buff_dmg = input.read_i32::()?; + let overstack_value = input.read_u32::()?; + let skillid = input.read_u32::()?; + let src_instid = input.read_u16::()?; + let dst_instid = input.read_u16::()?; + let src_master_instid = input.read_u16::()?; + let dst_master_instid = input.read_u16::()?; + + let iff = IFF::from_u8(input.read_u8()?).unwrap_or(IFF::None); + let buff = input.read_u8()?; + let result = CbtResult::from_u8(input.read_u8()?).unwrap_or(CbtResult::None); + let is_activation = CbtActivation::from_u8(input.read_u8()?).unwrap_or(CbtActivation::None); + let is_buffremove = CbtBuffRemove::from_u8(input.read_u8()?).unwrap_or(CbtBuffRemove::None); + let is_ninety = input.read_u8()? != 0; + let is_fifty = input.read_u8()? != 0; + let is_moving = input.read_u8()? != 0; + let is_statechange = CbtStateChange::from_u8(input.read_u8()?)?; + let is_flanking = input.read_u8()? != 0; + let is_shields = input.read_u8()? != 0; + let is_offcycle = input.read_u8()? != 0; + + // Four more bytes of internal tracking garbage. + input.read_u32::()?; + + Ok(CbtEvent { + time, + src_agent, + dst_agent, + value, + buff_dmg, + overstack_value, + skillid, + src_instid, + dst_instid, + src_master_instid, + dst_master_instid, + iff, + buff, + result, + is_activation, + is_buffremove, + is_ninety, + is_fifty, + is_moving, + is_statechange, + is_flanking, + is_shields, + is_offcycle, + }) +} /// Parse a complete EVTC file. /// /// * `input` - Input stream. @@ -328,7 +399,11 @@ pub fn parse_file(input: &mut T) -> ParseResult { let agents = parse_agents(input, header.agent_count)?; let skill_count = input.read_u32::()?; let skills = parse_skills(input, skill_count)?; - let events = parse_events(input)?; + let events = match header.revision { + 0 => parse_events(input, parse_event_rev0)?, + 1 => parse_events(input, parse_event_rev1)?, + x => return Err(ParseError::UnknownRevision(x)), + }; Ok(Evtc { header, diff --git a/src/raw/types.rs b/src/raw/types.rs index 5d4ee10..fb99708 100644 --- a/src/raw/types.rs +++ b/src/raw/types.rs @@ -1,7 +1,6 @@ use std::{self, fmt}; /// The "friend or foe" enum. -#[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)] pub enum IFF { /// Green vs green, red vs red. @@ -15,7 +14,6 @@ pub enum IFF { } /// Combat result (physical) -#[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)] pub enum CbtResult { /// Good physical hit @@ -43,7 +41,6 @@ pub enum CbtResult { } /// Combat activation -#[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)] pub enum CbtActivation { /// Field is not used in this kind of event. @@ -64,7 +61,6 @@ pub enum CbtActivation { /// /// The referenced fields are of the [`CbtEvent`](struct.CbtEvent.html) /// struct. -#[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)] pub enum CbtStateChange { /// Field is not used in this kind of event. @@ -139,7 +135,6 @@ pub enum CbtStateChange { } /// Combat buff remove type -#[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)] pub enum CbtBuffRemove { /// Field is not used in this kind of event. @@ -157,7 +152,6 @@ pub enum CbtBuffRemove { } /// Custom skill ids -#[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)] pub enum CbtCustomSkill { /// Not custom but important and unnamed. @@ -169,7 +163,6 @@ pub enum CbtCustomSkill { } /// Language -#[repr(C)] #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)] pub enum Language { /// English. @@ -183,7 +176,12 @@ pub enum Language { } /// A combat event. -#[repr(C)] +/// +/// This event combines both the old structure and the new structure. Fields not +/// present in the source are zeroed. When dealing with events, always make sure +/// to check the header.revision tag. +/// +/// For conflicting data types, the bigger one is chosen (e.g. u32 over u16). #[derive(Clone, Debug, PartialEq, Eq)] pub struct CbtEvent { /// System time since Windows was started, in milliseconds. @@ -197,15 +195,17 @@ pub struct CbtEvent { /// Estimated buff damage. Zero on application event. pub buff_dmg: i32, /// Estimated overwritten stack duration for buff application. - pub overstack_value: u16, + pub overstack_value: u32, /// Skill id. - pub skillid: u16, + pub skillid: u32, /// Agent map instance id. pub src_instid: u16, /// Agent map instance id. pub dst_instid: u16, /// Master source agent map instance id if source is a minion/pet. pub src_master_instid: u16, + /// Master destination agent map instance id if destination is a minion/pet. + pub dst_master_instid: u16, pub iff: IFF, /// Buff application, removal or damage event. pub buff: u8, @@ -223,10 +223,11 @@ pub struct CbtEvent { pub is_flanking: bool, /// All or part damage was vs. barrier/shield. pub is_shields: bool, + /// False if buff dmg happened during tick, true otherwise. + pub is_offcycle: bool, } /// An agent. -#[repr(C)] #[derive(Clone)] pub struct Agent { /// Agent id. @@ -290,7 +291,6 @@ impl fmt::Debug for Agent { } /// A skill. -#[repr(C)] #[derive(Clone)] pub struct Skill { /// Skill id. diff --git a/src/statistics/boon.rs b/src/statistics/boon.rs index 0aa5ab2..196bd1d 100644 --- a/src/statistics/boon.rs +++ b/src/statistics/boon.rs @@ -161,6 +161,14 @@ impl BoonQueue { self.backlog = 0; } + /// Cleanse a single stack + pub fn drop_single(&mut self) { + if self.is_empty() { + return; + } + self.queue.pop(); + } + /// Checks if any stacks are left. pub fn is_empty(&self) -> bool { self.queue.is_empty() @@ -247,7 +255,7 @@ impl Mul for Stacks { #[derive(Clone, Default)] pub struct BoonLog { // Keep a separate RecordFunc for each boon. - inner: FnvHashMap>, + inner: FnvHashMap>, } impl BoonLog { @@ -257,7 +265,7 @@ impl BoonLog { } /// Add an event to the boon log. - pub fn log(&mut self, time: u64, boon_id: u16, stacks: u32) { + pub fn log(&mut self, time: u64, boon_id: u32, stacks: u32) { let func = self.inner.entry(boon_id).or_insert_with(Default::default); let current = func.tally(); if current.0 == stacks as i32 { @@ -272,7 +280,7 @@ impl BoonLog { /// * `a` - Start time point. /// * `b` - End time point. /// * `boon_id` - ID of the boon that you want to get the average for. - pub fn average_stacks(&self, a: u64, b: u64, boon_id: u16) -> f32 { + pub fn average_stacks(&self, a: u64, b: u64, boon_id: u32) -> f32 { assert!(b >= a, "timespan is negative?!"); let func = if let Some(f) = self.inner.get(&boon_id) { f @@ -287,7 +295,7 @@ impl BoonLog { /// /// * `x` - Time point. /// * `boon_id` - ID of the boon that you want to get. - pub fn stacks_at(&self, x: u64, boon_id: u16) -> u32 { + pub fn stacks_at(&self, x: u64, boon_id: u32) -> u32 { self.inner .get(&boon_id) .map(|f| f.get(&x)) diff --git a/src/statistics/damage.rs b/src/statistics/damage.rs index 0fcbf9b..0c26a9b 100644 --- a/src/statistics/damage.rs +++ b/src/statistics/damage.rs @@ -14,7 +14,7 @@ pub struct Meta { pub source: u64, pub target: u64, pub kind: DamageType, - pub skill: u16, + pub skill: u32, } /// A small wrapper that wraps a damage number. @@ -54,7 +54,7 @@ impl DamageLog { source: u64, target: u64, kind: DamageType, - skill: u16, + skill: u32, value: u64, ) { self.inner.insert( diff --git a/src/statistics/gamedata.rs b/src/statistics/gamedata.rs index 6f370d0..faa8cb3 100644 --- a/src/statistics/gamedata.rs +++ b/src/statistics/gamedata.rs @@ -35,7 +35,7 @@ pub const XERA_PHASE2_ID: u16 = 0x3F9E; /// * maximum number of stacks /// * boon type (intensity or duration) #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Boon(pub u16, pub &'static str, pub u32, pub BoonType); +pub struct Boon(pub u32, pub &'static str, pub u32, pub BoonType); impl Boon { pub fn create_queue(&self) -> BoonQueue { @@ -83,7 +83,7 @@ pub static BOONS: &[Boon] = &[ Boon(738, "Vulnerability", 25, BoonType::Intensity), ]; -pub fn get_boon(boon_id: u16) -> Option<&'static Boon> { +pub fn get_boon(boon_id: u32) -> Option<&'static Boon> { BOONS.iter().find(|b| b.0 == boon_id) } @@ -91,19 +91,19 @@ pub fn get_boon(boon_id: u16) -> Option<&'static Boon> { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Trigger { /// Triggers when the given boon is applied to the player. - BoonPlayer(u16), + BoonPlayer(u32), /// Triggers when the given boon is applied to the boss. - BoonBoss(u16), + BoonBoss(u32), /// Triggers when the given skill is used by a player. - SkillByPlayer(u16), + SkillByPlayer(u32), /// Triggers when the given skill is used on a player. - SkillOnPlayer(u16), + SkillOnPlayer(u32), /// Triggers when the given boon is stripped from an enemy. - BoonStripped(u16), + BoonStripped(u32), /// Triggers when the given entity spawned. Spawn(u16), /// Triggers when the boss finishes channeling the given skill. - ChannelComplete(u16), + ChannelComplete(u32), } /// Struct describing a boss mechanic. diff --git a/src/statistics/trackers.rs b/src/statistics/trackers.rs index 66a7bf8..98915d2 100644 --- a/src/statistics/trackers.rs +++ b/src/statistics/trackers.rs @@ -230,7 +230,7 @@ impl Tracker for CombatTimeTracker { /// boons defined in `evtclib::statistics::gamedata::BOONS`. pub struct BoonTracker { boon_logs: FnvHashMap, - boon_queues: FnvHashMap>, + boon_queues: FnvHashMap>, last_time: u64, } @@ -284,7 +284,7 @@ impl BoonTracker { /// /// * `agent_addr` - The address of the agent. /// * `buff_id` - The buff (or condition) id. - fn get_queue(&mut self, agent_addr: u64, buff_id: u16) -> Option<&mut BoonQueue> { + fn get_queue(&mut self, agent_addr: u64, buff_id: u32) -> Option<&mut BoonQueue> { use std::collections::hash_map::Entry; let entry = self .boon_queues -- cgit v1.2.3