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));                  }              }          } | 
