#[cfg(test)]
#[path = "../../tests/interfaces/temp_entities.rs"]
mod tests;
use crate::NotThreadSafe;
use crate::entities::Entity;
use crate::user_messages::Recipients;
use sdk_raw::interfaces::temp_entities::{
EffectData, MAX_PARTICLE_SYSTEMS, PARTICLE_DISPATCH_FROM_ENTITY,
PARTICLE_DISPATCH_RESET_PARTICLES, PARTICLE_EFFECT, PATTACH_ABSORIGIN,
PATTACH_ABSORIGIN_FOLLOW, PATTACH_POINT, PATTACH_POINT_FOLLOW, PATTACH_ROOTBONE_FOLLOW,
};
use sdk_raw::vcall;
use std::ffi::c_int;
use std::marker::PhantomData;
use std::ops::RangeInclusive;
use std::ptr::NonNull;
const ATTACHMENTS: RangeInclusive<u8> = 1..=15;
#[doc(alias("ParticleAttachment_t"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ParticleAttachment {
#[doc(alias("PATTACH_ABSORIGIN"))]
Origin,
#[doc(alias("PATTACH_ABSORIGIN_FOLLOW"))]
OriginFollow,
#[doc(alias("PATTACH_POINT"))]
Point(u8),
#[doc(alias("PATTACH_POINT_FOLLOW"))]
PointFollow(u8),
#[doc(alias("PATTACH_ROOTBONE_FOLLOW"))]
RootBoneFollow,
}
impl ParticleAttachment {
const fn to_raw(self) -> (c_int, Option<u8>) {
match self {
Self::Origin => (PATTACH_ABSORIGIN, None),
Self::OriginFollow => (PATTACH_ABSORIGIN_FOLLOW, None),
Self::Point(point) => (PATTACH_POINT, Some(point)),
Self::PointFollow(point) => (PATTACH_POINT_FOLLOW, Some(point)),
Self::RootBoneFollow => (PATTACH_ROOTBONE_FOLLOW, None),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ParticleEffect<'a> {
pub system: usize,
pub entity: Entity<'a>,
pub attachment: ParticleAttachment,
pub reset: bool,
}
impl<'a> ParticleEffect<'a> {
pub const fn new(system: usize, entity: Entity<'a>) -> Self {
Self {
system,
entity,
attachment: ParticleAttachment::OriginFollow,
reset: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
pub enum ParticleEffectError {
#[error("model attachment {0} is outside the 1 to 15 clients receive")]
Attachment(u8),
#[error("the entity is not networked, so no client knows it")]
NotNetworked,
#[error("particle system {0} is beyond the 8192 clients receive")]
SystemOutOfRange(usize),
}
#[doc(alias("ITempEntsSystem"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TempEntities<'s> {
raw: NonNull<sys::ITempEntsSystem>,
_scope: PhantomData<&'s ()>,
_not_thread_safe: NotThreadSafe,
}
impl<'s> TempEntities<'s> {
pub(crate) const unsafe fn from_raw(raw: NonNull<sys::ITempEntsSystem>) -> Self {
Self {
raw,
_scope: PhantomData,
_not_thread_safe: PhantomData,
}
}
pub const fn as_ptr(self) -> *mut sys::ITempEntsSystem {
self.raw.as_ptr()
}
#[doc(alias("DispatchParticleEffect", "DispatchEffect"))]
pub fn dispatch_particle_effect(
self,
recipients: &Recipients,
effect: &ParticleEffect<'_>,
) -> Result<(), ParticleEffectError> {
let ParticleEffect {
system,
entity,
attachment,
reset,
} = *effect;
let hit_box = c_int::try_from(system)
.ok()
.filter(|&system| system < MAX_PARTICLE_SYSTEMS)
.ok_or(ParticleEffectError::SystemOutOfRange(system))?;
let edict = entity.edict().ok_or(ParticleEffectError::NotNetworked)?;
let (attach_type, point) = attachment.to_raw();
if let Some(point) = point
&& !ATTACHMENTS.contains(&point)
{
return Err(ParticleEffectError::Attachment(point));
}
if recipients.is_empty() {
return Ok(());
}
let mut data = EffectData::new();
data.origin = entity.position().map_or(data.origin, sys::Vector::from);
data.flags = PARTICLE_DISPATCH_FROM_ENTITY;
data.entity_index = edict.index();
data.attachment_index = c_int::from(point.unwrap_or(0));
data.damage_type = attach_type;
data.hit_box = hit_box;
if reset {
data.flags |= PARTICLE_DISPATCH_RESET_PARTICLES;
}
let filter = recipients.filter();
let this = self.as_ptr();
unsafe {
let pushed = &raw mut (*this)._base.m_nStatusPushed;
pushed.write(pushed.read() + 1);
vcall!(this as sys::ITempEntsSystem__bindgen_vtable => ITempEntsSystem_DispatchEffect(
filter.as_raw(),
0.0,
&raw const data.origin,
PARTICLE_EFFECT.as_ptr(),
data.as_raw(),
));
pushed.write(pushed.read() - 1);
}
Ok(())
}
}