From 702288636c4e407860cc5244d07725794d7b8d04 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Thu, 29 Aug 2024 20:19:09 +0200 Subject: implement some new events evtclib hasn't kept up too well with the newest things arcdps now reports. This commit at least introduces the correct CbtStateChange variants for all of the new features, and it adds "high level" EventKinds for some of them. There are still plenty of unimplemented ones, but we can get to that later. Since there are multiple "internal use" variants now, FromRawEventError::UnexpectedReplInfo has been renamed to UnexpectedInternalEvent. --- src/event.rs | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/gamedata.rs | 14 ++++++ src/raw/types.rs | 73 +++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 5 deletions(-) diff --git a/src/event.rs b/src/event.rs index 0a904bc..27ed2db 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,7 +1,7 @@ //! Event definitions. //! //! This module contains the different types of events in their high-level form. -use super::raw; +use super::{gamedata::Ruleset, raw}; use std::convert::TryFrom; use std::fmt::Write; @@ -22,10 +22,12 @@ pub enum FromRawEventError { UnknownDamageEvent, #[error("an unknown language byte was found")] UnknownLanguage, + #[error("an unknown ruleset bit was found")] + UnknownRuleset, #[error("the event contains invalid text")] InvalidText, - #[error("an unexpected REPLINFO was found")] - UnexpectedReplInfo, + #[error("an unexpected internal event was found")] + UnexpectedInternalEvent, } /// A rusty enum for all possible combat events. @@ -214,6 +216,69 @@ pub enum EventKind { /// Note that the tag id is volatile and depends on the game build. Do not rely on the actual /// value of this! Tag { agent_addr: u64, tag_id: i32 }, + + /// The given agent has a new barrier percentage. + /// + /// The percentage is given multiplied by 100, so 95% is given as 9500. + BarrierUpdate { + agent_addr: u64, + percentage: u32, + }, + + /// The arcdps stats have been reset. + /// + /// The given ID is the species ID of the agent that triggered the reset. + StatReset { species_id: u64 }, + + /// When the map instance has been started. + /// + /// The instance age is given in milliseconds. + InstanceStart { + age: u64, + }, + + /// The log's boss agent changed. + LogNpcUpdate { + species_id: u64, + agent_address: u64, + timestamp: u32, + }, + + /// The fractal scale. + FractalScale { scale: u32 }, + + /// Which ruleset is active. + Ruleset { ruleset: Ruleset }, + + /// A squad ground marker has been placed. + /// + /// `x`/`y`/`z` give the coordinates. If they are zero or infinite, the marker has been + /// removed. + /// + /// `marker_index` gives the index of the marker (e.g. 0 is arrow). + SquadMarker { + x: f32, + y: f32, + z: f32, + marker_index: u8, + }, + + /// Version string of the arcdps build. + ArcBuild { + build: String, + }, + + /// Glider state of the given agent. + Glider { + agent_addr: u64, + deployed: bool, + }, + + /// A stun has been broken early. + Stunbreak { + agent_addr: u64, + remaining_duration: i32, + } } /// A higher-level representation of a combat event. @@ -414,7 +479,10 @@ impl TryFrom<&raw::CbtEvent> for Event { }, // The README says "internal use, won't see anywhere", so if we find one, we treat it // as an error. - CbtStateChange::ReplInfo => return Err(FromRawEventError::UnexpectedReplInfo), + CbtStateChange::ReplInfo + | CbtStateChange::IdleEvent => { + return Err(FromRawEventError::UnexpectedInternalEvent) + }, CbtStateChange::StackActive => EventKind::StackActive { agent_addr: raw_event.src_agent, stack_id: raw_event.dst_agent as u32, @@ -424,6 +492,59 @@ impl TryFrom<&raw::CbtEvent> for Event { stack_id: raw_event.padding_end, duration: raw_event.value, }, + CbtStateChange::BarrierUpdate => EventKind::BarrierUpdate { + agent_addr: raw_event.src_agent, + percentage: raw_event.dst_agent as u32, + }, + CbtStateChange::StatReset => EventKind::StatReset { + species_id: raw_event.src_agent + }, + CbtStateChange::InstanceStart => EventKind::InstanceStart { + age: raw_event.src_agent, + }, + CbtStateChange::LogNpcUpdate => EventKind::LogNpcUpdate { + species_id: raw_event.src_agent, + agent_address: raw_event.dst_agent, + timestamp: u32::from_le_bytes(raw_event.value.to_le_bytes()), + }, + CbtStateChange::FractalScale => EventKind::FractalScale { + scale: raw_event.src_agent as u32, + }, + CbtStateChange::Ruleset => EventKind::Ruleset { + ruleset: if raw_event.src_agent & 1 > 0 { + Ruleset::PvE + } else if raw_event.src_agent & 2 > 0 { + Ruleset::WvW + } else if raw_event.src_agent & 4 > 0 { + Ruleset::PvP + } else { + return Err(FromRawEventError::UnknownRuleset); + } + }, + CbtStateChange::SquadMarker => EventKind::SquadMarker { + x: f32::from_bits((raw_event.src_agent >> 32) as u32), + y: f32::from_bits((raw_event.src_agent & 0xffff_ffff) as u32), + z: f32::from_bits((raw_event.dst_agent >> 32) as u32), + marker_index: raw_event.skillid as u8, + }, + CbtStateChange::ArcBuild => { + // Skip 8 bytes because the text starts at src_agent, and not at time. + let data = &get_error_bytes(raw_event)[8..]; + EventKind::ArcBuild { + build: raw::cstr_up_to_nul(data) + .ok_or(FromRawEventError::InvalidText)? + .to_string_lossy() + .into_owned(), + } + }, + CbtStateChange::Glider => EventKind::Glider { + agent_addr: raw_event.src_agent, + deployed: raw_event.value > 0, + }, + CbtStateChange::Stunbreak => EventKind::Stunbreak { + agent_addr: raw_event.src_agent, + remaining_duration: raw_event.value, + }, // XXX: implement proper handling of those events! CbtStateChange::BuffInfo | CbtStateChange::BuffFormula @@ -431,7 +552,16 @@ impl TryFrom<&raw::CbtEvent> for Event { | CbtStateChange::SkillTiming | CbtStateChange::BreakbarState | CbtStateChange::BreakbarPercent - | CbtStateChange::BarrierUpdate => { + | CbtStateChange::RateHealth + | CbtStateChange::IdToGuid + | CbtStateChange::Effect2 + // Probably not too useful for us: + | CbtStateChange::Extension + | CbtStateChange::ApiDelayed + | CbtStateChange::ExtensionCombat + // Retired, would need to find docs first: + | CbtStateChange::Last90BeforeDown + | CbtStateChange::Effect => { return Err(FromRawEventError::UnknownStateChange( raw_event.is_statechange, )) diff --git a/src/gamedata.rs b/src/gamedata.rs index c6f3040..8fcd45f 100644 --- a/src/gamedata.rs +++ b/src/gamedata.rs @@ -8,6 +8,20 @@ use std::{ }; use thiserror::Error; +/// The different rulesets, affecting skill & trait balancing. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum Ruleset { + /// Player-versus-Environment. + /// + /// Active in open world, raids, strikes, fractals, ... + PvE, + /// World-versus-World. + WvW, + /// (Structured) Player-versus-Player. + PvP, +} + /// The game mode in which a log was produced. /// /// Note that the distinction made here is relatively arbitrary, but hopefully still useful. In diff --git a/src/raw/types.rs b/src/raw/types.rs index 1bc3d08..b272c9b 100644 --- a/src/raw/types.rs +++ b/src/raw/types.rs @@ -191,12 +191,85 @@ pub enum CbtStateChange { /// `src_agent` is agent, `value` is float with percent BreakbarPercent, /// `time` is the start of the error string. + /// + /// In modern arcdps, this is called `CBTS_INTEGRITY`, but we leave it for backward + /// compatibility. Error, /// `src_agent` is the agent, `value` is the tag id + /// + /// In modern arcdps, this is called `CBTS_MARKER`, but we leave it for backward compatibility. Tag, /// `src_agent` is at barrier percent, `dst_agent` is the percentage times 10000 (so 99.5% /// will be 9950). BarrierUpdate, + /// `src_agent` is the species id of the agent that triggered the reset. + StatReset, + /// For extension use, not managed by arcdps. + Extension, + /// For events that are deemed unsafe to report real-time. + /// + /// Should not appear in evtc files. + ApiDelayed, + /// Map instance start. + /// + /// `src_agent` are milliseconds since the instance was created. + InstanceStart, + /// Tick health. + /// + /// `src_agent` is 25 - tickrate, when tickrate <= 20 + RateHealth, + /// No longer used. + Last90BeforeDown, + /// No longer used. + Effect, + /// content id to guid association for volatile types + /// + /// src_agent: (uint8_t*)&src_agent is uint8_t[16] guid of content + /// overstack_value: is of enum contentlocal + IdToGuid, + /// Log boss agent changed. + /// + /// src_agent: species id of agent + /// dst_agent: related to agent + /// value: as uint32_t, server unix timestamp + LogNpcUpdate, + /// Internal use. + IdleEvent, + /// For extension use. + ExtensionCombat, + /// Fractal scale. + /// + /// `src_agent` will be the fractal scale. + FractalScale, + /// Play graphical effect. + /// + /// src_agent: related to agent + /// dst_agent: effect at location of agent (if applicable) + /// value: (float*)&value is float[3], location x/y/z (if not at agent location) + /// iff: (uint32_t*)&iff is uint32_t[1], effect duration + /// buffremove: (uint32_t*)&buffremove is uint32_t[1], trackable id of effect. id dst_agent and location is 0/0/0, effect was stopped + /// is_shields: (int16_t*)&is_shields is int16_t[3], orientation x/y/z, values are original*1000 + /// is_flanking: effect is on a non-static platform + Effect2, + /// Ruleset for self. + /// + /// `src_agent` has bit 0 if PvE, bit 1 if WvW and bit 2 if PvP. + Ruleset, + /// Squad ground markers. + /// + /// src_agent: (float*)&src_agent is float[3], x/y/z of marker location. if values are all zero or infinity, this marker is removed + /// skillid: index of marker eg. 0 is arrow + SquadMarker, + /// Arcdps build info. + /// + /// `src_agent` is a null-terminated string matching the full build string. + ArcBuild, + /// Glider status change. + /// + /// `src_agent` is the agent, `value` 1 is deployed and 0 is stowed. + Glider, + /// `src_agent` is the agent, `value` is the remaining duration. + Stunbreak, } /// Combat buff remove type -- cgit v1.2.3