aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/event.rs14
-rw-r--r--src/raw/parser.rs101
-rw-r--r--src/raw/types.rs24
-rw-r--r--src/statistics/boon.rs16
-rw-r--r--src/statistics/damage.rs4
-rw-r--r--src/statistics/gamedata.rs16
-rw-r--r--src/statistics/trackers.rs4
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