#[cfg(test)]
#[path = "../tests/entities/fields.rs"]
mod tests;
use crate::entities::{Entity, EntityHandle};
use crate::interfaces::ValveEngine;
use crate::math::{Color32, QAngle, Vector};
use sdk_raw::entities::datamap::{DataField, DataMaps};
use std::ffi::{CStr, CString, c_char, c_int};
use std::marker::PhantomData;
use std::sync::OnceLock;
const MAX_EMBEDDING_DEPTH: usize = DataMaps::MAX_EMBEDDING_DEPTH;
pub(super) struct BaseField<T> {
name: &'static CStr,
field_type: sys::fieldtype_t,
offset: OnceLock<usize>,
_value: PhantomData<fn() -> T>,
}
impl<T: FieldValue> BaseField<T> {
pub(super) const fn new(name: &'static CStr, field_type: sys::fieldtype_t) -> Self {
Self {
name,
field_type,
offset: OnceLock::new(),
_value: PhantomData,
}
}
pub(super) fn offset(&self, entity: Entity<'_>) -> Result<usize, FieldError> {
debug_assert!(T::FIELD_TYPES.contains(&self.field_type));
if let Some(&offset) = self.offset.get() {
return Ok(offset);
}
let offset = entity
.find_base_entity_field(self.name, self.field_type, size_of::<T::Stored>())
.ok_or_else(|| FieldError::NotFound {
name: self.name.to_owned(),
})?;
Ok(*self.offset.get_or_init(|| offset))
}
pub(super) fn read(&self, entity: Entity<'_>) -> Result<T, FieldError> {
let offset = self.offset(entity)?;
let stored = unsafe { entity.as_ptr().byte_add(offset).cast::<T::Stored>().read() };
Ok(T::from_stored(stored))
}
pub(super) fn write(
&self,
engine: ValveEngine<'_>,
entity: Entity<'_>,
value: T,
) -> Result<(), FieldError> {
let offset = self.offset(entity)?;
unsafe {
entity
.as_ptr()
.byte_add(offset)
.cast::<T::Stored>()
.write(value.into_stored())
};
entity.network_state_changed(engine, offset);
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum FieldError {
#[error("the entity's datamaps declare no member {name:?}")]
NotFound {
name: CString,
},
#[error("member {name:?} has {count} elements, so has no element {element}")]
OutOfBounds {
name: CString,
count: usize,
element: usize,
},
#[error("member {name:?} is declared as field type {field_type}, not as the value's")]
WrongType {
name: CString,
field_type: sys::fieldtype_t,
},
}
pub trait FieldValue: sealed::Sealed + Copy + 'static {}
mod sealed {
pub trait Sealed: Sized {
type Stored: Copy;
const FIELD_TYPES: &'static [sys::fieldtype_t];
fn from_stored(stored: Self::Stored) -> Self;
fn into_stored(self) -> Self::Stored;
}
}
macro_rules! stored_as_is {
($($ty:ty => [$($field_type:ident),+ $(,)?]),+ $(,)?) => {$(
impl FieldValue for $ty {}
impl sealed::Sealed for $ty {
type Stored = Self;
const FIELD_TYPES: &'static [sys::fieldtype_t] = &[$(sys::$field_type),+];
fn from_stored(stored: Self) -> Self {
stored
}
fn into_stored(self) -> Self {
self
}
}
)+};
}
stored_as_is!(
bool => [_fieldtypes_FIELD_BOOLEAN],
u8 => [_fieldtypes_FIELD_CHARACTER],
i16 => [_fieldtypes_FIELD_SHORT],
c_int => [
_fieldtypes_FIELD_INTEGER,
_fieldtypes_FIELD_TICK,
_fieldtypes_FIELD_MODELINDEX,
_fieldtypes_FIELD_MATERIALINDEX,
],
f32 => [_fieldtypes_FIELD_FLOAT, _fieldtypes_FIELD_TIME],
);
#[derive(Clone, Copy)]
pub(crate) struct PooledString(pub(crate) *const c_char);
impl FieldValue for PooledString {}
impl sealed::Sealed for PooledString {
type Stored = sys::string_t;
const FIELD_TYPES: &'static [sys::fieldtype_t] = &[
sys::_fieldtypes_FIELD_STRING,
sys::_fieldtypes_FIELD_MODELNAME,
sys::_fieldtypes_FIELD_SOUNDNAME,
];
fn from_stored(stored: sys::string_t) -> Self {
Self(stored.pszValue)
}
fn into_stored(self) -> sys::string_t {
sys::string_t { pszValue: self.0 }
}
}
impl FieldValue for Color32 {}
impl sealed::Sealed for Color32 {
type Stored = sys::color32;
const FIELD_TYPES: &'static [sys::fieldtype_t] = &[sys::_fieldtypes_FIELD_COLOR32];
fn from_stored(stored: sys::color32) -> Self {
stored.into()
}
fn into_stored(self) -> sys::color32 {
self.into()
}
}
impl<'s> Entity<'s> {
pub fn data_element<T: FieldValue>(self, name: &CStr, element: usize) -> Result<T, FieldError> {
let offset = self.data_element_offset::<T>(name, element)?;
let stored = unsafe { self.as_ptr().byte_add(offset).cast::<T::Stored>().read() };
Ok(T::from_stored(stored))
}
fn data_element_offset<T: FieldValue>(
self,
name: &CStr,
element: usize,
) -> Result<usize, FieldError> {
let (field, offset) =
find_field(self.data_maps(), name, 0).ok_or_else(|| FieldError::NotFound {
name: name.to_owned(),
})?;
let wrong_type = || FieldError::WrongType {
name: name.to_owned(),
field_type: field.fieldType,
};
let count = usize::from(field.fieldSize);
let bytes = usize::try_from(field.fieldSizeInBytes).map_err(|_| wrong_type())?;
let size = size_of::<T::Stored>();
if !T::FIELD_TYPES.contains(&field.fieldType)
|| count == 0
|| bytes != size * count
|| !offset.is_multiple_of(align_of::<T::Stored>())
{
return Err(wrong_type());
}
if element >= count {
return Err(FieldError::OutOfBounds {
name: name.to_owned(),
count,
element,
});
}
Ok(offset + element * size)
}
#[doc(alias("GetEntProp", "GetEntPropFloat", "GetEntPropEnt", "GetEntPropVector"))]
pub fn data_field<T: FieldValue>(self, name: &CStr) -> Result<T, FieldError> {
self.data_element(name, 0)
}
pub fn set_data_element<T: FieldValue>(
self,
engine: ValveEngine<'_>,
name: &CStr,
element: usize,
value: T,
) -> Result<(), FieldError> {
let offset = self.data_element_offset::<T>(name, element)?;
unsafe {
self.as_ptr()
.byte_add(offset)
.cast::<T::Stored>()
.write(value.into_stored())
};
self.network_state_changed(engine, offset);
Ok(())
}
#[doc(alias("SetEntProp", "SetEntPropFloat", "SetEntPropEnt", "SetEntPropVector"))]
pub fn set_data_field<T: FieldValue>(
self,
engine: ValveEngine<'_>,
name: &CStr,
value: T,
) -> Result<(), FieldError> {
self.set_data_element(engine, name, 0, value)
}
}
impl FieldValue for EntityHandle {}
impl sealed::Sealed for EntityHandle {
type Stored = u32;
const FIELD_TYPES: &'static [sys::fieldtype_t] = &[sys::_fieldtypes_FIELD_EHANDLE];
fn from_stored(stored: u32) -> Self {
Self::from_raw(stored)
}
fn into_stored(self) -> u32 {
self.to_raw()
}
}
impl FieldValue for QAngle {}
impl sealed::Sealed for QAngle {
type Stored = sys::QAngle;
const FIELD_TYPES: &'static [sys::fieldtype_t] = &[sys::_fieldtypes_FIELD_VECTOR];
fn from_stored(stored: sys::QAngle) -> Self {
stored.into()
}
fn into_stored(self) -> sys::QAngle {
self.into()
}
}
impl FieldValue for Vector {}
impl sealed::Sealed for Vector {
type Stored = sys::Vector;
const FIELD_TYPES: &'static [sys::fieldtype_t] = &[
sys::_fieldtypes_FIELD_VECTOR,
sys::_fieldtypes_FIELD_POSITION_VECTOR,
];
fn from_stored(stored: sys::Vector) -> Self {
stored.into()
}
fn into_stored(self) -> sys::Vector {
self.into()
}
}
fn find_field<'a>(maps: DataMaps<'a>, name: &CStr, depth: usize) -> Option<(&'a DataField, usize)> {
for map in maps {
for field in map.fields() {
if field.fieldType == sys::_fieldtypes_FIELD_EMBEDDED
&& field.fieldSize == 1
&& depth < MAX_EMBEDDING_DEPTH
&& let Some((found, inner)) = find_field(field.embedded(), name, depth + 1)
{
return Some((found, field.offset()?.checked_add(inner)?));
}
if field.name() == Some(name) {
return Some((field, field.offset()?));
}
}
}
None
}