#[cfg(test)]
#[path = "../tests/entities/solid.rs"]
mod tests;
use crate::entities::Entity;
use crate::math::Vector;
use crate::server::{InterfaceError, Server};
use sdk_raw::entities::{
FSOLID_CUSTOMBOXTEST, FSOLID_CUSTOMRAYTEST, FSOLID_FORCE_WORLD_ALIGNED, FSOLID_NOT_SOLID,
FSOLID_NOT_STANDABLE, FSOLID_ROOT_PARENT_ALIGNED, FSOLID_TRIGGER, FSOLID_TRIGGER_TOUCH_DEBRIS,
FSOLID_USE_TRIGGER_BOUNDS, FSOLID_VOLUME_CONTENTS, find_solid_flags_field,
};
use sdk_raw::vcall;
use std::ptr::NonNull;
use std::sync::OnceLock;
static SOLID_FLAGS_OFFSET: OnceLock<usize> = OnceLock::new();
bitflags::bitflags! {
#[doc(alias("SolidFlags_t", "m_usSolidFlags"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct SolidFlags: u16 {
#[doc(alias("FSOLID_CUSTOMBOXTEST"))]
const CUSTOM_BOX_TEST = FSOLID_CUSTOMBOXTEST;
#[doc(alias("FSOLID_CUSTOMRAYTEST"))]
const CUSTOM_RAY_TEST = FSOLID_CUSTOMRAYTEST;
#[doc(alias("FSOLID_FORCE_WORLD_ALIGNED"))]
const FORCE_WORLD_ALIGNED = FSOLID_FORCE_WORLD_ALIGNED;
#[doc(alias("FSOLID_NOT_SOLID"))]
const NOT_SOLID = FSOLID_NOT_SOLID;
#[doc(alias("FSOLID_NOT_STANDABLE"))]
const NOT_STANDABLE = FSOLID_NOT_STANDABLE;
#[doc(alias("FSOLID_ROOT_PARENT_ALIGNED"))]
const ROOT_PARENT_ALIGNED = FSOLID_ROOT_PARENT_ALIGNED;
#[doc(alias("FSOLID_TRIGGER"))]
const TRIGGER = FSOLID_TRIGGER;
#[doc(alias("FSOLID_TRIGGER_TOUCH_DEBRIS"))]
const TRIGGER_TOUCH_DEBRIS = FSOLID_TRIGGER_TOUCH_DEBRIS;
#[doc(alias("FSOLID_USE_TRIGGER_BOUNDS"))]
const USE_TRIGGER_BOUNDS = FSOLID_USE_TRIGGER_BOUNDS;
#[doc(alias("FSOLID_VOLUME_CONTENTS"))]
const VOLUME_CONTENTS = FSOLID_VOLUME_CONTENTS;
const _ = !0;
}
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum SolidFlagsError {
#[error("the game's datamaps do not declare `m_Collision.m_usSolidFlags` as the SDK does")]
UnsupportedLayout,
#[error(transparent)]
Interface(#[from] InterfaceError),
}
#[doc(alias("SolidType_t", "m_nSolidType"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SolidType {
#[doc(alias("SOLID_NONE"))]
None,
#[doc(alias("SOLID_BSP"))]
Bsp,
#[doc(alias("SOLID_BBOX"))]
BoundingBox,
#[doc(alias("SOLID_OBB"))]
OrientedBox,
#[doc(alias("SOLID_OBB_YAW"))]
OrientedBoxYaw,
#[doc(alias("SOLID_CUSTOM"))]
Custom,
#[doc(alias("SOLID_VPHYSICS"))]
Vphysics,
}
impl SolidType {
pub const fn from_raw(value: u8) -> Option<Self> {
Some(match value as sys::SolidType_t {
sys::SolidType_t_SOLID_NONE => Self::None,
sys::SolidType_t_SOLID_BSP => Self::Bsp,
sys::SolidType_t_SOLID_BBOX => Self::BoundingBox,
sys::SolidType_t_SOLID_OBB => Self::OrientedBox,
sys::SolidType_t_SOLID_OBB_YAW => Self::OrientedBoxYaw,
sys::SolidType_t_SOLID_CUSTOM => Self::Custom,
sys::SolidType_t_SOLID_VPHYSICS => Self::Vphysics,
_ => return None,
})
}
pub const fn to_raw(self) -> u8 {
(match self {
Self::None => sys::SolidType_t_SOLID_NONE,
Self::Bsp => sys::SolidType_t_SOLID_BSP,
Self::BoundingBox => sys::SolidType_t_SOLID_BBOX,
Self::OrientedBox => sys::SolidType_t_SOLID_OBB,
Self::OrientedBoxYaw => sys::SolidType_t_SOLID_OBB_YAW,
Self::Custom => sys::SolidType_t_SOLID_CUSTOM,
Self::Vphysics => sys::SolidType_t_SOLID_VPHYSICS,
}) as u8
}
}
impl Entity<'_> {
#[doc(alias("OBBMins", "OBBMaxs", "m_vecMins", "m_vecMaxs"))]
pub fn local_bounds(self) -> Option<(Vector, Vector)> {
let collideable = NonNull::new(unsafe {
vcall!(self.server_entity() => IServerEntity_GetCollideable())
})?;
let mins = NonNull::new(
unsafe { vcall!(collideable.as_ptr() => ICollideable_OBBMins()) }.cast_mut(),
)?;
let maxs = NonNull::new(
unsafe { vcall!(collideable.as_ptr() => ICollideable_OBBMaxs()) }.cast_mut(),
)?;
Some(unsafe { (mins.as_ptr().read().into(), maxs.as_ptr().read().into()) })
}
#[doc(alias("FSOLID_CUSTOMRAYTEST", "m_usSolidFlags"))]
pub fn set_custom_ray_test(
self,
server: Server<'_>,
enabled: bool,
) -> Result<bool, SolidFlagsError> {
self.set_solid_flag(server, SolidFlags::CUSTOM_RAY_TEST, enabled)
}
fn set_solid_flag(
self,
server: Server<'_>,
flag: SolidFlags,
enabled: bool,
) -> Result<bool, SolidFlagsError> {
let offset = self.solid_flags_offset()?;
let engine = server.valve_engine()?;
let flags = self.solid_flags_at(offset);
let mut changed = flags;
changed.set(flag, enabled);
if changed != flags {
unsafe {
self.as_ptr()
.byte_add(offset)
.cast::<u16>()
.write(changed.bits())
};
self.network_state_changed(engine, offset);
}
Ok(flags.contains(flag))
}
#[doc(alias("FSOLID_TRIGGER_TOUCH_DEBRIS", "m_usSolidFlags"))]
pub fn set_trigger_touch_debris(
self,
server: Server<'_>,
enabled: bool,
) -> Result<bool, SolidFlagsError> {
self.set_solid_flag(server, SolidFlags::TRIGGER_TOUCH_DEBRIS, enabled)
}
#[doc(alias("GetSolidFlags", "m_usSolidFlags"))]
pub fn solid_flags(self) -> Result<SolidFlags, SolidFlagsError> {
Ok(self.solid_flags_at(self.solid_flags_offset()?))
}
fn solid_flags_at(self, offset: usize) -> SolidFlags {
SolidFlags::from_bits_retain(unsafe { self.as_ptr().byte_add(offset).cast::<u16>().read() })
}
fn solid_flags_offset(self) -> Result<usize, SolidFlagsError> {
if let Some(&offset) = SOLID_FLAGS_OFFSET.get() {
return Ok(offset);
}
let offset =
find_solid_flags_field(self.data_maps()).ok_or(SolidFlagsError::UnsupportedLayout)?;
Ok(*SOLID_FLAGS_OFFSET.get_or_init(|| offset))
}
#[doc(alias("GetSolid", "m_nSolidType", "SolidType_t"))]
pub fn solid_type(self) -> Option<SolidType> {
let collideable = NonNull::new(unsafe {
vcall!(self.server_entity() => IServerEntity_GetCollideable())
})?;
let solid = unsafe { vcall!(collideable.as_ptr() => ICollideable_GetSolid()) };
u8::try_from(solid).ok().and_then(SolidType::from_raw)
}
}