#[cfg(test)]
#[path = "../tests/entities/outputs.rs"]
mod tests;
use crate::entities::Entity;
use sdk_raw::entities::datamap::{DataField, FTYPEDESC_OUTPUT};
use sdk_raw::util::cstr::copy_cstr;
use std::collections::HashSet;
use std::ffi::{CStr, CString};
use std::ptr::NonNull;
pub const MAX_ACTIONS: usize = 4096;
pub const MAX_OUTPUTS: usize = 1024;
#[doc(alias("CBaseEntityOutput"))]
#[derive(Debug, Clone, PartialEq)]
pub struct Output<'s> {
pub name: &'s CStr,
pub actions: Vec<OutputAction>,
}
#[doc(alias("CEventAction"))]
#[derive(Debug, Clone, PartialEq)]
pub struct OutputAction {
pub target: CString,
pub input: CString,
pub parameter: CString,
pub delay: f32,
pub times_to_fire: Option<u32>,
}
impl<'s> Entity<'s> {
#[doc(alias("CBaseEntityOutput", "m_ActionList"))]
pub fn output_actions(self, name: &CStr) -> Option<Vec<OutputAction>> {
let (field, offset) = self.data_maps().find_key_field(name.to_bytes())?;
unsafe { self.output_actions_at(field, offset) }
}
unsafe fn output_actions_at(
self,
field: &DataField,
offset: usize,
) -> Option<Vec<OutputAction>> {
let is_output = field.flags & FTYPEDESC_OUTPUT != 0
&& field.fieldType == sys::_fieldtypes_FIELD_CUSTOM
&& offset.is_multiple_of(align_of::<sys::CBaseEntityOutput>());
if !is_output {
return None;
}
let mut next = unsafe {
let output = self
.as_ptr()
.byte_add(offset)
.cast::<sys::CBaseEntityOutput>();
(&raw const (*output).m_ActionList).read()
};
let mut actions = Vec::new();
while let Some(action) = NonNull::new(next)
&& actions.len() < MAX_ACTIONS
{
let action = unsafe { action.read() };
let (target, input, parameter) = unsafe {
(
copy_string(action.m_iTarget),
copy_string(action.m_iTargetInput),
copy_string(action.m_iParameter),
)
};
actions.push(OutputAction {
target,
input,
parameter,
delay: action.m_flDelay,
times_to_fire: u32::try_from(action.m_nTimesToFire).ok(),
});
next = action.m_pNext;
}
Some(actions)
}
#[doc(alias("CBaseEntityOutput", "m_ActionList"))]
pub fn outputs(self) -> Vec<Output<'s>> {
let mut names = HashSet::new();
let mut outputs = Vec::new();
for (field, offset) in self.data_maps().key_fields() {
let Some(name) = field.external_name() else {
continue;
};
if !names.insert(name.to_bytes().to_ascii_lowercase()) {
continue;
}
let Some(offset) = offset else {
continue;
};
if let Some(actions) = unsafe { self.output_actions_at(field, offset) } {
outputs.push(Output { name, actions });
if outputs.len() == MAX_OUTPUTS {
break;
}
}
}
outputs
}
}
unsafe fn copy_string(string: sys::string_t) -> CString {
unsafe { copy_cstr(string.pszValue) }.unwrap_or_default()
}