use crate::Server;
use crate::entities::{Entity, EntityHandle};
use crate::math::Vector;
use crate::tf2::custom_damage::CustomDamage;
use sdk_raw::tf2::damage;
use std::fmt;
use std::mem::{MaybeUninit, offset_of};
use std::ptr::NonNull;
#[doc(alias("TF_DMG_CUSTOM_BACKSTAB"))]
#[allow(
clippy::unnecessary_cast,
reason = "`ETFDmgCustom` is `c_int` on Windows but `c_uint` on Linux"
)]
pub const CUSTOM_DAMAGE_BACKSTAB: i32 = sys::ETFDmgCustom_TF_DMG_CUSTOM_BACKSTAB as i32;
#[doc(alias("TF_DMG_CUSTOM_DECAPITATION"))]
#[allow(
clippy::unnecessary_cast,
reason = "`ETFDmgCustom` is `c_int` on Windows but `c_uint` on Linux"
)]
pub const CUSTOM_DAMAGE_DECAPITATION: i32 = sys::ETFDmgCustom_TF_DMG_CUSTOM_DECAPITATION as i32;
#[doc(alias("TF_DMG_CUSTOM_DECAPITATION_BOSS"))]
#[allow(
clippy::unnecessary_cast,
reason = "`ETFDmgCustom` is `c_int` on Windows but `c_uint` on Linux"
)]
pub const CUSTOM_DAMAGE_DECAPITATION_BOSS: i32 =
sys::ETFDmgCustom_TF_DMG_CUSTOM_DECAPITATION_BOSS as i32;
#[doc(alias("TF_DMG_CUSTOM_HEADSHOT"))]
#[allow(
clippy::unnecessary_cast,
reason = "`ETFDmgCustom` is `c_int` on Windows but `c_uint` on Linux"
)]
pub const CUSTOM_DAMAGE_HEADSHOT: i32 = sys::ETFDmgCustom_TF_DMG_CUSTOM_HEADSHOT as i32;
#[doc(alias("TF_DMG_CUSTOM_HEADSHOT_DECAPITATION"))]
#[allow(
clippy::unnecessary_cast,
reason = "`ETFDmgCustom` is `c_int` on Windows but `c_uint` on Linux"
)]
pub const CUSTOM_DAMAGE_HEADSHOT_DECAPITATION: i32 =
sys::ETFDmgCustom_TF_DMG_CUSTOM_HEADSHOT_DECAPITATION as i32;
#[doc(alias("TF_DMG_CUSTOM_MERASMUS_DECAPITATION"))]
#[allow(
clippy::unnecessary_cast,
reason = "`ETFDmgCustom` is `c_int` on Windows but `c_uint` on Linux"
)]
pub const CUSTOM_DAMAGE_MERASMUS_DECAPITATION: i32 =
sys::ETFDmgCustom_TF_DMG_CUSTOM_MERASMUS_DECAPITATION as i32;
#[doc(alias("TF_DMG_CUSTOM_PLASMA"))]
#[allow(
clippy::unnecessary_cast,
reason = "`ETFDmgCustom` is `c_int` on Windows but `c_uint` on Linux"
)]
pub const CUSTOM_DAMAGE_PLASMA: i32 = sys::ETFDmgCustom_TF_DMG_CUSTOM_PLASMA as i32;
#[doc(alias("TF_DMG_CUSTOM_PLASMA_CHARGED"))]
#[allow(
clippy::unnecessary_cast,
reason = "`ETFDmgCustom` is `c_int` on Windows but `c_uint` on Linux"
)]
pub const CUSTOM_DAMAGE_PLASMA_CHARGED: i32 = sys::ETFDmgCustom_TF_DMG_CUSTOM_PLASMA_CHARGED as i32;
#[doc(alias("TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING"))]
#[allow(
clippy::unnecessary_cast,
reason = "`ETFDmgCustom` is `c_int` on Windows but `c_uint` on Linux"
)]
pub const CUSTOM_DAMAGE_TAUNT_BARBARIAN_SWING: i32 =
sys::ETFDmgCustom_TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING as i32;
macro_rules! scalar {
(
$(#[$get_meta:meta])* $get:ident,
$(#[$set_meta:meta])* $set:ident,
$field:ident,
$ty:ty
) => {
scalar!(
$(#[$get_meta])* $get,
$(#[$set_meta])* $set,
$field,
$ty,
|_: $ty| true
);
};
(
$(#[$get_meta:meta])* $get:ident,
$(#[$set_meta:meta])* $set:ident,
$field:ident,
$ty:ty,
$valid:expr
) => {
$(#[$get_meta])*
pub fn $get(&self) -> $ty {
unsafe { (&raw const (*self.raw.as_ptr()).$field).read() }
}
$(#[$set_meta])*
pub fn $set(&mut self, value: $ty) {
assert!(
($valid)(value),
"invalid damage field: {}",
stringify!($field)
);
unsafe { (&raw mut (*self.raw.as_mut_ptr()).$field).write(value) };
}
};
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[doc(alias("ECritType"))]
#[allow(
clippy::unnecessary_cast,
reason = "`ECritType` is `c_int` on Windows but `c_uint` on Linux"
)]
#[repr(i32)]
pub enum CriticalHit {
#[default]
#[doc(alias("CRIT_NONE"))]
None = sys::CTakeDamageInfo_ECritType_CRIT_NONE as i32,
#[doc(alias("CRIT_MINI"))]
Mini = sys::CTakeDamageInfo_ECritType_CRIT_MINI as i32,
#[doc(alias("CRIT_FULL"))]
Full = sys::CTakeDamageInfo_ECritType_CRIT_FULL as i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CriticalPolicy {
pub full: bool,
pub mini: bool,
}
impl CriticalPolicy {
pub const ALLOW_ALL: Self = Self {
full: true,
mini: true,
};
pub const DISABLE_ALL: Self = Self {
full: false,
mini: false,
};
}
impl Default for CriticalPolicy {
fn default() -> Self {
Self::ALLOW_ALL
}
}
#[derive(Debug)]
pub struct DamageEvent<'s> {
pub victim: Entity<'s>,
pub info: DamageInfo,
}
impl<'s> DamageEvent<'s> {
pub unsafe fn from_raw(
_server: Server<'s>,
victim: NonNull<sys::CBaseEntity>,
info: NonNull<sys::CTakeDamageInfo>,
) -> Self {
Self {
victim: unsafe { Entity::from_raw(victim) },
info: unsafe { DamageInfo::copy_from_raw(info) },
}
}
}
#[doc(alias("CTakeDamageInfo"))]
pub struct DamageInfo {
raw: MaybeUninit<sys::CTakeDamageInfo>,
}
impl DamageInfo {
pub const MAX_DAMAGE: f32 = 1_000_000.0;
pub fn new(amount: f32, damage_type: DamageType) -> Self {
let mut result = Self {
raw: MaybeUninit::zeroed(),
};
result.set_amount(amount);
result.set_max_damage(amount);
result.set_base_damage(damage::BASEDAMAGE_NOT_SPECIFIED);
result.set_damage_type(damage_type);
result.set_attacker(EntityHandle::INVALID);
result.set_inflictor(EntityHandle::INVALID);
result.set_weapon(EntityHandle::INVALID);
result.set_bonus_provider(EntityHandle::INVALID);
result.set_ammo_type(sdk_raw::tf2::damage::NO_AMMO_TYPE);
result
}
pub unsafe fn copy_from_raw(raw: NonNull<sys::CTakeDamageInfo>) -> Self {
let mut result = Self {
raw: MaybeUninit::uninit(),
};
unsafe { std::ptr::copy_nonoverlapping(raw.as_ptr(), result.raw.as_mut_ptr(), 1) };
result
}
fn valid_damage(value: f32) -> bool {
value.is_finite() && (0.0..=Self::MAX_DAMAGE).contains(&value)
}
pub fn apply_critical_policy(&mut self, policy: CriticalPolicy) -> bool {
let denied = match self.critical_hit() {
Some(CriticalHit::Full) => !policy.full,
Some(CriticalHit::Mini) => !policy.mini,
_ => false,
};
if denied {
self.set_amount((self.amount() - self.damage_bonus()).max(0.0));
self.set_damage_bonus(0.0);
self.set_bonus_provider(EntityHandle::INVALID);
self.set_incoming_critical(CriticalHit::None);
}
denied
}
pub fn as_ptr(&self) -> *const sys::CTakeDamageInfo {
self.raw.as_ptr()
}
#[doc(alias("GetAttacker"))]
pub fn attacker(&self) -> EntityHandle {
self.handle(offset_of!(sys::CTakeDamageInfo, m_hAttacker))
}
#[doc(alias("GetCritType"))]
pub fn critical_hit(&self) -> Option<CriticalHit> {
match unsafe { (&raw const (*self.as_ptr()).m_eCritType).read() } {
sys::CTakeDamageInfo_ECritType_CRIT_NONE => Some(CriticalHit::None),
sys::CTakeDamageInfo_ECritType_CRIT_MINI => Some(CriticalHit::Mini),
sys::CTakeDamageInfo_ECritType_CRIT_FULL => Some(CriticalHit::Full),
_ => None,
}
}
#[doc(alias("GetDamageCustom"))]
pub fn custom_kind(&self) -> CustomDamage {
CustomDamage::from_raw(self.custom_damage())
}
#[doc(alias("GetDamageForce"))]
pub fn damage_force(&self) -> Vector {
unsafe { (&raw const (*self.as_ptr()).m_vecDamageForce).read() }.into()
}
#[doc(alias("GetDamagePosition"))]
pub fn damage_position(&self) -> Vector {
unsafe { (&raw const (*self.as_ptr()).m_vecDamagePosition).read() }.into()
}
#[doc(alias("GetDamageType"))]
pub fn damage_type(&self) -> DamageType {
DamageType::from_bits_retain(unsafe {
(&raw const (*self.as_ptr()).m_bitsDamageType).read()
} as u32)
}
fn handle(&self, offset: usize) -> EntityHandle {
EntityHandle::from_raw(unsafe {
let handle = self.as_ptr().byte_add(offset).cast::<sys::EHANDLE>();
(&raw const (*handle)._base.m_Index).read()
})
}
#[doc(alias("GetInflictor"))]
pub fn inflictor(&self) -> EntityHandle {
self.handle(offset_of!(sys::CTakeDamageInfo, m_hInflictor))
}
#[doc(alias("GetReportedPosition"))]
pub fn reported_position(&self) -> Vector {
unsafe { (&raw const (*self.as_ptr()).m_vecReportedPosition).read() }.into()
}
#[doc(alias("ScaleDamage"))]
pub fn scale_amount(&mut self, factor: f32) {
self.set_amount(self.amount() * factor);
}
#[doc(alias("SetAttacker"))]
pub fn set_attacker(&mut self, handle: EntityHandle) {
self.set_handle(offset_of!(sys::CTakeDamageInfo, m_hAttacker), handle);
}
#[doc(alias("SetDamageCustom"))]
pub fn set_custom_kind(&mut self, kind: CustomDamage) {
self.set_custom_damage(kind.to_raw());
}
fn set_bonus_provider(&mut self, handle: EntityHandle) {
self.set_handle(
offset_of!(sys::CTakeDamageInfo, m_hDamageBonusProvider),
handle,
);
}
#[doc(alias("SetDamageForce"))]
pub fn set_damage_force(&mut self, force: Vector) {
assert!(force.is_finite(), "invalid damage field: m_vecDamageForce");
unsafe { (&raw mut (*self.raw.as_mut_ptr()).m_vecDamageForce).write(force.into()) };
}
#[doc(alias("SetDamagePosition"))]
pub fn set_damage_position(&mut self, position: Vector) {
assert!(
position.is_finite(),
"invalid damage field: m_vecDamagePosition"
);
unsafe { (&raw mut (*self.raw.as_mut_ptr()).m_vecDamagePosition).write(position.into()) };
}
#[doc(alias("SetDamageType"))]
pub fn set_damage_type(&mut self, value: DamageType) {
unsafe { (&raw mut (*self.raw.as_mut_ptr()).m_bitsDamageType).write(value.bits() as i32) };
}
fn set_handle(&mut self, offset: usize, handle: EntityHandle) {
assert!(
handle
.index()
.is_none_or(|index| index < EntityHandle::SLOTS),
"damage handle index exceeds the entity table"
);
unsafe {
let field = self
.raw
.as_mut_ptr()
.byte_add(offset)
.cast::<sys::EHANDLE>();
(&raw mut (*field)._base.m_Index).write(handle.to_raw());
};
}
#[doc(alias("SetCritType"))]
pub fn set_incoming_critical(&mut self, critical: CriticalHit) {
self.write_critical(critical);
let ordinary = self.damage_type().difference(DamageType::CRITICAL);
self.set_damage_type(if critical == CriticalHit::Full {
ordinary | DamageType::CRITICAL
} else {
ordinary
});
}
#[doc(alias("SetInflictor"))]
pub fn set_inflictor(&mut self, handle: EntityHandle) {
self.set_handle(offset_of!(sys::CTakeDamageInfo, m_hInflictor), handle);
}
#[doc(alias("SetReportedPosition"))]
pub fn set_reported_position(&mut self, position: Vector) {
assert!(
position.is_finite(),
"invalid damage field: m_vecReportedPosition"
);
unsafe { (&raw mut (*self.raw.as_mut_ptr()).m_vecReportedPosition).write(position.into()) };
}
#[doc(alias("SetWeapon"))]
pub fn set_weapon(&mut self, handle: EntityHandle) {
self.set_handle(offset_of!(sys::CTakeDamageInfo, m_hWeapon), handle);
}
#[doc(alias("GetWeapon"))]
pub fn weapon(&self) -> EntityHandle {
self.handle(offset_of!(sys::CTakeDamageInfo, m_hWeapon))
}
fn write_critical(&mut self, critical: CriticalHit) {
unsafe { (&raw mut (*self.raw.as_mut_ptr()).m_eCritType).write(critical as _) };
}
scalar! {
#[doc(alias("GetDamage"))]
amount,
#[doc(alias("SetDamage"))]
set_amount,
m_flDamage,
f32,
Self::valid_damage
}
scalar! {
#[doc(alias("GetMaxDamage"))]
max_damage,
#[doc(alias("SetMaxDamage"))]
set_max_damage,
m_flMaxDamage,
f32,
Self::valid_damage
}
scalar! {
#[doc(alias("GetBaseDamage", "m_flBaseDamage"))]
base_damage,
#[doc(alias("m_flBaseDamage"))]
set_base_damage,
m_flBaseDamage,
f32,
|value: f32| value == damage::BASEDAMAGE_NOT_SPECIFIED || Self::valid_damage(value)
}
scalar! {
#[doc(alias("GetDamageBonus"))]
damage_bonus,
#[doc(alias("SetDamageBonus"))]
set_damage_bonus,
m_flDamageBonus,
f32,
Self::valid_damage
}
scalar! {
#[doc(alias("GetDamageCustom"))]
custom_damage,
#[doc(alias("SetDamageCustom"))]
set_custom_damage,
m_iDamageCustom,
i32
}
scalar! {
#[doc(alias("GetAmmoType"))]
ammo_type,
#[doc(alias("SetAmmoType"))]
set_ammo_type,
m_iAmmoType,
i32
}
scalar! {
#[doc(alias("IsForceFriendlyFire"))]
force_friendly_fire,
#[doc(alias("SetForceFriendlyFire"))]
set_force_friendly_fire,
m_bForceFriendlyFire,
bool
}
}
impl Clone for DamageInfo {
fn clone(&self) -> Self {
unsafe { Self::copy_from_raw(NonNull::from(&self.raw).cast()) }
}
}
impl fmt::Debug for DamageInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DamageInfo")
.field("amount", &self.amount())
.field("damage_type", &self.damage_type())
.field("critical_hit", &self.critical_hit())
.field("attacker", &self.attacker())
.field("weapon", &self.weapon())
.finish_non_exhaustive()
}
}
bitflags::bitflags! {
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DamageType: u32 {
#[doc(alias("DMG_ALWAYSGIB"))]
const ALWAYS_GIB = damage::DMG_ALWAYSGIB as u32;
#[doc(alias("DMG_BLAST"))]
const BLAST = damage::DMG_BLAST as u32;
#[doc(alias("DMG_BUCKSHOT"))]
const BUCKSHOT = damage::DMG_BUCKSHOT as u32;
#[doc(alias("DMG_BULLET"))]
const BULLET = damage::DMG_BULLET as u32;
#[doc(alias("DMG_BURN"))]
const BURN = damage::DMG_BURN as u32;
#[doc(alias("DMG_CLUB"))]
const CLUB = damage::DMG_CLUB as u32;
#[doc(alias("DMG_CRITICAL", "DMG_ACID"))]
const CRITICAL = damage::DMG_CRITICAL as u32;
#[doc(alias("DMG_CRUSH"))]
const CRUSH = damage::DMG_CRUSH as u32;
#[doc(alias("DMG_DIRECT"))]
const DIRECT = damage::DMG_DIRECT as u32;
#[doc(alias("DMG_DONT_COUNT_DAMAGE_TOWARDS_CRIT_RATE", "DMG_DISSOLVE"))]
const DONT_COUNT_DAMAGE_TOWARDS_CRIT_RATE =
damage::DMG_DONT_COUNT_DAMAGE_TOWARDS_CRIT_RATE as u32;
#[doc(alias("DMG_DROWN"))]
const DROWN = damage::DMG_DROWN as u32;
#[doc(alias("DMG_FALL"))]
const FALL = damage::DMG_FALL as u32;
#[doc(alias("DMG_GENERIC"))]
const GENERIC = damage::DMG_GENERIC as u32;
#[doc(alias("DMG_HALF_FALLOFF", "DMG_RADIATION"))]
const HALF_FALLOFF = damage::DMG_HALF_FALLOFF as u32;
#[doc(alias("DMG_IGNITE", "DMG_PLASMA"))]
const IGNITE = damage::DMG_IGNITE as u32;
#[doc(alias("DMG_MELEE", "DMG_BLAST_SURFACE"))]
const MELEE = damage::DMG_MELEE as u32;
#[doc(alias("DMG_NEVERGIB"))]
const NEVER_GIB = damage::DMG_NEVERGIB as u32;
#[doc(alias("DMG_NOCLOSEDISTANCEMOD", "DMG_POISON"))]
const NO_CLOSE_DISTANCE_MOD = damage::DMG_NOCLOSEDISTANCEMOD as u32;
#[doc(alias("DMG_PREVENT_PHYSICS_FORCE"))]
const PREVENT_PHYSICS_FORCE = damage::DMG_PREVENT_PHYSICS_FORCE as u32;
#[doc(alias("DMG_RADIUS_MAX", "DMG_ENERGYBEAM"))]
const RADIUS_MAX = damage::DMG_RADIUS_MAX as u32;
#[doc(alias("DMG_SHOCK"))]
const SHOCK = damage::DMG_SHOCK as u32;
#[doc(alias("DMG_SLASH"))]
const SLASH = damage::DMG_SLASH as u32;
#[doc(alias("DMG_USEDISTANCEMOD", "DMG_SLOWBURN"))]
const USE_DISTANCE_MOD = damage::DMG_USEDISTANCEMOD as u32;
#[doc(alias("DMG_USE_HITLOCATIONS", "DMG_AIRBOAT"))]
const USE_HIT_LOCATIONS = damage::DMG_USE_HITLOCATIONS as u32;
const _ = !0;
}
}
impl DamageType {
#[doc(alias("DMG_IGNORE_DEBUFFS"))]
pub const IGNORE_DEBUFFS: Self = Self::from_bits_retain(damage::DMG_IGNORE_DEBUFFS as u32);
#[doc(alias("DMG_IGNORE_MAXHEALTH"))]
pub const IGNORE_MAX_HEALTH: Self = Self::from_bits_retain(damage::DMG_IGNORE_MAXHEALTH as u32);
}