diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/agent.rs | 732 | ||||
-rw-r--r-- | src/lib.rs | 739 | ||||
-rw-r--r-- | src/processing.rs | 20 |
3 files changed, 750 insertions, 741 deletions
diff --git a/src/agent.rs b/src/agent.rs new file mode 100644 index 0000000..11f81d1 --- /dev/null +++ b/src/agent.rs @@ -0,0 +1,732 @@ +use std::convert::TryFrom; +use std::marker::PhantomData; + +use getset::{CopyGetters, Getters, Setters}; +use num_traits::FromPrimitive; + +use super::{ + gamedata::{EliteSpec, Profession}, + raw, EvtcError, +}; + +/// Player-specific agent data. +/// +/// Player agents are characters controlled by a player and as such, they contain data about the +/// account and character used (name, profession), as well as the squad composition. +/// +/// Note that a `Player` is only the player character itself. Any additional entities that are +/// spawned by the player (clones, illusions, banners, ...) are either a [`Character`][Character] +/// or a [`Gadget`][Gadget]. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)] +pub struct Player { + /// The player's profession. + #[get_copy = "pub"] + profession: Profession, + + /// The player's elite specialization, if any is equipped. + #[get_copy = "pub"] + elite: Option<EliteSpec>, + + character_name: String, + + account_name: String, + + /// The subgroup the player was in. + #[get_copy = "pub"] + subgroup: u8, +} + +impl Player { + /// The player's character name. + pub fn character_name(&self) -> &str { + &self.character_name + } + + /// The player's account name. + /// + /// This includes the leading colon and the 4-digit denominator. + pub fn account_name(&self) -> &str { + &self.account_name + } +} + +/// Gadget-specific agent data. +/// +/// Gadgets are entities that are spawned by certain skills. They are mostly inanimate objects that +/// only exist to achieve a certain skill effect. +/// +/// Examples of this include the [banners](https://wiki.guildwars2.com/wiki/Banner) spawned by +/// Warriors, but also skill effects like the roots created by +/// [Entangle](https://wiki.guildwars2.com/wiki/Entangle) or the other objects in the arena. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[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 + } +} + +/// Character-specific agent data. +/// +/// Characters are NPCs such as the bosses themselves, additional mobs that they spawn, but also +/// friendly characters like Mesmer's clones and illusions, Necromancer minions, and so on. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[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 + } +} + +/// The type of an agent. +/// +/// arcdps differentiates between three types of agents: [`Player`][Player], +/// [`Character`][Character] and [`Gadget`][Gadget]. This enum unifies handling between them by +/// allowing you to pattern match or use one of the accessor methods. +/// +/// The main way to obtain a `AgentKind` is by using the [`.kind()`][Agent::kind] method on an +/// [`Agent`][Agent]. In cases where you already have a [`raw::Agent`][raw::Agent] available, you +/// can also use the [`TryFrom`][TryFrom]/[`TryInto`][std::convert::TryInto] traits to convert a +/// `raw::Agent` or `&raw::Agent` to a `AgentKind`: +/// +/// ```no_run +/// # use evtclib::{AgentKind, raw}; +/// use std::convert::TryInto; +/// // Get a raw::Agent from somewhere +/// let raw_agent: raw::Agent = panic!(); +/// // Convert it +/// let agent: AgentKind = raw_agent.try_into().unwrap(); +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[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), +} + +impl AgentKind { + fn from_raw_character(raw_agent: &raw::Agent) -> Result<Character, EvtcError> { + assert!(raw_agent.is_character()); + let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?; + Ok(Character { + id: raw_agent.prof as u16, + name: name.to_str()?.to_owned(), + }) + } + + fn from_raw_gadget(raw_agent: &raw::Agent) -> Result<Gadget, EvtcError> { + assert!(raw_agent.is_gadget()); + let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?; + Ok(Gadget { + id: raw_agent.prof as u16, + name: name.to_str()?.to_owned(), + }) + } + + fn from_raw_player(raw_agent: &raw::Agent) -> Result<Player, EvtcError> { + assert!(raw_agent.is_player()); + let character_name = raw::cstr_up_to_nul(&raw_agent.name) + .ok_or(EvtcError::InvalidData)? + .to_str()?; + let account_name = raw::cstr_up_to_nul(&raw_agent.name[character_name.len() + 1..]) + .ok_or(EvtcError::InvalidData)? + .to_str()?; + let subgroup = raw_agent.name[character_name.len() + account_name.len() + 2] - b'0'; + let elite = if raw_agent.is_elite == 0 { + None + } else { + Some( + EliteSpec::from_u32(raw_agent.is_elite) + .ok_or(EvtcError::InvalidEliteSpec(raw_agent.is_elite))?, + ) + }; + Ok(Player { + profession: Profession::from_u32(raw_agent.prof) + .ok_or(EvtcError::InvalidProfession(raw_agent.prof))?, + elite, + character_name: character_name.to_owned(), + account_name: account_name.to_owned(), + subgroup, + }) + } + + /// Accesses the inner [`Player`][Player] struct, if available. + pub fn as_player(&self) -> Option<&Player> { + if let AgentKind::Player(ref player) = *self { + Some(player) + } else { + None + } + } + + /// Determines whether this `AgentKind` contains a player. + pub fn is_player(&self) -> bool { + self.as_player().is_some() + } + + /// Accesses the inner [`Gadget`][Gadget] struct, if available. + pub fn as_gadget(&self) -> Option<&Gadget> { + if let AgentKind::Gadget(ref gadget) = *self { + Some(gadget) + } else { + None + } + } + + /// Determines whether this `AgentKind` contains a gadget. + pub fn is_gadget(&self) -> bool { + self.as_gadget().is_some() + } + + /// Accesses the inner [`Character`][Character] struct, if available. + pub fn as_character(&self) -> Option<&Character> { + if let AgentKind::Character(ref character) = *self { + Some(character) + } else { + None + } + } + + /// Determines whether this `AgentKind` contains a character. + pub fn is_character(&self) -> bool { + self.as_character().is_some() + } +} + +impl TryFrom<raw::Agent> for AgentKind { + type Error = EvtcError; + /// Convenience method to avoid manual borrowing. + /// + /// Note that this conversion will consume the agent, so if you plan on re-using it, use the + /// `TryFrom<&raw::Agent>` implementation that works with a reference. + fn try_from(raw_agent: raw::Agent) -> Result<Self, Self::Error> { + Self::try_from(&raw_agent) + } +} + +impl TryFrom<&raw::Agent> for AgentKind { + type Error = EvtcError; + + /// Extract the correct `AgentKind` from the given [raw agent][raw::Agent]. + /// + /// This automatically discerns between player, gadget and characters. + /// + /// Note that in most cases, you probably want to use `Agent::try_from` or even + /// [`process`][process] instead of this function. + fn try_from(raw_agent: &raw::Agent) -> Result<Self, Self::Error> { + if raw_agent.is_character() { + Ok(AgentKind::Character(AgentKind::from_raw_character( + raw_agent, + )?)) + } else if raw_agent.is_gadget() { + Ok(AgentKind::Gadget(AgentKind::from_raw_gadget(raw_agent)?)) + } else if raw_agent.is_player() { + Ok(AgentKind::Player(AgentKind::from_raw_player(raw_agent)?)) + } else { + Err(EvtcError::InvalidData) + } + } +} + +/// An agent. +/// +/// Agents in arcdps are very versatile, as a lot of things end up being an "agent". This includes: +/// * Players +/// * Bosses +/// * Any additional mobs that spawn +/// * Mesmer illusions +/// * Ranger spirits, pets +/// * Guardian spirit weapons +/// * ... +/// +/// Generally, you can divide them into three kinds ([`AgentKind`][AgentKind]): +/// * [`Player`][Player]: All players themselves. +/// * [`Character`][Character]: Non-player mobs, including most bosses, "adds" and player-generated +/// characters. +/// * [`Gadget`][Gadget]: Some additional gadgets, such as ley rifts, continuum split, ... +/// +/// All of these agents share some common fields, which are the ones accessible in `Agent<Kind>`. +/// The kind can be retrieved using [`.kind()`][Agent::kind], which can be matched on. +/// +/// # Obtaining an agent +/// +/// The normal way to obtain the agents is to use the [`.agents()`](Log::agents) method on a +/// [`Log`][Log], or one of the other accessor methods (like [`.players()`][Log::players] or +/// [`.agent_by_addr()`][Log::agent_by_addr]). +/// +/// In the cases where you already have a [`raw::Agent`][raw::Agent] available, you can also +/// convert it to an [`Agent`][Agent] by using the standard +/// [`TryFrom`][TryFrom]/[`TryInto`][std::convert::TryInto] traits: +/// +/// ```no_run +/// # use evtclib::{Agent, raw}; +/// use std::convert::TryInto; +/// let raw_agent: raw::Agent = panic!(); +/// let agent: Agent = raw_agent.try_into().unwrap(); +/// ``` +/// +/// Note that you can convert references as well, so if you plan on re-using the raw agent +/// afterwards, you should opt for `Agent::try_from(&raw_agent)` instead. +/// +/// # The `Kind` parameter +/// +/// The type parameter is not actually used and only exists at the type level. It can be used to +/// tag `Agent`s containing a known kind. For example, `Agent<Player>` implements +/// [`.player()`][Agent::player], which returns a `&Player` directly (instead of a +/// `Option<&Player>`). This works because such tagged `Agent`s can only be constructed (safely) +/// using [`.as_player()`][Agent::as_player], [`.as_gadget()`][Agent::as_gadget] or +/// [`.as_character()`][Agent::as_character]. This is useful since functions like +/// [`Log::players`][Log::players], which already filter only players, don't require the consumer +/// to do another check/pattern match for the right agent kind. +/// +/// The unit type `()` is used to tag `Agent`s which contain an undetermined type, and it is the +/// default if you write `Agent` without any parameters. +/// +/// The downside is that methods which work on `Agent`s theoretically should be generic over +/// `Kind`. An escape hatch is the method [`.erase()`][Agent::erase], which erases the kind +/// information and produces the default `Agent<()>`. Functions/methods that only take `Agent<()>` +/// can therefore be used by any other agent as well. +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[derive(Debug, Clone, Hash, PartialEq, Eq, Getters, CopyGetters, Setters)] +// For the reasoning of #[repr(C)] see Agent::transmute. +#[repr(C)] +pub struct Agent<Kind = ()> { + /// The address of this agent. + /// + /// This is not actually the address of the in-memory Rust object, but rather a serialization + /// detail of arcdps. You should consider this as an opaque number and only compare it to other + /// agent addresses. + #[getset(get_copy = "pub", set = "pub(crate)")] + addr: u64, + + /// The kind of this agent. + #[getset(get = "pub", set = "pub(crate)")] + kind: AgentKind, + + /// The toughness of this agent. + /// + /// This is not an absolute number, but a relative indicator that indicates this agent's + /// toughness relative to the other people in the squad. + /// + /// 0 means lowest toughness, 10 means highest toughness. + #[get_copy = "pub"] + toughness: i16, + + /// The concentration of this agent. + /// + /// This is not an absolute number, but a relative indicator that indicates this agent's + /// concentration relative to the other people in the squad. + /// + /// 0 means lowest concentration, 10 means highest concentration. + #[getset(get_copy = "pub", set = "pub(crate)")] + concentration: i16, + + /// The healing power of this agent. + /// + /// This is not an absolute number, but a relative indicator that indicates this agent's + /// healing power relative to the other people in the squad. + /// + /// 0 means lowest healing power, 10 means highest healing power. + #[getset(get_copy = "pub", set = "pub(crate)")] + healing: i16, + + /// The condition damage of this agent. + /// + /// This is not an absolute number, but a relative indicator that indicates this agent's + /// condition damage relative to the other people in the squad. + /// + /// 0 means lowest condition damage, 10 means highest condition damage. + #[getset(get_copy = "pub", set = "pub(crate)")] + condition: i16, + + /// The instance ID of this agent. + #[getset(get_copy = "pub", set = "pub(crate)")] + instance_id: u16, + + /// The timestamp of the first event entry with this agent. + #[getset(get_copy = "pub", set = "pub(crate)")] + first_aware: u64, + + /// The timestamp of the last event entry with this agent. + #[getset(get_copy = "pub", set = "pub(crate)")] + last_aware: u64, + + /// The master agent's address. + #[getset(get_copy = "pub", set = "pub(crate)")] + master_agent: Option<u64>, + + #[cfg_attr(feature = "serde", serde(skip_serializing))] + phantom_data: PhantomData<Kind>, +} + +// We could derive this, however that would derive Deserialize generically for Agent<T>, where T is +// deserializable. In particular, this would mean that you could deserialize Agent<Character>, +// Agent<Player> and Agent<Gadget> directly, which would not be too bad - the problem is that serde +// has no way of knowing if the serialized agent actually is a character/player/gadget agent, as +// that information only exists on the type level. This meant that you could "coerce" agents into +// the wrong type, safely, using a serialization followed by a deserialization. +// Now this doesn't actually lead to memory unsafety or other bad behaviour, but it could mean that +// the program would panic if you tried to access a non-existing field, e.g. by calling +// Agent<Character>::id() on a non-character agent. +// In order to prevent this, we manually implement Deserialize only for Agent<()>, so that the +// usual conversion functions with the proper checks have to be used. +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Agent { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + use serde::de::{self, MapAccess, Visitor}; + use std::fmt; + + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + Addr, + Kind, + Toughness, + Concentration, + Healing, + Condition, + InstanceId, + FirstAware, + LastAware, + MasterAgent, + }; + + struct AgentVisitor; + + impl<'de> Visitor<'de> for AgentVisitor { + type Value = Agent; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct Agent") + } + + fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<Agent, V::Error> { + let mut addr = None; + let mut kind = None; + let mut toughness = None; + let mut concentration = None; + let mut healing = None; + let mut condition = None; + let mut instance_id = None; + let mut first_aware = None; + let mut last_aware = None; + let mut master_agent = None; + + while let Some(key) = map.next_key()? { + match key { + Field::Addr => { + if addr.is_some() { + return Err(de::Error::duplicate_field("addr")); + } + addr = Some(map.next_value()?); + } + Field::Kind => { + if kind.is_some() { + return Err(de::Error::duplicate_field("kind")); + } + kind = Some(map.next_value()?); + } + Field::Toughness => { + if toughness.is_some() { + return Err(de::Error::duplicate_field("toughness")); + } + toughness = Some(map.next_value()?); + } + Field::Concentration => { + if concentration.is_some() { + return Err(de::Error::duplicate_field("concentration")); + } + concentration = Some(map.next_value()?); + } + Field::Healing => { + if healing.is_some() { + return Err(de::Error::duplicate_field("healing")); + } + healing = Some(map.next_value()?); + } + Field::Condition => { + if condition.is_some() { + return Err(de::Error::duplicate_field("condition")); + } + condition = Some(map.next_value()?); + } + Field::InstanceId => { + if instance_id.is_some() { + return Err(de::Error::duplicate_field("instance_id")); + } + instance_id = Some(map.next_value()?); + } + Field::FirstAware => { + if first_aware.is_some() { + return Err(de::Error::duplicate_field("first_aware")); + } + first_aware = Some(map.next_value()?); + } + Field::LastAware => { + if last_aware.is_some() { + return Err(de::Error::duplicate_field("last_aware")); + } + last_aware = Some(map.next_value()?); + } + Field::MasterAgent => { + if master_agent.is_some() { + return Err(de::Error::duplicate_field("master_agent")); + } + master_agent = Some(map.next_value()?); + } + } + } + + Ok(Agent { + addr: addr.ok_or_else(|| de::Error::missing_field("addr"))?, + kind: kind.ok_or_else(|| de::Error::missing_field("kind"))?, + toughness: toughness.ok_or_else(|| de::Error::missing_field("toughness"))?, + concentration: concentration + .ok_or_else(|| de::Error::missing_field("concentration"))?, + healing: healing.ok_or_else(|| de::Error::missing_field("healing"))?, + condition: condition.ok_or_else(|| de::Error::missing_field("condition"))?, + instance_id: instance_id + .ok_or_else(|| de::Error::missing_field("instance_id"))?, + first_aware: first_aware + .ok_or_else(|| de::Error::missing_field("first_aware"))?, + last_aware: last_aware.ok_or_else(|| de::Error::missing_field("last_aware"))?, + master_agent: master_agent + .ok_or_else(|| de::Error::missing_field("master_agent"))?, + phantom_data: PhantomData, + }) + } + }; + + const FIELDS: &'static [&'static str] = &[ + "addr", + "kind", + "toughness", + "concentration", + "healing", + "condition", + "instance_id", + "first_aware", + "last_aware", + "master_agent", + ]; + deserializer.deserialize_struct("Agent", FIELDS, AgentVisitor) + } +} + +impl TryFrom<&raw::Agent> for Agent { + type Error = EvtcError; + + /// Parse a raw agent. + fn try_from(raw_agent: &raw::Agent) -> Result<Self, Self::Error> { + let kind = AgentKind::try_from(raw_agent)?; + Ok(Agent { + addr: raw_agent.addr, + kind, + toughness: raw_agent.toughness, + concentration: raw_agent.concentration, + healing: raw_agent.healing, + condition: raw_agent.condition, + instance_id: 0, + first_aware: 0, + last_aware: u64::max_value(), + master_agent: None, + phantom_data: PhantomData, + }) + } +} + +impl TryFrom<raw::Agent> for Agent { + type Error = EvtcError; + + /// Convenience method to avoid manual borrowing. + /// + /// Note that this conversion will consume the agent, so if you plan on re-using it, use the + /// `TryFrom<&raw::Agent>` implementation that works with a reference. + fn try_from(raw_agent: raw::Agent) -> Result<Self, Self::Error> { + Agent::try_from(&raw_agent) + } +} + +impl<Kind> Agent<Kind> { + /// Unconditionally change the tagged type. + #[inline] + fn transmute<T>(&self) -> &Agent<T> { + // Beware, unsafe code ahead! + // + // What are we doing here? + // In Agent<T>, T is a marker type that only exists at the type level. There is no actual + // value of type T being held, instead, we use PhantomData under the hood. This is so we + // can implement special methods on Agent<Player>, Agent<Gadget> and Agent<Character>, + // which allows us in some cases to avoid the "second check" (e.g. Log::players() can + // return Agent<Player>, as the function already makes sure all returned agents are + // players). This makes the interface more ergonomical, as we can prove to the type checker + // at compile time that a given Agent has a certain AgentKind. + // + // Why is this safe? + // PhantomData<T> (which is what Agent<T> boils down to) is a zero-sized type, which means + // it does not actually change the layout of the struct. There is some discussion in [1], + // which suggests that this is true for #[repr(C)] structs (which Agent is). We can + // therefore safely transmute from Agent<U> to Agent<T>, for any U and T. + // + // Can this lead to unsafety? + // No, the actual data access is still done through safe rust and a if-let. In the worst + // case it can lead to an unexpected panic, but the "guarantee" made by T is rather weak in + // that regard. + // + // What are the alternatives? + // None, as far as I'm aware. Going from Agent<U> to Agent<T> is possible in safe Rust by + // destructuring the struct, or alternatively by [2] (if it would be implemented). However, + // when dealing with references, there seems to be no way to safely go from Agent<U> to + // Agent<T>, even if they share the same layout. + // + // [1]: https://www.reddit.com/r/rust/comments/avrbvc/is_it_safe_to_transmute_foox_to_fooy_if_the/ + // [2]: https://github.com/rust-lang/rfcs/pull/2528 + unsafe { &*(self as *const Agent<Kind> as *const Agent<T>) } + } + + /// Erase any extra information about the contained agent kind. + #[inline] + pub fn erase(&self) -> &Agent { + self.transmute() + } + + /// Try to convert this `Agent` to an `Agent` representing a `Player`. + #[inline] + pub fn as_player(&self) -> Option<&Agent<Player>> { + if self.kind.is_player() { + Some(self.transmute()) + } else { + None + } + } + + /// Try to convert this `Agent` to an `Agent` representing a `Gadget`. + #[inline] + pub fn as_gadget(&self) -> Option<&Agent<Gadget>> { + if self.kind.is_gadget() { + Some(self.transmute()) + } else { + None + } + } + + /// Try to convert this `Agent` to an `Agent` representing a `Character`. + #[inline] + pub fn as_character(&self) -> Option<&Agent<Character>> { + if self.kind.is_character() { + Some(self.transmute()) + } else { + None + } + } +} + +impl Agent<Player> { + /// Directly access the underlying player data. + #[inline] + pub fn player(&self) -> &Player { + self.kind.as_player().expect("Agent<Player> had no player!") + } + + /// Shorthand to get the player's account name. + #[inline] + pub fn account_name(&self) -> &str { + self.player().account_name() + } + + /// Shorthand to get the player's character name. + #[inline] + pub fn character_name(&self) -> &str { + self.player().character_name() + } + + /// Shorthand to get the player's elite specialization. + #[inline] + pub fn elite(&self) -> Option<EliteSpec> { + self.player().elite() + } + + /// Shorthand to get the player's profession. + #[inline] + pub fn profession(&self) -> Profession { + self.player().profession() + } + + /// Shorthand to get the player's subgroup. + #[inline] + pub fn subgroup(&self) -> u8 { + self.player().subgroup() + } +} + +impl Agent<Gadget> { + /// Directly access the underlying gadget data. + #[inline] + pub fn gadget(&self) -> &Gadget { + self.kind.as_gadget().expect("Agent<Gadget> had no gadget!") + } + + /// Shorthand to get the gadget's id. + #[inline] + pub fn id(&self) -> u16 { + self.gadget().id() + } + + /// Shorthand to get the gadget's name. + #[inline] + pub fn name(&self) -> &str { + self.gadget().name() + } +} + +impl Agent<Character> { + /// Directly access the underlying character data. + #[inline] + pub fn character(&self) -> &Character { + self.kind + .as_character() + .expect("Agent<Character> had no character!") + } + + /// Shorthand to get the character's id. + #[inline] + pub fn id(&self) -> u16 { + self.character().id() + } + + /// Shorthand to get the character's name. + #[inline] + pub fn name(&self) -> &str { + self.character().name() + } +} @@ -88,15 +88,14 @@ //! While there are legitimate use cases for writing/modification support, they are currently not //! implemented (but might be in a future version). -use std::convert::TryFrom; -use std::marker::PhantomData; - -use getset::{CopyGetters, Getters}; 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}; @@ -137,728 +136,6 @@ pub enum EvtcError { Utf8Error(#[from] std::str::Utf8Error), } -/// Player-specific agent data. -/// -/// Player agents are characters controlled by a player and as such, they contain data about the -/// account and character used (name, profession), as well as the squad composition. -/// -/// Note that a `Player` is only the player character itself. Any additional entities that are -/// spawned by the player (clones, illusions, banners, ...) are either a [`Character`][Character] -/// or a [`Gadget`][Gadget]. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, Hash, PartialEq, Eq, CopyGetters)] -pub struct Player { - /// The player's profession. - #[get_copy = "pub"] - profession: Profession, - - /// The player's elite specialization, if any is equipped. - #[get_copy = "pub"] - elite: Option<EliteSpec>, - - character_name: String, - - account_name: String, - - /// The subgroup the player was in. - #[get_copy = "pub"] - subgroup: u8, -} - -impl Player { - /// The player's character name. - pub fn character_name(&self) -> &str { - &self.character_name - } - - /// The player's account name. - /// - /// This includes the leading colon and the 4-digit denominator. - pub fn account_name(&self) -> &str { - &self.account_name - } -} - -/// Gadget-specific agent data. -/// -/// Gadgets are entities that are spawned by certain skills. They are mostly inanimate objects that -/// only exist to achieve a certain skill effect. -/// -/// Examples of this include the [banners](https://wiki.guildwars2.com/wiki/Banner) spawned by -/// Warriors, but also skill effects like the roots created by -/// [Entangle](https://wiki.guildwars2.com/wiki/Entangle) or the other objects in the arena. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[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 - } -} - -/// Character-specific agent data. -/// -/// Characters are NPCs such as the bosses themselves, additional mobs that they spawn, but also -/// friendly characters like Mesmer's clones and illusions, Necromancer minions, and so on. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[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 - } -} - -/// The type of an agent. -/// -/// arcdps differentiates between three types of agents: [`Player`][Player], -/// [`Character`][Character] and [`Gadget`][Gadget]. This enum unifies handling between them by -/// allowing you to pattern match or use one of the accessor methods. -/// -/// The main way to obtain a `AgentKind` is by using the [`.kind()`][Agent::kind] method on an -/// [`Agent`][Agent]. In cases where you already have a [`raw::Agent`][raw::Agent] available, you -/// can also use the [`TryFrom`][TryFrom]/[`TryInto`][std::convert::TryInto] traits to convert a -/// `raw::Agent` or `&raw::Agent` to a `AgentKind`: -/// -/// ```no_run -/// # use evtclib::{AgentKind, raw}; -/// use std::convert::TryInto; -/// // Get a raw::Agent from somewhere -/// let raw_agent: raw::Agent = panic!(); -/// // Convert it -/// let agent: AgentKind = raw_agent.try_into().unwrap(); -/// ``` -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[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), -} - -impl AgentKind { - fn from_raw_character(raw_agent: &raw::Agent) -> Result<Character, EvtcError> { - assert!(raw_agent.is_character()); - let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?; - Ok(Character { - id: raw_agent.prof as u16, - name: name.to_str()?.to_owned(), - }) - } - - fn from_raw_gadget(raw_agent: &raw::Agent) -> Result<Gadget, EvtcError> { - assert!(raw_agent.is_gadget()); - let name = raw::cstr_up_to_nul(&raw_agent.name).ok_or(EvtcError::InvalidData)?; - Ok(Gadget { - id: raw_agent.prof as u16, - name: name.to_str()?.to_owned(), - }) - } - - fn from_raw_player(raw_agent: &raw::Agent) -> Result<Player, EvtcError> { - assert!(raw_agent.is_player()); - let character_name = raw::cstr_up_to_nul(&raw_agent.name) - .ok_or(EvtcError::InvalidData)? - .to_str()?; - let account_name = raw::cstr_up_to_nul(&raw_agent.name[character_name.len() + 1..]) - .ok_or(EvtcError::InvalidData)? - .to_str()?; - let subgroup = raw_agent.name[character_name.len() + account_name.len() + 2] - b'0'; - let elite = if raw_agent.is_elite == 0 { - None - } else { - Some( - EliteSpec::from_u32(raw_agent.is_elite) - .ok_or(EvtcError::InvalidEliteSpec(raw_agent.is_elite))?, - ) - }; - Ok(Player { - profession: Profession::from_u32(raw_agent.prof) - .ok_or(EvtcError::InvalidProfession(raw_agent.prof))?, - elite, - character_name: character_name.to_owned(), - account_name: account_name.to_owned(), - subgroup, - }) - } - - /// Accesses the inner [`Player`][Player] struct, if available. - pub fn as_player(&self) -> Option<&Player> { - if let AgentKind::Player(ref player) = *self { - Some(player) - } else { - None - } - } - - /// Determines whether this `AgentKind` contains a player. - pub fn is_player(&self) -> bool { - self.as_player().is_some() - } - - /// Accesses the inner [`Gadget`][Gadget] struct, if available. - pub fn as_gadget(&self) -> Option<&Gadget> { - if let AgentKind::Gadget(ref gadget) = *self { - Some(gadget) - } else { - None - } - } - - /// Determines whether this `AgentKind` contains a gadget. - pub fn is_gadget(&self) -> bool { - self.as_gadget().is_some() - } - - /// Accesses the inner [`Character`][Character] struct, if available. - pub fn as_character(&self) -> Option<&Character> { - if let AgentKind::Character(ref character) = *self { - Some(character) - } else { - None - } - } - - /// Determines whether this `AgentKind` contains a character. - pub fn is_character(&self) -> bool { - self.as_character().is_some() - } -} - -impl TryFrom<raw::Agent> for AgentKind { - type Error = EvtcError; - /// Convenience method to avoid manual borrowing. - /// - /// Note that this conversion will consume the agent, so if you plan on re-using it, use the - /// `TryFrom<&raw::Agent>` implementation that works with a reference. - fn try_from(raw_agent: raw::Agent) -> Result<Self, Self::Error> { - Self::try_from(&raw_agent) - } -} - -impl TryFrom<&raw::Agent> for AgentKind { - type Error = EvtcError; - - /// Extract the correct `AgentKind` from the given [raw agent][raw::Agent]. - /// - /// This automatically discerns between player, gadget and characters. - /// - /// Note that in most cases, you probably want to use `Agent::try_from` or even - /// [`process`][process] instead of this function. - fn try_from(raw_agent: &raw::Agent) -> Result<Self, Self::Error> { - if raw_agent.is_character() { - Ok(AgentKind::Character(AgentKind::from_raw_character( - raw_agent, - )?)) - } else if raw_agent.is_gadget() { - Ok(AgentKind::Gadget(AgentKind::from_raw_gadget(raw_agent)?)) - } else if raw_agent.is_player() { - Ok(AgentKind::Player(AgentKind::from_raw_player(raw_agent)?)) - } else { - Err(EvtcError::InvalidData) - } - } -} - -/// An agent. -/// -/// Agents in arcdps are very versatile, as a lot of things end up being an "agent". This includes: -/// * Players -/// * Bosses -/// * Any additional mobs that spawn -/// * Mesmer illusions -/// * Ranger spirits, pets -/// * Guardian spirit weapons -/// * ... -/// -/// Generally, you can divide them into three kinds ([`AgentKind`][AgentKind]): -/// * [`Player`][Player]: All players themselves. -/// * [`Character`][Character]: Non-player mobs, including most bosses, "adds" and player-generated -/// characters. -/// * [`Gadget`][Gadget]: Some additional gadgets, such as ley rifts, continuum split, ... -/// -/// All of these agents share some common fields, which are the ones accessible in `Agent<Kind>`. -/// The kind can be retrieved using [`.kind()`][Agent::kind], which can be matched on. -/// -/// # Obtaining an agent -/// -/// The normal way to obtain the agents is to use the [`.agents()`](Log::agents) method on a -/// [`Log`][Log], or one of the other accessor methods (like [`.players()`][Log::players] or -/// [`.agent_by_addr()`][Log::agent_by_addr]). -/// -/// In the cases where you already have a [`raw::Agent`][raw::Agent] available, you can also -/// convert it to an [`Agent`][Agent] by using the standard -/// [`TryFrom`][TryFrom]/[`TryInto`][std::convert::TryInto] traits: -/// -/// ```no_run -/// # use evtclib::{Agent, raw}; -/// use std::convert::TryInto; -/// let raw_agent: raw::Agent = panic!(); -/// let agent: Agent = raw_agent.try_into().unwrap(); -/// ``` -/// -/// Note that you can convert references as well, so if you plan on re-using the raw agent -/// afterwards, you should opt for `Agent::try_from(&raw_agent)` instead. -/// -/// # The `Kind` parameter -/// -/// The type parameter is not actually used and only exists at the type level. It can be used to -/// tag `Agent`s containing a known kind. For example, `Agent<Player>` implements -/// [`.player()`][Agent::player], which returns a `&Player` directly (instead of a -/// `Option<&Player>`). This works because such tagged `Agent`s can only be constructed (safely) -/// using [`.as_player()`][Agent::as_player], [`.as_gadget()`][Agent::as_gadget] or -/// [`.as_character()`][Agent::as_character]. This is useful since functions like -/// [`Log::players`][Log::players], which already filter only players, don't require the consumer -/// to do another check/pattern match for the right agent kind. -/// -/// The unit type `()` is used to tag `Agent`s which contain an undetermined type, and it is the -/// default if you write `Agent` without any parameters. -/// -/// The downside is that methods which work on `Agent`s theoretically should be generic over -/// `Kind`. An escape hatch is the method [`.erase()`][Agent::erase], which erases the kind -/// information and produces the default `Agent<()>`. Functions/methods that only take `Agent<()>` -/// can therefore be used by any other agent as well. -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[derive(Debug, Clone, Hash, PartialEq, Eq, Getters, CopyGetters)] -// For the reasoning of #[repr(C)] see Agent::transmute. -#[repr(C)] -pub struct Agent<Kind = ()> { - /// The address of this agent. - /// - /// This is not actually the address of the in-memory Rust object, but rather a serialization - /// detail of arcdps. You should consider this as an opaque number and only compare it to other - /// agent addresses. - #[get_copy = "pub"] - addr: u64, - - /// The kind of this agent. - #[get = "pub"] - kind: AgentKind, - - /// The toughness of this agent. - /// - /// This is not an absolute number, but a relative indicator that indicates this agent's - /// toughness relative to the other people in the squad. - /// - /// 0 means lowest toughness, 10 means highest toughness. - #[get_copy = "pub"] - toughness: i16, - - /// The concentration of this agent. - /// - /// This is not an absolute number, but a relative indicator that indicates this agent's - /// concentration relative to the other people in the squad. - /// - /// 0 means lowest concentration, 10 means highest concentration. - #[get_copy = "pub"] - concentration: i16, - - /// The healing power of this agent. - /// - /// This is not an absolute number, but a relative indicator that indicates this agent's - /// healing power relative to the other people in the squad. - /// - /// 0 means lowest healing power, 10 means highest healing power. - #[get_copy = "pub"] - healing: i16, - - /// The condition damage of this agent. - /// - /// This is not an absolute number, but a relative indicator that indicates this agent's - /// condition damage relative to the other people in the squad. - /// - /// 0 means lowest condition damage, 10 means highest condition damage. - #[get_copy = "pub"] - condition: i16, - - /// The instance ID of this agent. - #[get_copy = "pub"] - instance_id: u16, - - /// The timestamp of the first event entry with this agent. - #[get_copy = "pub"] - first_aware: u64, - - /// The timestamp of the last event entry with this agent. - #[get_copy = "pub"] - last_aware: u64, - - /// The master agent's address. - #[get_copy = "pub"] - master_agent: Option<u64>, - - #[cfg_attr(feature = "serde", serde(skip_serializing))] - phantom_data: PhantomData<Kind>, -} - -// We could derive this, however that would derive Deserialize generically for Agent<T>, where T is -// deserializable. In particular, this would mean that you could deserialize Agent<Character>, -// Agent<Player> and Agent<Gadget> directly, which would not be too bad - the problem is that serde -// has no way of knowing if the serialized agent actually is a character/player/gadget agent, as -// that information only exists on the type level. This meant that you could "coerce" agents into -// the wrong type, safely, using a serialization followed by a deserialization. -// Now this doesn't actually lead to memory unsafety or other bad behaviour, but it could mean that -// the program would panic if you tried to access a non-existing field, e.g. by calling -// Agent<Character>::id() on a non-character agent. -// In order to prevent this, we manually implement Deserialize only for Agent<()>, so that the -// usual conversion functions with the proper checks have to be used. -#[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for Agent { - fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { - use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor}; - use std::fmt; - - #[derive(serde::Deserialize)] - #[serde(field_identifier, rename_all = "snake_case")] - enum Field { - Addr, - Kind, - Toughness, - Concentration, - Healing, - Condition, - InstanceId, - FirstAware, - LastAware, - MasterAgent, - }; - - struct AgentVisitor; - - impl<'de> Visitor<'de> for AgentVisitor { - type Value = Agent; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("struct Agent") - } - - fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<Agent, V::Error> { - let mut addr = None; - let mut kind = None; - let mut toughness = None; - let mut concentration = None; - let mut healing = None; - let mut condition = None; - let mut instance_id = None; - let mut first_aware = None; - let mut last_aware = None; - let mut master_agent = None; - - while let Some(key) = map.next_key()? { - match key { - Field::Addr => { - if addr.is_some() { - return Err(de::Error::duplicate_field("addr")); - } - addr = Some(map.next_value()?); - } - Field::Kind => { - if kind.is_some() { - return Err(de::Error::duplicate_field("kind")); - } - kind = Some(map.next_value()?); - } - Field::Toughness => { - if toughness.is_some() { - return Err(de::Error::duplicate_field("toughness")); - } - toughness = Some(map.next_value()?); - } - Field::Concentration => { - if concentration.is_some() { - return Err(de::Error::duplicate_field("concentration")); - } - concentration = Some(map.next_value()?); - } - Field::Healing => { - if healing.is_some() { - return Err(de::Error::duplicate_field("healing")); - } - healing = Some(map.next_value()?); - } - Field::Condition => { - if condition.is_some() { - return Err(de::Error::duplicate_field("condition")); - } - condition = Some(map.next_value()?); - } - Field::InstanceId=> { - if instance_id.is_some() { - return Err(de::Error::duplicate_field("instance_id")); - } - instance_id = Some(map.next_value()?); - } - Field::FirstAware=> { - if first_aware.is_some() { - return Err(de::Error::duplicate_field("first_aware")); - } - first_aware = Some(map.next_value()?); - } - Field::LastAware => { - if last_aware.is_some() { - return Err(de::Error::duplicate_field("last_aware")); - } - last_aware = Some(map.next_value()?); - } - Field::MasterAgent => { - if master_agent.is_some() { - return Err(de::Error::duplicate_field("master_agent")); - } - master_agent = Some(map.next_value()?); - } - } - } - - Ok(Agent { - addr: addr.ok_or_else(|| de::Error::missing_field("addr"))?, - kind: kind.ok_or_else(|| de::Error::missing_field("kind"))?, - toughness: toughness.ok_or_else(|| de::Error::missing_field("toughness"))?, - concentration: concentration - .ok_or_else(|| de::Error::missing_field("concentration"))?, - healing: healing.ok_or_else(|| de::Error::missing_field("healing"))?, - condition: condition.ok_or_else(|| de::Error::missing_field("condition"))?, - instance_id: instance_id - .ok_or_else(|| de::Error::missing_field("instance_id"))?, - first_aware: first_aware - .ok_or_else(|| de::Error::missing_field("first_aware"))?, - last_aware: last_aware.ok_or_else(|| de::Error::missing_field("last_aware"))?, - master_agent: master_agent - .ok_or_else(|| de::Error::missing_field("master_agent"))?, - phantom_data: PhantomData, - }) - } - }; - - const FIELDS: &'static [&'static str] = &[ - "addr", - "kind", - "toughness", - "concentration", - "healing", - "condition", - "instance_id", - "first_aware", - "last_aware", - "master_agent", - ]; - deserializer.deserialize_struct("Agent", FIELDS, AgentVisitor) - } -} - -impl TryFrom<&raw::Agent> for Agent { - type Error = EvtcError; - - /// Parse a raw agent. - fn try_from(raw_agent: &raw::Agent) -> Result<Self, Self::Error> { - let kind = AgentKind::try_from(raw_agent)?; - Ok(Agent { - addr: raw_agent.addr, - kind, - toughness: raw_agent.toughness, - concentration: raw_agent.concentration, - healing: raw_agent.healing, - condition: raw_agent.condition, - instance_id: 0, - first_aware: 0, - last_aware: u64::max_value(), - master_agent: None, - phantom_data: PhantomData, - }) - } -} - -impl TryFrom<raw::Agent> for Agent { - type Error = EvtcError; - - /// Convenience method to avoid manual borrowing. - /// - /// Note that this conversion will consume the agent, so if you plan on re-using it, use the - /// `TryFrom<&raw::Agent>` implementation that works with a reference. - fn try_from(raw_agent: raw::Agent) -> Result<Self, Self::Error> { - Agent::try_from(&raw_agent) - } -} - -impl<Kind> Agent<Kind> { - /// Unconditionally change the tagged type. - #[inline] - fn transmute<T>(&self) -> &Agent<T> { - // Beware, unsafe code ahead! - // - // What are we doing here? - // In Agent<T>, T is a marker type that only exists at the type level. There is no actual - // value of type T being held, instead, we use PhantomData under the hood. This is so we - // can implement special methods on Agent<Player>, Agent<Gadget> and Agent<Character>, - // which allows us in some cases to avoid the "second check" (e.g. Log::players() can - // return Agent<Player>, as the function already makes sure all returned agents are - // players). This makes the interface more ergonomical, as we can prove to the type checker - // at compile time that a given Agent has a certain AgentKind. - // - // Why is this safe? - // PhantomData<T> (which is what Agent<T> boils down to) is a zero-sized type, which means - // it does not actually change the layout of the struct. There is some discussion in [1], - // which suggests that this is true for #[repr(C)] structs (which Agent is). We can - // therefore safely transmute from Agent<U> to Agent<T>, for any U and T. - // - // Can this lead to unsafety? - // No, the actual data access is still done through safe rust and a if-let. In the worst - // case it can lead to an unexpected panic, but the "guarantee" made by T is rather weak in - // that regard. - // - // What are the alternatives? - // None, as far as I'm aware. Going from Agent<U> to Agent<T> is possible in safe Rust by - // destructuring the struct, or alternatively by [2] (if it would be implemented). However, - // when dealing with references, there seems to be no way to safely go from Agent<U> to - // Agent<T>, even if they share the same layout. - // - // [1]: https://www.reddit.com/r/rust/comments/avrbvc/is_it_safe_to_transmute_foox_to_fooy_if_the/ - // [2]: https://github.com/rust-lang/rfcs/pull/2528 - unsafe { &*(self as *const Agent<Kind> as *const Agent<T>) } - } - - /// Erase any extra information about the contained agent kind. - #[inline] - pub fn erase(&self) -> &Agent { - self.transmute() - } - - /// Try to convert this `Agent` to an `Agent` representing a `Player`. - #[inline] - pub fn as_player(&self) -> Option<&Agent<Player>> { - if self.kind.is_player() { - Some(self.transmute()) - } else { - None - } - } - - /// Try to convert this `Agent` to an `Agent` representing a `Gadget`. - #[inline] - pub fn as_gadget(&self) -> Option<&Agent<Gadget>> { - if self.kind.is_gadget() { - Some(self.transmute()) - } else { - None - } - } - - /// Try to convert this `Agent` to an `Agent` representing a `Character`. - #[inline] - pub fn as_character(&self) -> Option<&Agent<Character>> { - if self.kind.is_character() { - Some(self.transmute()) - } else { - None - } - } -} - -impl Agent<Player> { - /// Directly access the underlying player data. - #[inline] - pub fn player(&self) -> &Player { - self.kind.as_player().expect("Agent<Player> had no player!") - } - - /// Shorthand to get the player's account name. - #[inline] - pub fn account_name(&self) -> &str { - self.player().account_name() - } - - /// Shorthand to get the player's character name. - #[inline] - pub fn character_name(&self) -> &str { - self.player().character_name() - } - - /// Shorthand to get the player's elite specialization. - #[inline] - pub fn elite(&self) -> Option<EliteSpec> { - self.player().elite() - } - - /// Shorthand to get the player's profession. - #[inline] - pub fn profession(&self) -> Profession { - self.player().profession() - } - - /// Shorthand to get the player's subgroup. - #[inline] - pub fn subgroup(&self) -> u8 { - self.player().subgroup() - } -} - -impl Agent<Gadget> { - /// Directly access the underlying gadget data. - #[inline] - pub fn gadget(&self) -> &Gadget { - self.kind.as_gadget().expect("Agent<Gadget> had no gadget!") - } - - /// Shorthand to get the gadget's id. - #[inline] - pub fn id(&self) -> u16 { - self.gadget().id() - } - - /// Shorthand to get the gadget's name. - #[inline] - pub fn name(&self) -> &str { - self.gadget().name() - } -} - -impl Agent<Character> { - /// Directly access the underlying character data. - #[inline] - pub fn character(&self) -> &Character { - self.kind - .as_character() - .expect("Agent<Character> had no character!") - } - - /// Shorthand to get the character's id. - #[inline] - pub fn id(&self) -> u16 { - self.character().id() - } - - /// Shorthand to get the character's name. - #[inline] - pub fn name(&self) -> &str { - self.character().name() - } -} - /// A fully processed log file. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] @@ -877,12 +154,12 @@ impl Log { /// Return an agent based on its address. pub fn agent_by_addr(&self, addr: u64) -> Option<&Agent> { - self.agents.iter().find(|a| a.addr == addr) + self.agents.iter().find(|a| a.addr() == addr) } /// Return an agent based on the instance ID. pub fn agent_by_instance_id(&self, instance_id: u16) -> Option<&Agent> { - self.agents.iter().find(|a| a.instance_id == instance_id) + self.agents.iter().find(|a| a.instance_id() == instance_id) } /// Return the master agent of the given agent. @@ -890,7 +167,7 @@ impl Log { /// * `addr` - The address of the agent which to get the master for. pub fn master_agent(&self, addr: u64) -> Option<&Agent> { self.agent_by_addr(addr) - .and_then(|a| a.master_agent) + .and_then(|a| a.master_agent()) .and_then(|a| self.agent_by_addr(a)) } @@ -915,7 +192,7 @@ impl Log { /// and Xera. pub fn boss(&self) -> &Agent { self.characters() - .find(|c| c.character().id == self.boss_id) + .find(|c| c.character().id() == self.boss_id) .map(Agent::erase) .expect("Boss has no agent!") } @@ -930,7 +207,7 @@ impl Log { .map(Encounter::bosses) .unwrap_or(&[] as &[_]); self.characters() - .filter(|c| bosses.iter().any(|boss| *boss as u16 == c.character().id)) + .filter(|c| bosses.iter().any(|boss| *boss as u16 == c.character().id())) .map(Agent::erase) .collect() } diff --git a/src/processing.rs b/src/processing.rs index 722440b..11bdda7 100644 --- a/src/processing.rs +++ b/src/processing.rs @@ -110,7 +110,7 @@ fn setup_agents(data: &raw::Evtc) -> Result<Vec<Agent>, EvtcError> { fn get_agent_by_addr(agents: &mut [Agent], addr: u64) -> Option<&mut Agent> { for agent in agents { - if agent.addr == addr { + if agent.addr() == addr { return Some(agent); } } @@ -121,11 +121,11 @@ fn set_agent_awares(data: &raw::Evtc, agents: &mut [Agent]) -> Result<(), EvtcEr for event in &data.events { if event.is_statechange == raw::CbtStateChange::None { if let Some(current_agent) = get_agent_by_addr(agents, event.src_agent) { - current_agent.instance_id = event.src_instid; - if current_agent.first_aware == 0 { - current_agent.first_aware = event.time; + current_agent.set_instance_id(event.src_instid); + if current_agent.first_aware() == 0 { + current_agent.set_first_aware(event.time); } - current_agent.last_aware = event.time; + current_agent.set_last_aware(event.time); } } } @@ -137,17 +137,17 @@ fn set_agent_masters(data: &raw::Evtc, agents: &mut [Agent]) -> Result<(), EvtcE if event.src_master_instid != 0 { let mut master_addr = None; for agent in &*agents { - if agent.instance_id == event.src_master_instid - && agent.first_aware < event.time - && event.time < agent.last_aware + if agent.instance_id() == event.src_master_instid + && agent.first_aware() < event.time + && event.time < agent.last_aware() { - master_addr = Some(agent.addr); + master_addr = Some(agent.addr()); break; } } if let Some(master_addr) = master_addr { if let Some(current_slave) = get_agent_by_addr(agents, event.src_agent) { - current_slave.master_agent = Some(master_addr); + current_slave.set_master_agent(Some(master_addr)); } } } |