aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/event.rs5
-rw-r--r--src/lib.rs87
-rw-r--r--src/raw/mod.rs9
-rw-r--r--src/raw/parser.rs29
-rw-r--r--src/raw/types.rs3
5 files changed, 123 insertions, 10 deletions
diff --git a/src/event.rs b/src/event.rs
index 0ccbced..6dc6591 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -1,3 +1,6 @@
+//! Event definitions.
+//!
+//! This module contains the different types of events in their high-level form.
use super::raw;
use std::convert::TryFrom;
@@ -19,7 +22,7 @@ pub enum FromRawEventError {
/// A rusty enum for all possible combat events.
///
-/// This makes dealing with `CbtEvent` a bit saner (and safer).
+/// This makes dealing with [`CbtEvent`][raw::CbtEvent] a bit saner (and safer).
#[derive(Clone, Debug, PartialEq)]
pub enum EventKind {
// State change events
diff --git a/src/lib.rs b/src/lib.rs
index b77f3bc..8f7d9d1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,21 +1,58 @@
//! `evtclib` is a crate aiming to provide utility functions to parse and work
//! with `.evtc` reports generated by arcdps.
//!
+//! # About evtc Files
+//!
+//! evtc files are files generated by the (inofficial) arcdps addon to Guild Wars 2. They contain
+//! metadata about a fight in the game, such as the boss's name (if it was a raid or fractal boss),
+//! the participants, and a stripped-down log of the complete fight.
+//!
+//! There are other programs (such as
+//! [GW2-Elite-Insights-Parser](https://github.com/baaron4/GW2-Elite-Insights-Parser/)) and
+//! websites (such as [dps.report](https://dps.report)) which allow you to generate reports from
+//! evtc files.
+//!
+//! A common way to store and distribute evtc files is to zip them to either a `.evtc.zip` (old
+//! way) or a `.zevtc` (new way). evtclib uses [`zip`](https://crates.io/crates/zip) to read them,
+//! prodiving the [`raw::parse_zip`][raw::parse_zip] convenience function.
+//!
+//! # Crate Structure
+//!
+//! The crate consists of two main parts: The [`raw`][raw] parser, which is used to read structured
+//! data from binary input streams, and the higher-level abstrations provided in the root and
+//! [`event`][event] submodules.
+//!
+//! Additionally, there are some defintions (such as IDs for various game items) in the
+//! [`gamedata`][gamedata] module.
+//!
+//! The main structs that you should be dealing with are the [`Log`][Log] and its components, such
+//! as [`Event`][Event] and [`Agent`][Agent].
+//!
//! # Workflow
//!
+//! Currently, there is no convenience function to turn a file into a [`Log`][Log] directly, so you
+//! have to use the [`raw`][raw] submodule to obtain a low-level [`Evtc`][raw::Evtc], and then
+//! convert it to the high-level [`Log`][Log].
+//!
//! ```no_run
-//! # use std::fs::File;
-//! // Open some file for processing
-//! let mut file = File::open("my_log.evtc").unwrap();
+//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
+//! use std::fs::File;
+//! // Open a file for processing
+//! let mut file = File::open("my_log.evtc")?;
//! // Parse the raw content of the file
-//! let raw_log = evtclib::raw::parse_file(&mut file).unwrap();
+//! let raw_log = evtclib::raw::parse_file(&mut file)?;
//! // Process the file to do the nitty-gritty low-level stuff done
-//! let log = evtclib::process(&raw_log).unwrap();
+//! let log = evtclib::process(&raw_log)?;
//! // Do work on the log
+//! for player in log.players() {
+//! println!("Player {} participated!", player.account_name());
+//! }
+//! # Ok(())
+//! # }
//! ```
//!
-//! (Look at the note on "Buffering" in the [parser
-//! module](raw/parser/index.html#buffering))
+//! Make sure to take a look at the note on "Buffering" in the [parser
+//! module](raw/parser/index.html#buffering) in order to increase the speed of your application.
use std::convert::TryFrom;
use std::marker::PhantomData;
@@ -32,14 +69,23 @@ pub use event::{Event, EventKind};
pub mod gamedata;
pub use gamedata::{Boss, EliteSpec, Profession};
+/// Any error that can occur during the processing of evtc files.
#[derive(Error, Debug)]
pub enum EvtcError {
+ /// Generic error for invalid data in the evtc file.
#[error("invalid data has been provided")]
InvalidData,
+ /// The profession id is not known.
+ ///
+ /// The field contains the unknown profession id.
#[error("invalid profession id: {0}")]
InvalidProfession(u32),
+ /// The elite specialization id is not known.
+ ///
+ /// The field contains the unknown elite specialization id.
#[error("invalid elite specialization id: {0}")]
InvalidEliteSpec(u32),
+ /// The file contains invalid utf-8.
#[error("utf8 decoding error: {0}")]
Utf8Error(#[from] std::str::Utf8Error),
}
@@ -95,12 +141,17 @@ impl Player {
/// [Entangle](https://wiki.guildwars2.com/wiki/Entangle) or the other objects in the arena.
#[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)]
pub struct Gadget {
+ /// The id of the gadget.
+ ///
+ /// Note that gadgets do not have true ids and the id is generated "through a combination of
+ /// gadget parameters".
#[get_copy = "pub"]
id: u16,
name: String,
}
impl Gadget {
+ /// The name of the gadget.
pub fn name(&self) -> &str {
&self.name
}
@@ -112,12 +163,14 @@ impl Gadget {
/// friendly characters like Mesmer's clones and illusions, Necromancer minions, and so on.
#[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)]
pub struct Character {
+ /// The id of the character.
#[get_copy = "pub"]
id: u16,
name: String,
}
impl Character {
+ /// The name of the character.
pub fn name(&self) -> &str {
&self.name
}
@@ -144,8 +197,17 @@ impl Character {
/// ```
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum AgentKind {
+ /// The agent is a player.
+ ///
+ /// The player-specific data is in the included [`Player`][Player] struct.
Player(Player),
+ /// The agent is a gadget.
+ ///
+ /// The gadget-specific data is in the included [`Gadget`][Gadget] struct.
Gadget(Gadget),
+ /// The agent is a character.
+ ///
+ /// The character-specific data is in the included [`Character`][Character] struct.
Character(Character),
}
@@ -753,6 +815,17 @@ impl Log {
}
}
+/// Main function to turn a low-level [`Evtc`][raw::Evtc] to a high-level [`Log`][Log].
+///
+/// This function takes an [`Evtc`][raw::Evtc] and does the required type conversions and
+/// pre-processing to get a high-level [`Log`][Log]. This pre-processing includes
+///
+/// * Setting the correct aware times for the agents
+/// * Setting the master agents for each agent
+/// * Converting all events
+///
+/// Note that the structures are quite different, so this function does not consume the given
+/// [`Evtc`][raw::Evtc].
pub fn process(data: &raw::Evtc) -> Result<Log, EvtcError> {
// Prepare "augmented" agents
let mut agents = setup_agents(data)?;
diff --git a/src/raw/mod.rs b/src/raw/mod.rs
index 77a8571..0b17933 100644
--- a/src/raw/mod.rs
+++ b/src/raw/mod.rs
@@ -33,6 +33,15 @@ pub fn parse_zip<R: Read + Seek>(input: R) -> ParseResult<Evtc> {
/// at the first nul byte instead of raising an error.
///
/// If the slice does not end with a nul byte, this function returns `None`.
+///
+/// ```
+/// # use evtclib::raw::cstr_up_to_nul;
+/// # fn doctest() -> Option<()> {
+/// assert_eq!(cstr_up_to_nul(b"foo\0bar\0")?.to_bytes(), b"foo");
+/// # Some(())
+/// # }
+/// # doctest().unwrap();
+/// ```
pub fn cstr_up_to_nul(bytes: &[u8]) -> Option<&CStr> {
let index = bytes.iter().position(|c| *c == 0)?;
CStr::from_bytes_with_nul(&bytes[..index + 1]).ok()
diff --git a/src/raw/parser.rs b/src/raw/parser.rs
index b7d6aad..eaf8e6b 100644
--- a/src/raw/parser.rs
+++ b/src/raw/parser.rs
@@ -62,6 +62,11 @@
//! buffered: cargo run --release 0.22s user 0.04s system 94% cpu 0.275 total
//! raw file: cargo run --release 0.79s user 1.47s system 98% cpu 2.279 total
//! ```
+//!
+//! # Resources
+//!
+//! * [evtc readme](https://www.deltaconnected.com/arcdps/evtc/README.txt)
+//! * [C++ output code](https://www.deltaconnected.com/arcdps/evtc/writeencounter.cpp)
use byteorder::{LittleEndian, ReadBytesExt, LE};
use num_traits::FromPrimitive;
@@ -84,9 +89,12 @@ pub struct Header {
}
/// A completely parsed (raw) EVTC file.
+///
+/// Note that this struct does not yet do any preprocessing of the events. It is simply a
+/// representation of the input file as a structured object.
#[derive(Clone, Debug)]
pub struct Evtc {
- /// The file header values
+ /// The file header values.
pub header: Header,
/// The skill count.
pub skill_count: u32,
@@ -99,27 +107,44 @@ pub struct Evtc {
}
/// A partially-parsed EVTC file, containing everything but the events.
-/// This can speed up parsing for applications which can work with the header.
+///
+/// This can speed up parsing for applications which can work with the header, as the event stream
+/// is the largest chunk of data that has to be parsed.
#[derive(Clone, Debug)]
pub struct PartialEvtc {
+ /// The file header values.
pub header: Header,
+ /// The skill count.
pub skill_count: u32,
+ /// The actual agents.
pub agents: Vec<Agent>,
+ /// The skills.
pub skills: Vec<Skill>,
}
+/// Any error that can occur during parsing.
#[derive(Error, Debug)]
pub enum ParseError {
+ /// The error stems from an underlying input/output error.
#[error("IO error: {0}")]
Io(#[from] io::Error),
+ /// The error is caused by invalid UTF-8 data in the file.
+ ///
+ /// Names in the evtc are expected to be encoded with UTF-8.
#[error("utf8 decoding error: {0}")]
Utf8Error(#[from] std::string::FromUtf8Error),
+ /// A generic error to signal invalid data has been encountered.
#[error("invalid data")]
InvalidData,
+ /// The header is malformed.
+ ///
+ /// This is the error that you get when you try to parse a non-evtc file.
#[error("malformed header")]
MalformedHeader,
+ /// The revision used by the file is not known.
#[error("unknown revision: {0}")]
UnknownRevision(u8),
+ /// The given ZIP archive is invalid.
#[error("invalid archive: {0}")]
InvalidZip(#[from] zip::result::ZipError),
}
diff --git a/src/raw/types.rs b/src/raw/types.rs
index 6d72892..2e1958c 100644
--- a/src/raw/types.rs
+++ b/src/raw/types.rs
@@ -1,3 +1,6 @@
+//! Raw evtc structs.
+//!
+//! This module contains the translated definitions from arcdps's C structs.
use num_derive::FromPrimitive;
use std::{self, fmt};