use num_traits::FromPrimitive;
use thiserror::Error;
pub mod raw;
mod agent;
pub use agent::{Agent, AgentKind, Character, Gadget, Player};
pub mod event;
pub use event::{Event, EventKind};
mod processing;
pub use processing::{process, process_file, process_stream, Compression};
pub mod gamedata;
use gamedata::Boss;
pub use gamedata::{EliteSpec, Encounter, GameMode, Profession};
pub mod analyzers;
pub use analyzers::{Analyzer, Outcome};
#[derive(Error, Debug)]
pub enum EvtcError {
#[error("the file could not be parsed: {0}")]
ParseError(#[from] raw::ParseError),
#[error("invalid data has been provided")]
InvalidData,
#[error("invalid profession id: {0}")]
InvalidProfession(u32),
#[error("invalid elite specialization id: {0}")]
InvalidEliteSpec(u32),
#[error("utf8 decoding error: {0}")]
Utf8Error(#[from] std::str::Utf8Error),
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct Log {
agents: Vec<Agent>,
events: Vec<Event>,
boss_id: u16,
}
impl Log {
#[inline]
pub fn agents(&self) -> &[Agent] {
&self.agents
}
pub fn agent_by_addr(&self, addr: u64) -> Option<&Agent> {
self.agents
.binary_search_by_key(&addr, Agent::addr)
.ok()
.map(|i| &self.agents[i])
}
pub fn agent_by_instance_id(&self, instance_id: u16) -> Option<&Agent> {
self.agents.iter().find(|a| a.instance_id() == instance_id)
}
pub fn master_agent(&self, addr: u64) -> Option<&Agent> {
self.agent_by_addr(addr)
.and_then(|a| a.master_agent())
.and_then(|a| self.agent_by_addr(a))
}
pub fn players(&self) -> impl Iterator<Item = &Agent<Player>> {
self.agents.iter().filter_map(|a| a.as_player())
}
pub fn characters(&self) -> impl Iterator<Item = &Agent<Character>> {
self.agents.iter().filter_map(|a| a.as_character())
}
pub fn gadgets(&self) -> impl Iterator<Item = &Agent<Gadget>> {
self.agents.iter().filter_map(|a| a.as_gadget())
}
pub fn boss(&self) -> &Agent {
self.characters()
.find(|c| c.character().id() == self.boss_id)
.map(Agent::erase)
.expect("Boss has no agent!")
}
pub fn boss_agents(&self) -> Vec<&Agent> {
let bosses = self
.encounter()
.map(Encounter::bosses)
.unwrap_or(&[] as &[_]);
self.characters()
.filter(|c| bosses.iter().any(|boss| *boss as u16 == c.character().id()))
.map(Agent::erase)
.collect()
}
pub fn is_boss(&self, addr: u64) -> bool {
let bosses = self
.encounter()
.map(Encounter::bosses)
.unwrap_or(&[] as &[_]);
let agent = self
.agent_by_addr(addr)
.and_then(Agent::as_character)
.and_then(|c| Boss::from_u16(c.id()));
agent.map(|e| bosses.contains(&e)).unwrap_or(false)
}
#[inline]
pub fn encounter_id(&self) -> u16 {
self.boss_id
}
#[inline]
pub fn encounter(&self) -> Option<Encounter> {
Encounter::from_header_id(self.boss_id)
}
pub fn analyzer<'s>(&'s self) -> Option<Box<dyn Analyzer + 's>> {
analyzers::for_log(self)
}
#[inline]
pub fn events(&self) -> &[Event] {
&self.events
}
pub fn span(&self) -> u64 {
let first = self.events().first().map(Event::time).unwrap_or(0);
let last = self.events().last().map(Event::time).unwrap_or(0);
last - first
}
pub fn is_generic(&self) -> bool {
self.boss_id == 1
}
pub fn game_mode(&self) -> Option<GameMode> {
if self.is_generic() {
Some(GameMode::WvW)
} else {
self.encounter().map(Encounter::game_mode)
}
}
}
impl Log {
pub fn is_cm(&self) -> bool {
self.analyzer().map(|a| a.is_cm()).unwrap_or(false)
}
pub fn local_start_timestamp(&self) -> Option<u32> {
self.events().iter().find_map(|e| {
if let EventKind::LogStart {
local_timestamp, ..
} = e.kind()
{
Some(*local_timestamp)
} else {
None
}
})
}
pub fn local_end_timestamp(&self) -> Option<u32> {
self.events().iter().find_map(|e| {
if let EventKind::LogEnd {
local_timestamp, ..
} = e.kind()
{
Some(*local_timestamp)
} else {
None
}
})
}
pub fn was_rewarded(&self) -> bool {
self.events()
.iter()
.any(|e| matches!(e.kind(), EventKind::Reward { .. }))
}
pub fn errors(&self) -> Vec<&str> {
self.events()
.iter()
.filter_map(|e| {
if let EventKind::Error { ref text } = e.kind() {
Some(text as &str)
} else {
None
}
})
.collect()
}
pub fn build_id(&self) -> Option<u64> {
for event in self.events() {
if let EventKind::Build { build } = event.kind() {
return Some(*build);
}
}
None
}
}