diff options
Diffstat (limited to 'src')
| -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 | 
