diff options
-rw-r--r-- | src/event.rs | 14 | ||||
-rw-r--r-- | src/raw/parser.rs | 101 | ||||
-rw-r--r-- | src/raw/types.rs | 24 | ||||
-rw-r--r-- | src/statistics/boon.rs | 16 | ||||
-rw-r--r-- | src/statistics/damage.rs | 4 | ||||
-rw-r--r-- | src/statistics/gamedata.rs | 16 | ||||
-rw-r--r-- | 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<T: Read>(input: &mut T) -> ParseResult<Header> { 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::<LittleEndian>()?; - // 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<T: Read>(input: &mut T) -> ParseResult<Header> { arcdps_build: build_string, combat_id, agent_count, + revision, }) } @@ -249,10 +255,11 @@ pub fn parse_skill<T: Read>(input: &mut T) -> ParseResult<Skill> { /// Parse all combat events. /// /// * `input` - Input stream. -pub fn parse_events<T: Read>(input: &mut T) -> ParseResult<Vec<CbtEvent>> { +/// * `parser` - The parse function to use. +pub fn parse_events<T: Read>(input: &mut T, parser: fn(&mut T) -> ParseResult<CbtEvent>) -> ParseResult<Vec<CbtEvent>> { 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<T: Read>(input: &mut T) -> ParseResult<Vec<CbtEvent>> { /// Parse a single combat event. /// +/// This works for old combat events, i.e. files with revision == 0. +/// /// * `input` - Input stream. -pub fn parse_event<T: Read>(input: &mut T) -> ParseResult<CbtEvent> { +pub fn parse_event_rev0<T: Read>(input: &mut T) -> ParseResult<CbtEvent> { let time = input.read_u64::<LittleEndian>()?; let src_agent = input.read_u64::<LE>()?; let dst_agent = input.read_u64::<LE>()?; let value = input.read_i32::<LE>()?; let buff_dmg = input.read_i32::<LE>()?; - let overstack_value = input.read_u16::<LE>()?; - let skillid = input.read_u16::<LE>()?; + let overstack_value = input.read_u16::<LE>()? as u32; + let skillid = input.read_u16::<LE>()? as u32; let src_instid = input.read_u16::<LE>()?; let dst_instid = input.read_u16::<LE>()?; let src_master_instid = input.read_u16::<LE>()?; @@ -306,6 +315,7 @@ pub fn parse_event<T: Read>(input: &mut T) -> ParseResult<CbtEvent> { src_instid, dst_instid, src_master_instid, + dst_master_instid: 0, iff, buff, result, @@ -317,9 +327,70 @@ pub fn parse_event<T: Read>(input: &mut T) -> ParseResult<CbtEvent> { 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<T: Read>(input: &mut T) -> ParseResult<CbtEvent> { + let time = input.read_u64::<LittleEndian>()?; + let src_agent = input.read_u64::<LE>()?; + let dst_agent = input.read_u64::<LE>()?; + let value = input.read_i32::<LE>()?; + let buff_dmg = input.read_i32::<LE>()?; + let overstack_value = input.read_u32::<LE>()?; + let skillid = input.read_u32::<LE>()?; + let src_instid = input.read_u16::<LE>()?; + let dst_instid = input.read_u16::<LE>()?; + let src_master_instid = input.read_u16::<LE>()?; + let dst_master_instid = input.read_u16::<LE>()?; + + 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::<LE>()?; + + 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<T: Read>(input: &mut T) -> ParseResult<Evtc> { let agents = parse_agents(input, header.agent_count)?; let skill_count = input.read_u32::<LittleEndian>()?; 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<u64> for Stacks { #[derive(Clone, Default)] pub struct BoonLog { // Keep a separate RecordFunc for each boon. - inner: FnvHashMap<u16, RecordFunc<u64, (), Stacks>>, + inner: FnvHashMap<u32, RecordFunc<u64, (), Stacks>>, } 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<u64, BoonLog>, - boon_queues: FnvHashMap<u64, FnvHashMap<u16, BoonQueue>>, + boon_queues: FnvHashMap<u64, FnvHashMap<u32, BoonQueue>>, 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 |