aboutsummaryrefslogtreecommitdiff
path: root/src/agent.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/agent.rs')
-rw-r--r--src/agent.rs732
1 files changed, 732 insertions, 0 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()
+ }
+}