#[cfg(test)]
#[path = "../tests/entities/props.rs"]
mod tests;
use crate::entities::Entity;
use crate::entities::spawn::{EntitySpawn, SpawnError};
use crate::inputs::{InputError, InputValue};
use crate::interfaces::{ModelInfo, ServerTools};
use crate::math::{QAngle, Vector};
use std::ffi::{CStr, CString, c_int};
const DYNAMIC_PROP_CLASS: &CStr = c"CDynamicProp";
const ORNAMENT_PROP_CLASS: &CStr = c"COrnamentProp";
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum PropError {
#[error("the entity is not a {class:?}")]
WrongClass {
class: &'static CStr,
},
#[error("the model {model:?} is not precached")]
ModelNotPrecached {
model: CString,
},
#[error(transparent)]
Input(#[from] InputError),
#[error(transparent)]
Spawn(#[from] SpawnError),
}
#[doc(alias("prop_dynamic", "CDynamicProp"))]
#[derive(Debug, Clone, Copy)]
pub struct DynamicProp<'s> {
tools: ServerTools<'s>,
entity: Entity<'s>,
}
impl<'s> DynamicProp<'s> {
pub fn new(tools: ServerTools<'s>, entity: Entity<'s>) -> Result<Self, PropError> {
if entity.is_a(DYNAMIC_PROP_CLASS) {
Ok(Self { tools, entity })
} else {
Err(PropError::WrongClass {
class: DYNAMIC_PROP_CLASS,
})
}
}
pub fn spawn(
tools: ServerTools<'s>,
models: ModelInfo<'_>,
spawn: &DynamicPropSpawn<'_>,
) -> Result<Self, PropError> {
if models.model_index(spawn.model).is_none() {
return Err(PropError::ModelNotPrecached {
model: spawn.model.to_owned(),
});
}
let mut prop = EntitySpawn::new(spawn.class.class_name())
.model(spawn.model)
.origin(spawn.origin)
.angles(spawn.angles)
.key(c"solid", &number_value(spawn.solid.to_raw()))
.key(c"skin", &number_value(spawn.skin))
.key(c"StartDisabled", flag_value(spawn.start_disabled));
if let Some(animation) = spawn.animation {
prop = prop.key(c"DefaultAnim", animation);
}
if let Some(name) = spawn.name {
prop = prop.name(name);
}
let entity = unsafe { prop.spawn(tools) }?;
Ok(Self { tools, entity })
}
#[doc(alias("SetAttached"))]
pub fn attach(self, target: Entity<'_>) -> Result<(), PropError> {
self.check_ornament()?;
Ok(self.tools.accept_input(
self.entity,
c"SetAttached",
InputValue::String(c"!activator"),
target,
self.entity,
)?)
}
fn check_ornament(self) -> Result<(), PropError> {
if self.entity.is_a(ORNAMENT_PROP_CLASS) {
Ok(())
} else {
Err(PropError::WrongClass {
class: ORNAMENT_PROP_CLASS,
})
}
}
#[doc(alias("Detach"))]
pub fn detach(self) -> Result<(), PropError> {
self.check_ornament()?;
self.input(c"Detach", InputValue::Void)
}
#[doc(alias("DisableCollision"))]
pub fn disable_collision(self) -> Result<(), PropError> {
self.input(c"DisableCollision", InputValue::Void)
}
#[doc(alias("EnableCollision"))]
pub fn enable_collision(self) -> Result<(), PropError> {
self.input(c"EnableCollision", InputValue::Void)
}
pub const fn entity(self) -> Entity<'s> {
self.entity
}
#[doc(alias("TurnOff", "Disable"))]
pub fn hide(self) -> Result<(), PropError> {
self.input(c"TurnOff", InputValue::Void)
}
fn input(self, name: &CStr, value: InputValue<'_>) -> Result<(), PropError> {
Ok(self
.tools
.accept_input(self.entity, name, value, self.entity, self.entity)?)
}
#[doc(alias("SetAnimation"))]
pub fn set_animation(self, animation: &CStr) -> Result<(), PropError> {
self.input(c"SetAnimation", InputValue::String(animation))
}
#[doc(alias("SetDefaultAnimation", "DefaultAnim"))]
pub fn set_default_animation(self, animation: &CStr) -> Result<(), PropError> {
self.input(c"SetDefaultAnimation", InputValue::String(animation))
}
#[doc(alias("TurnOn", "Enable"))]
pub fn show(self) -> Result<(), PropError> {
self.input(c"TurnOn", InputValue::Void)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum DynamicPropClass {
#[doc(alias("prop_dynamic"))]
#[default]
Dynamic,
#[doc(alias("prop_dynamic_override"))]
Override,
#[doc(alias("prop_dynamic_ornament"))]
Ornament,
}
impl DynamicPropClass {
pub const fn class_name(self) -> &'static CStr {
match self {
Self::Dynamic => c"prop_dynamic",
Self::Override => c"prop_dynamic_override",
Self::Ornament => c"prop_dynamic_ornament",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DynamicPropSpawn<'a> {
pub class: DynamicPropClass,
pub model: &'a CStr,
pub origin: Vector,
pub angles: QAngle,
pub solid: PropSolid,
pub animation: Option<&'a CStr>,
pub skin: c_int,
pub start_disabled: bool,
pub name: Option<&'a CStr>,
}
impl<'a> DynamicPropSpawn<'a> {
pub const fn new(model: &'a CStr, origin: Vector) -> Self {
Self {
class: DynamicPropClass::Dynamic,
model,
origin,
angles: QAngle {
pitch: 0.0,
yaw: 0.0,
roll: 0.0,
},
solid: PropSolid::Vphysics,
animation: None,
skin: 0,
start_disabled: false,
name: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum PropSolid {
#[doc(alias("SOLID_NONE"))]
None,
#[doc(alias("SOLID_BBOX"))]
BoundingBox,
#[doc(alias("SOLID_VPHYSICS"))]
#[default]
Vphysics,
}
impl PropSolid {
pub const fn to_raw(self) -> c_int {
match self {
Self::None => sys::SolidType_t_SOLID_NONE as c_int,
Self::BoundingBox => sys::SolidType_t_SOLID_BBOX as c_int,
Self::Vphysics => sys::SolidType_t_SOLID_VPHYSICS as c_int,
}
}
}
fn flag_value(flag: bool) -> &'static CStr {
if flag { c"1" } else { c"0" }
}
fn number_value(number: c_int) -> CString {
CString::new(number.to_string()).expect("numbers contain no NUL")
}