diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2018-04-23 15:14:35 +0200 | 
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2018-04-23 15:14:35 +0200 | 
| commit | 1a465ee75229d1268f20f2739ba29c4f84f70e7f (patch) | |
| tree | 747cb3d817aa66f150824f799b3987cbea80e877 /src | |
| parent | 6e7431f0ce600502c335b75c8acfe0cf448b68e6 (diff) | |
| download | evtclib-1a465ee75229d1268f20f2739ba29c4f84f70e7f.tar.gz evtclib-1a465ee75229d1268f20f2739ba29c4f84f70e7f.tar.bz2 evtclib-1a465ee75229d1268f20f2739ba29c4f84f70e7f.zip | |
add basic translation to more readable events
This basically implements the "event logic" as described in the README,
though it produces easier-to-digest events.
The test binary show 0 failed events on an example log, but of course,
not all mechanics are used there, and the parsing logic may very well
contain some errors.
Diffstat (limited to 'src')
| -rw-r--r-- | src/event.rs | 350 | ||||
| -rw-r--r-- | src/lib.rs | 172 | ||||
| -rw-r--r-- | src/main.rs | 79 | ||||
| -rw-r--r-- | src/raw/types.rs | 25 | 
4 files changed, 623 insertions, 3 deletions
| diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..2ae3f05 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,350 @@ +use super::raw; + +use num_traits::FromPrimitive; + +/// A rusty enum for all possible combat events. +/// +/// This makes dealing with `CbtEvent` a bit saner (and safer). +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum EventKind { +    // State change events +    /// The agent has entered combat. +    EnterCombat { +        agent_addr: u64, +        subgroup: u64, +    }, +    /// The agent has left combat. +    ExitCombat { +        agent_addr: u64, +    }, +    /// The agent is now alive. +    ChangeUp { +        agent_addr: u64, +    }, +    /// The agent is now downed. +    ChangeDown { +        agent_addr: u64, +    }, +    /// The agent is now dead. +    ChangeDead { +        agent_addr: u64, +    }, +    /// The agent is now in tracking range. +    Spawn { +        agent_addr: u64, +    }, +    /// The agent has left the tracking range. +    Despawn { +        agent_addr: u64, +    }, +    /// The agent has reached a health treshold. +    HealthUpdate { +        agent_addr: u64, +        /// The new health, as percentage multiplied by 10000. +        health: u16, +    }, +    /// The logging has started. +    LogStart { +        server_timestamp: u32, +        local_timestamp: u32, +    }, +    /// The logging has finished. +    LogEnd { +        server_timestamp: u32, +        local_timestamp: u32, +    }, +    /// The agent has swapped the weapon set. +    WeaponSwap { +        agent_addr: u64, +        set: WeaponSet, +    }, +    /// The given agent has its max health changed. +    MaxHealthUpdate { +        agent_addr: u64, +        max_health: u64, +    }, +    /// The given agent is the point-of-view. +    PointOfView { +        agent_addr: u64, +    }, +    /// The given language is the text language. +    Language { +        language: raw::Language, +    }, +    /// The log was made with the given game build. +    Build { +        build: u64, +    }, +    /// The shard id of the server. +    ShardId { +        shard_id: u64, +    }, +    /// A reward has been awarded. +    Reward { +        reward_id: u64, +        reward_type: i32, +    }, + +    /// A skill has been used. +    SkillUse { +        source_agent_addr: u64, +        skill_id: u16, +        activation: Activation, +    }, + +    /// Condition damage tick. +    ConditionTick { +        source_agent_addr: u64, +        destination_agent_addr: u64, +        condition_id: u16, +        damage: i32, +    }, + +    /// Condition damage tick that was negated by invulnerability. +    InvulnTick { +        source_agent_addr: u64, +        destination_agent_addr: u64, +        condition_id: u16, +    }, + +    /// Physical damage. +    Physical { +        source_agent_addr: u64, +        destination_agent_addr: u64, +        skill_id: u16, +        damage: i32, +        result: raw::CbtResult, +    }, + +    /// Buff applied. +    BuffApplication { +        source_agent_addr: u64, +        destination_agent_addr: u64, +        buff_id: u16, +        duration: i32, +        overstack: u16, +    }, + +    /// Buff removed. +    BuffRemove { +        source_agent_addr: u64, +        destination_agent_addr: u64, +        buff_id: u16, +        total_duration: i32, +        longest_stack: i32, +        removal: raw::CbtBuffRemove, +    }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Event { +    pub time: u64, +    pub kind: EventKind, +    pub is_ninety: bool, +    pub is_fifty: bool, +    pub is_moving: bool, +    pub is_flanking: bool, +    pub is_shields: bool, +} + +impl Event { +    pub fn from_raw(raw_event: &raw::CbtEvent) -> Option<Event> { +        use raw::CbtStateChange; +        let kind = match raw_event.is_statechange { +            // Check for state change events first. +            CbtStateChange::EnterCombat => EventKind::EnterCombat { +                agent_addr: raw_event.src_agent, +                subgroup: raw_event.dst_agent, +            }, +            CbtStateChange::ExitCombat => EventKind::ExitCombat { +                agent_addr: raw_event.src_agent, +            }, +            CbtStateChange::ChangeUp => EventKind::ChangeUp { +                agent_addr: raw_event.src_agent, +            }, +            CbtStateChange::ChangeDead => EventKind::ChangeDead { +                agent_addr: raw_event.src_agent, +            }, +            CbtStateChange::ChangeDown => EventKind::ChangeDown { +                agent_addr: raw_event.src_agent, +            }, +            CbtStateChange::Spawn => EventKind::Spawn { +                agent_addr: raw_event.src_agent, +            }, +            CbtStateChange::Despawn => EventKind::Despawn { +                agent_addr: raw_event.src_agent, +            }, +            CbtStateChange::HealthUpdate => EventKind::HealthUpdate { +                agent_addr: raw_event.src_agent, +                health: raw_event.dst_agent as u16, +            }, +            CbtStateChange::LogStart => EventKind::LogStart { +                server_timestamp: raw_event.value as u32, +                local_timestamp: raw_event.buff_dmg as u32, +            }, +            CbtStateChange::LogEnd => EventKind::LogEnd { +                server_timestamp: raw_event.value as u32, +                local_timestamp: raw_event.buff_dmg as u32, +            }, +            CbtStateChange::WeapSwap => EventKind::WeaponSwap { +                agent_addr: raw_event.src_agent, +                set: WeaponSet::from_u64(raw_event.dst_agent), +            }, +            CbtStateChange::MaxHealthUpdate => EventKind::MaxHealthUpdate { +                agent_addr: raw_event.src_agent, +                max_health: raw_event.dst_agent, +            }, +            CbtStateChange::PointOfView => EventKind::PointOfView { +                agent_addr: raw_event.src_agent, +            }, +            CbtStateChange::Language => EventKind::Language { +                language: raw::Language::from_u64(raw_event.src_agent).unwrap(), +            }, +            CbtStateChange::GwBuild => EventKind::Build { +                build: raw_event.src_agent, +            }, +            CbtStateChange::ShardId => EventKind::ShardId { +                shard_id: raw_event.src_agent, +            }, +            CbtStateChange::Reward => EventKind::Reward { +                reward_id: raw_event.dst_agent, +                reward_type: raw_event.value, +            }, + +            CbtStateChange::None => if let Some(kind) = check_activation(raw_event) { +                kind +            } else { +                return None +            }, +        }; +        Some(Event { +            time: raw_event.time, +            kind: kind, +            is_ninety: raw_event.is_ninety, +            is_fifty: raw_event.is_fifty, +            is_moving: raw_event.is_moving, +            is_flanking: raw_event.is_flanking, +            is_shields: raw_event.is_shields, +        }) +    } +} + +fn check_activation(raw_event: &raw::CbtEvent) -> Option<EventKind> { +    use raw::CbtActivation; +    match raw_event.is_activation { +        CbtActivation::None => check_buffremove(raw_event), + +        activation => Some(EventKind::SkillUse { +            source_agent_addr: raw_event.src_agent, +            skill_id: raw_event.skillid, +            activation: match activation { +                CbtActivation::Quickness => Activation::Quickness(raw_event.value), +                CbtActivation::Normal => Activation::Normal(raw_event.value), +                CbtActivation::CancelFire => Activation::CancelFire(raw_event.value), +                CbtActivation::CancelCancel => Activation::CancelCancel(raw_event.value), +                CbtActivation::Reset => Activation::Reset, +                CbtActivation::None => unreachable!(), +            }, +        }), +    } +} + +fn check_buffremove(raw_event: &raw::CbtEvent) -> Option<EventKind> { +    use raw::CbtBuffRemove; +    match raw_event.is_buffremove { +        CbtBuffRemove::None => check_damage(raw_event), + +        removal => Some(EventKind::BuffRemove { +            source_agent_addr: raw_event.src_agent, +            destination_agent_addr: raw_event.dst_agent, +            buff_id: raw_event.skillid, +            total_duration: raw_event.value, +            longest_stack: raw_event.buff_dmg, +            removal, +        }), +    } +} + +fn check_damage(raw_event: &raw::CbtEvent) -> Option<EventKind> { +    if raw_event.buff == 0 && raw_event.iff == raw::IFF::Foe && raw_event.dst_agent != 0 { +        Some(EventKind::Physical { +            source_agent_addr: raw_event.src_agent, +            destination_agent_addr: raw_event.dst_agent, +            skill_id: raw_event.skillid, +            damage: raw_event.value, +            result: raw_event.result, +        }) +    } else if raw_event.buff == 1 && raw_event.buff_dmg != 0 && raw_event.dst_agent != 0 && raw_event.value == 0 { +        Some(EventKind::ConditionTick { +            source_agent_addr: raw_event.src_agent, +            destination_agent_addr: raw_event.dst_agent, +            condition_id: raw_event.skillid, +            damage: raw_event.buff_dmg, +        }) +    } else if raw_event.buff == 1 && raw_event.buff_dmg == 0 && raw_event.value != 0 { +        Some(EventKind::BuffApplication { +            source_agent_addr: raw_event.src_agent, +            destination_agent_addr: raw_event.dst_agent, +            buff_id: raw_event.skillid, +            duration: raw_event.value, +            overstack: raw_event.overstack_value, +        }) +    } else if raw_event.buff == 1 && raw_event.buff_dmg == 0 && raw_event.value == 0 { +        Some(EventKind::InvulnTick { +            source_agent_addr: raw_event.src_agent, +            destination_agent_addr: raw_event.dst_agent, +            condition_id: raw_event.skillid, +        }) +    } else { +        None +    } +} + +/// The different weapon-sets in game. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum WeaponSet { +    /// First water weapon set. +    Water0, +    /// Second water weapon set. +    Water1, +    /// First land set. +    Land0, +    /// Second land set. +    Land1, +    /// An unknown weapon set. +    /// +    /// This can be caused bundles or anything else that uses the "weapon swap" +    /// event but is not a normal weapon set. +    Unknown(u8), +} + +impl WeaponSet { +    /// Parse a given integer into the correct enum value. +    pub fn from_u64(value: u64) -> WeaponSet { +        match value { +            // magic constants from arcdps README +            0 => WeaponSet::Water0, +            1 => WeaponSet::Water1, +            4 => WeaponSet::Land0, +            5 => WeaponSet::Land1, +            _ => WeaponSet::Unknown(value as u8), +        } +    } +} + +/// The different types to activate a skill. +/// +/// The parameter is the animation time in milliseconds. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Activation { +    /// The skill was activated with quickness. +    Quickness(i32), +    /// The skill was activated normally. +    Normal(i32), +    /// The skill was cancelled with reaching the channel time. +    CancelFire(i32), +    /// The skill was cancelled without reaching the channel time. +    CancelCancel(i32), +    /// The channel was completed successfully. +    Reset, +} @@ -7,3 +7,175 @@ extern crate byteorder;  extern crate num_traits;  pub mod raw; + +mod event; +pub use event::{Event, EventKind}; + +quick_error! { +    #[derive(Debug)] +    pub enum EvtcError { +        InvalidData { +            description("invalid data has been provided") +        } +        Utf8Error(err: ::std::string::FromUtf8Error) { +            from() +            description("utf8 decoding error") +            display("UTF-8 decoding error: {}", err) +            cause(err) +        } +    } +} + +/// The type of an agent. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AgentKind { +    Player { profession: u32, elite: u32 }, +    Gadget(u16), +    Character(u16), +} + +/// Name of an agent. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AgentName { +    Single(String), +    Player { character_name: String, account_name: String, subgroup: u8 } +} + +/// An agent. +#[derive(Debug, Clone)] +pub struct Agent { +    addr: u64, +    kind: AgentKind, +    toughness: i16, +    concentration: i16, +    healing: i16, +    condition: i16, +    name: AgentName, +    instance_id: u16, +    first_aware: u64, +    last_aware: u64, +    master_agent: Option<u64>, +} + +/// A fully processed log file. +#[derive(Debug, Clone)] +pub struct Log { +    agents: Vec<Agent>, +} + +pub fn process(data: &raw::Evtc) -> Result<Log, EvtcError> { +    // Prepare "augmented" agents +    let mut agents = setup_agents(data)?; + +    // Do the first aware/last aware field +    set_agent_awares(data, &mut agents)?; + +    // Set the master addr field +    set_agent_masters(data, &mut agents)?; + +    for agent in &agents { +        if let AgentKind::Player { .. } = agent.kind { +            println!("Agent: {:#?}", agent); +        } +    } + +    panic!(); +} + +fn setup_agents(data: &raw::Evtc) -> Result<Vec<Agent>, EvtcError> { +    let mut agents = Vec::with_capacity(data.agents.len()); + +    for raw_agent in &data.agents { +        let kind = if raw_agent.is_character() { +            AgentKind::Character(raw_agent.prof as u16) +        } else if raw_agent.is_gadget() { +            AgentKind::Gadget(raw_agent.prof as u16) +        } else if raw_agent.is_player() { +            AgentKind::Player { profession: raw_agent.prof, elite: raw_agent.is_elite } +        } else { +            return Err(EvtcError::InvalidData); +        }; + +        let name = if raw_agent.is_player() { +            let first = raw_agent.name.iter().cloned() +                .take_while(|c| *c != 0) +                .collect::<Vec<_>>(); +            let second = raw_agent.name.iter().cloned() +                .skip(first.len() + 1) +                .take_while(|c| *c != 0) +                .collect::<Vec<_>>(); +            let third = raw_agent.name[first.len() + second.len() + 2] - b'0'; +            AgentName::Player { +                character_name: String::from_utf8(first)?, +                account_name: String::from_utf8(second)?, +                subgroup: third, +            } +        } else { +            let name = raw_agent.name.iter().cloned() +                .take_while(|c| *c != 0) +                .collect::<Vec<_>>(); +            AgentName::Single(String::from_utf8(name)?) +        }; + +        let agent = Agent { +            addr: raw_agent.addr, +            kind: kind, +            toughness: raw_agent.toughness, +            concentration: raw_agent.concentration, +            healing: raw_agent.healing, +            condition: raw_agent.condition, +            name: name, +            instance_id: 0, +            first_aware: 0, +            last_aware: u64::max_value(), +            master_agent: None, +        }; + +        agents.push(agent); +    } +    Ok(agents) +} + +fn get_agent_by_addr(agents: &mut [Agent], addr: u64) -> Option<&mut Agent> { +    for agent in agents { +        if agent.addr == addr { +            return Some(agent) +        } +    } +    None +} + +fn set_agent_awares(data: &raw::Evtc, agents: &mut [Agent]) -> Result<(), EvtcError> { +    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.last_aware = event.time; +            } +        } +    } +    Ok(()) +} + +fn set_agent_masters(data: &raw::Evtc, agents: &mut [Agent]) -> Result<(), EvtcError> { +    for event in &data.events { +        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 { +                    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); +                } +            } +        } +    } +    Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 03418d0..94a8abf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,88 @@ extern crate evtclib;  use byteorder::{ReadBytesExt, BE, LE};  use std::fs::File; -use std::io::{Seek, SeekFrom}; +use std::io::BufReader; +use std::collections::HashMap; + +// My addr: 5480786193115521456 +// My instid: 624 +// Samarog: 18003064748228432752  pub fn main() -> Result<(), evtclib::raw::parser::ParseError> {      println!("Hello World!"); -    let mut f = File::open("material/Samarog.evtc")?; +    let mut f = BufReader::new(File::open("material/Samarog.evtc")?);      let result = evtclib::raw::parse_file(&mut f)?; +/* +    for agent in result.agents.iter().filter(|a| a.is_player()) { +        println!("Agent: {:?}", agent); +    } + +    let mut damage: HashMap<u16, u64> = HashMap::new(); +    let mut count = 0; +    for event in result.events.iter() { +        if event.is_statechange == evtclib::raw::CbtStateChange::None { +            if (event.dst_agent != 0 && event.dst_instid == 0) || (event.dst_instid != 0 && event.dst_agent == 0) { +                println!("{:#?}", event); +            } +        } +        let must_take = if event.src_instid == 624 && event.skillid == 19426 && (event.value == 287 || event.buff_dmg == 287) { +            println!("Event in question: {:#?}", event); +            true +        } else { false }; +        let mut taken = false; +        if event.src_instid == 624 || event.src_master_instid == 624 { +            //for target in result.agents.iter().filter(|a| a.is_character()) { +                if event.iff == evtclib::raw::IFF::Foe && event.dst_agent != 0 { +                    if event.is_statechange == evtclib::raw::CbtStateChange::None && event.is_buffremove == evtclib::raw::CbtBuffRemove::None { +                        let dmg = if event.buff == 1 && event.buff_dmg != 0 { +                            event.buff_dmg +                        } else if event.buff == 0 && event.value != 0 { +                            event.value +                        } else if [5, 6, 7].contains(&(event.result as u32)) { event.value } +                        else { +                            if must_take && !taken { +                                panic!("Failing event: {:#?}", event); +                            }; +                            continue; +                        }; +                        println!("{} {} {}", event.src_agent, event.skillid, dmg); +                        *damage.entry(event.skillid).or_insert(0) += dmg as u64; +                        count += 1; +                        taken = true; +                    } +                } +            //} +        } +        if must_take && !taken { +            panic!("Failing event: {:#?}", event); +        } +    } +    println!("Damage: {:#?}, Total: {}, Count: {}", damage, damage.values().sum::<u64>(), count); +    println!("Event count: {}", result.events.len()); +    println!("Events for me: {}", result.events.iter().filter(|e| e.src_instid == 624).count()); +*/ +    //let processed = evtclib::process(&result); +    use evtclib::EventKind; +    let mut count = 0; +    let mut damage = 0; +    let mut failed = 0; +    for event in &result.events { +        let shiny = if let Some(c) = evtclib::Event::from_raw(event) { +            c +        } else { +            println!("Failed: {:#?}", event); +            failed += 1; +            continue +        }; +        match &shiny.kind { +            &EventKind::Physical { source_agent_addr: src, damage: dmg, .. } if src == 5480786193115521456 => { count += 1; damage += dmg as u64; }, +            &EventKind::ConditionTick { source_agent_addr: src, damage: dmg, .. } if src == 5480786193115521456 => { count += 1; damage += dmg as u64; }, +            _ => (), +        } +    } +    println!("Count: {}, Damage: {}", count, damage); +    println!("Failed events: {}", failed);      Ok(())  } diff --git a/src/raw/types.rs b/src/raw/types.rs index f94c4c2..f616398 100644 --- a/src/raw/types.rs +++ b/src/raw/types.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{self, fmt};  /// The "friend or foe" enum.  #[repr(C)] @@ -234,6 +234,29 @@ pub struct Agent {      pub name: [u8; 64],  } +impl Agent { +    /// Checks whether this agent is a gadget. +    /// +    /// Gadgets are entities spawned by some skills, like the "Binding Roots" +    /// spawned by Entangle. +    pub fn is_gadget(&self) -> bool { +        self.is_elite == std::u32::MAX && (self.prof & 0xffff0000) == 0xffff0000 +    } + +    /// Checks whether this agent is a character. +    /// +    /// Characters are entities like clones, pets, minions, spirits, but also +    /// minis. +    pub fn is_character(&self) -> bool { +        self.is_elite == std::u32::MAX && (self.prof & 0xffff0000) != 0xffff0000 +    } + +    /// Checks whether this agent is a player. +    pub fn is_player(&self) -> bool { +        self.is_elite != std::u32::MAX +    } +} +  impl fmt::Debug for Agent {      fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {          write!( | 
