use super::raw;
use std::convert::TryFrom;
use std::io;
use byteorder::{BigEndian, WriteBytesExt};
use getset::{CopyGetters, Getters};
use num_traits::FromPrimitive;
use thiserror::Error;
#[derive(Clone, Debug, Error)]
pub enum FromRawEventError {
#[error("event contains an unknown state change: {0:?}")]
UnknownStateChange(raw::CbtStateChange),
#[error("event contains an unknown damage event")]
UnknownDamageEvent,
#[error("the event contains invalid text")]
InvalidText,
}
#[derive(Clone, Debug, PartialEq)]
pub enum EventKind {
EnterCombat { agent_addr: u64, subgroup: u64 },
ExitCombat { agent_addr: u64 },
ChangeUp { agent_addr: u64 },
ChangeDown { agent_addr: u64 },
ChangeDead { agent_addr: u64 },
Spawn { agent_addr: u64 },
Despawn { agent_addr: u64 },
HealthUpdate {
agent_addr: u64,
health: u16,
},
LogStart {
server_timestamp: u32,
local_timestamp: u32,
},
LogEnd {
server_timestamp: u32,
local_timestamp: u32,
},
WeaponSwap { agent_addr: u64, set: WeaponSet },
MaxHealthUpdate { agent_addr: u64, max_health: u64 },
PointOfView { agent_addr: u64 },
Language { language: raw::Language },
Build { build: u64 },
ShardId { shard_id: u64 },
Reward { reward_id: u64, reward_type: i32 },
SkillUse {
source_agent_addr: u64,
skill_id: u32,
activation: Activation,
},
ConditionTick {
source_agent_addr: u64,
destination_agent_addr: u64,
condition_id: u32,
damage: i32,
},
InvulnTick {
source_agent_addr: u64,
destination_agent_addr: u64,
condition_id: u32,
},
Physical {
source_agent_addr: u64,
destination_agent_addr: u64,
skill_id: u32,
damage: i32,
result: raw::CbtResult,
},
BuffApplication {
source_agent_addr: u64,
destination_agent_addr: u64,
buff_id: u32,
duration: i32,
overstack: u32,
},
BuffRemove {
source_agent_addr: u64,
destination_agent_addr: u64,
buff_id: u32,
total_duration: i32,
longest_stack: i32,
removal: raw::CbtBuffRemove,
},
Position {
agent_addr: u64,
x: f32,
y: f32,
z: f32,
},
Velocity {
agent_addr: u64,
x: f32,
y: f32,
z: f32,
},
Facing { agent_addr: u64, x: f32, y: f32 },
TeamChange { agent_addr: u64, team_id: u64 },
AttackTarget {
agent_addr: u64,
parent_agent_addr: u64,
targetable: bool,
},
Targetable { agent_addr: u64, targetable: bool },
MapId { map_id: u64 },
Guild {
source_agent_addr: u64,
raw_bytes: [u8; 16],
api_guild_id: Option<String>,
},
Error { text: String },
}
#[derive(Clone, Debug, PartialEq, CopyGetters, Getters)]
pub struct Event {
#[get_copy = "pub"]
time: u64,
#[get = "pub"]
kind: EventKind,
#[get_copy = "pub"]
is_ninety: bool,
#[get_copy = "pub"]
is_fifty: bool,
#[get_copy = "pub"]
is_moving: bool,
#[get_copy = "pub"]
is_flanking: bool,
#[get_copy = "pub"]
is_shields: bool,
}
impl TryFrom<raw::CbtEvent> for Event {
type Error = FromRawEventError;
fn try_from(raw_event: raw::CbtEvent) -> Result<Self, Self::Error> {
Event::try_from(&raw_event)
}
}
impl TryFrom<&raw::CbtEvent> for Event {
type Error = FromRawEventError;
fn try_from(raw_event: &raw::CbtEvent) -> Result<Self, Self::Error> {
use raw::CbtStateChange;
let kind = match raw_event.is_statechange {
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::Guild => EventKind::Guild {
source_agent_addr: raw_event.src_agent,
raw_bytes: get_guild_id_bytes(raw_event),
api_guild_id: get_api_guild_string(&get_guild_id_bytes(raw_event)),
},
CbtStateChange::Position => EventKind::Position {
agent_addr: raw_event.src_agent,
x: f32::from_bits((raw_event.dst_agent >> 32) as u32),
y: f32::from_bits((raw_event.dst_agent & 0xffff_ffff) as u32),
z: f32::from_bits(raw_event.value as u32),
},
CbtStateChange::Velocity => EventKind::Velocity {
agent_addr: raw_event.src_agent,
x: f32::from_bits((raw_event.dst_agent >> 32) as u32),
y: f32::from_bits((raw_event.dst_agent & 0xffff_ffff) as u32),
z: f32::from_bits(raw_event.value as u32),
},
CbtStateChange::Facing => EventKind::Facing {
agent_addr: raw_event.src_agent,
x: f32::from_bits((raw_event.dst_agent >> 32) as u32),
y: f32::from_bits((raw_event.dst_agent & 0xffff_ffff) as u32),
},
CbtStateChange::MapId => EventKind::MapId {
map_id: raw_event.src_agent,
},
CbtStateChange::TeamChange => EventKind::TeamChange {
agent_addr: raw_event.src_agent,
team_id: raw_event.dst_agent,
},
CbtStateChange::AttackTarget => EventKind::AttackTarget {
agent_addr: raw_event.src_agent,
parent_agent_addr: raw_event.dst_agent,
targetable: raw_event.value != 0,
},
CbtStateChange::Targetable => EventKind::Targetable {
agent_addr: raw_event.src_agent,
targetable: raw_event.dst_agent != 0,
},
CbtStateChange::Error => {
let data = get_error_bytes(&raw_event);
EventKind::Error {
text: raw::cstr_up_to_nul(&data)
.ok_or(FromRawEventError::InvalidText)?
.to_string_lossy()
.into_owned(),
}
}
CbtStateChange::BuffInitial
| CbtStateChange::ReplInfo
| CbtStateChange::StackActive
| CbtStateChange::StackReset
| CbtStateChange::BuffInfo
| CbtStateChange::BuffFormula
| CbtStateChange::SkillInfo
| CbtStateChange::SkillTiming
| CbtStateChange::BreakbarState
| CbtStateChange::BreakbarPercent => {
return Err(FromRawEventError::UnknownStateChange(
raw_event.is_statechange,
))
}
CbtStateChange::None => check_activation(raw_event)?,
};
Ok(Event {
time: raw_event.time,
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) -> Result<EventKind, FromRawEventError> {
use raw::CbtActivation;
match raw_event.is_activation {
CbtActivation::None => check_buffremove(raw_event),
activation => Ok(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) -> Result<EventKind, FromRawEventError> {
use raw::CbtBuffRemove;
match raw_event.is_buffremove {
CbtBuffRemove::None => check_damage(raw_event),
removal => Ok(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) -> Result<EventKind, FromRawEventError> {
if raw_event.buff == 0 && raw_event.iff == raw::IFF::Foe && raw_event.dst_agent != 0 {
Ok(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
{
Ok(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 {
Ok(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 {
Ok(EventKind::InvulnTick {
source_agent_addr: raw_event.src_agent,
destination_agent_addr: raw_event.dst_agent,
condition_id: raw_event.skillid,
})
} else {
Err(FromRawEventError::UnknownDamageEvent)
}
}
fn get_guild_id_bytes(raw_event: &raw::CbtEvent) -> [u8; 16] {
let mut result = [0; 16];
let mut cursor = io::Cursor::new(&mut result as &mut [u8]);
cursor.write_u64::<BigEndian>(raw_event.dst_agent).unwrap();
cursor.write_i32::<BigEndian>(raw_event.value).unwrap();
cursor.write_i32::<BigEndian>(raw_event.buff_dmg).unwrap();
result
}
fn get_api_guild_string(bytes: &[u8; 16]) -> Option<String> {
if bytes == &[0; 16] {
return None;
}
static PACKS: &[&[usize]] = &[
&[4, 5, 6, 7],
&[2, 3],
&[0, 1],
&[11, 10],
&[9, 8, 15, 14, 13, 12],
];
let result = PACKS
.iter()
.map(|p| p.iter().map(|i| format!("{:02X}", bytes[*i])).collect())
.collect::<Vec<String>>()
.join("-");
Some(result)
}
fn get_error_bytes(raw_event: &raw::CbtEvent) -> [u8; 32] {
let mut result = [0; 32];
let mut cursor = io::Cursor::new(&mut result as &mut [u8]);
cursor.write_u64::<BigEndian>(raw_event.time).unwrap();
cursor.write_u64::<BigEndian>(raw_event.src_agent).unwrap();
cursor.write_u64::<BigEndian>(raw_event.dst_agent).unwrap();
cursor.write_i32::<BigEndian>(raw_event.value).unwrap();
cursor.write_i32::<BigEndian>(raw_event.buff_dmg).unwrap();
result
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum WeaponSet {
Water0,
Water1,
Land0,
Land1,
Unknown(u8),
}
impl WeaponSet {
fn from_u64(value: u64) -> WeaponSet {
match value {
0 => WeaponSet::Water0,
1 => WeaponSet::Water1,
4 => WeaponSet::Land0,
5 => WeaponSet::Land1,
_ => WeaponSet::Unknown(value as u8),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Activation {
Quickness(i32),
Normal(i32),
CancelFire(i32),
CancelCancel(i32),
Reset,
}