#[cfg(test)]
#[path = "../tests/entities/spawn.rs"]
mod tests;
use crate::entities::Entity;
use crate::interfaces::ServerTools;
use crate::math::{QAngle, Vector};
use std::ffi::{CStr, CString};
#[doc(alias("CreateEntityByName", "DispatchSpawn", "ent_create"))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EntitySpawn {
activate: bool,
class: CString,
keys: Vec<(CString, CString)>,
non_finite: Option<&'static CStr>,
}
impl EntitySpawn {
pub fn new(class: &CStr) -> Self {
Self {
activate: true,
class: class.to_owned(),
keys: Vec::new(),
non_finite: None,
}
}
pub fn activate(mut self, activate: bool) -> Self {
self.activate = activate;
self
}
pub fn angles(self, angles: QAngle) -> Self {
self.vector_key(c"angles", [angles.pitch, angles.yaw, angles.roll])
}
pub fn class(&self) -> &CStr {
&self.class
}
pub fn key(mut self, key: &CStr, value: &CStr) -> Self {
self.keys.push((key.to_owned(), value.to_owned()));
self
}
pub fn keys(&self) -> impl Iterator<Item = (&CStr, &CStr)> {
self.keys
.iter()
.map(|(key, value)| (key.as_c_str(), value.as_c_str()))
}
pub fn model(self, model: &CStr) -> Self {
self.key(c"model", model)
}
pub fn name(self, name: &CStr) -> Self {
self.key(c"targetname", name)
}
pub fn origin(self, origin: Vector) -> Self {
self.vector_key(c"origin", [origin.x, origin.y, origin.z])
}
pub unsafe fn spawn<'s>(&self, tools: ServerTools<'s>) -> Result<Entity<'s>, SpawnError> {
if let Some(key) = self.non_finite {
return Err(SpawnError::NonFinite {
key: key.to_owned(),
});
}
let entity = unsafe { tools.create_entity_by_name(&self.class) }.ok_or_else(|| {
SpawnError::UnknownClass {
class: self.class.clone(),
}
})?;
for (key, value) in &self.keys {
if !tools.set_key_value(entity, key, value) {
let _ = tools.remove(entity);
return Err(SpawnError::KeyRejected { key: key.clone() });
}
}
unsafe { tools.dispatch_spawn(entity) };
if entity.is_marked_for_deletion() {
return Err(SpawnError::RemovedItself);
}
if self.activate {
unsafe { entity.activate() };
if entity.is_marked_for_deletion() {
return Err(SpawnError::RemovedItself);
}
}
Ok(entity)
}
fn vector_key(self, key: &'static CStr, [x, y, z]: [f32; 3]) -> Self {
if !(x.is_finite() && y.is_finite() && z.is_finite()) {
return Self {
non_finite: Some(key),
..self
};
}
let value = CString::new(format!("{x} {y} {z}")).expect("floats have no NUL");
self.key(key, &value)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum SpawnError {
#[error("the entity refused its {key:?} key")]
KeyRejected {
key: CString,
},
#[error("the {key:?} key has a value that is not finite")]
NonFinite {
key: CString,
},
#[error("the entity removed itself as it spawned")]
RemovedItself,
#[error("the game has no entity class {class:?}")]
UnknownClass {
class: CString,
},
}
impl<'s> Entity<'s> {
#[doc(alias("Activate", "ActivateEntity"))]
pub unsafe fn activate(self) {
unsafe { sdk_raw::entities::spawn::activate(self.as_ptr()) };
}
}