use super::*;
use crate::test_support::entities::{MockEntity, base_entity_fields, set_datamap};
use sdk_raw::entities::datamap::FTYPEDESC_KEY;
use sdk_raw::test_support::entities::{data_map, field};
use std::ffi::{c_int, c_short};
use std::ptr::{null, null_mut};
const BASE_OUTPUTS_OFFSET: usize = 320;
const EMBEDDED_OFFSET: usize = 256;
const OUTPUTS_OFFSET: usize = 128;
const STRING_OFFSET: usize = 192;
fn action(
target: &'static CStr,
input: &'static CStr,
parameter: Option<&'static CStr>,
(delay, times_to_fire): (f32, c_int),
next: *mut sys::CEventAction,
) -> *mut sys::CEventAction {
let string = |string: Option<&'static CStr>| sys::string_t {
pszValue: string.map_or(null(), CStr::as_ptr),
};
Box::into_raw(Box::new(sys::CEventAction {
m_iTarget: string(Some(target)),
m_iTargetInput: string(Some(input)),
m_iParameter: string(parameter),
m_flDelay: delay,
m_nTimesToFire: times_to_fire,
m_iIDStamp: 0,
m_pNext: next,
}))
}
#[test]
fn every_output_is_listed_once_by_name() {
use sys::{_fieldtypes_FIELD_CUSTOM as CUSTOM, _fieldtypes_FIELD_STRING as STRING};
let mut mock = MockEntity::new(3);
let output_size = size_of::<sys::CBaseEntityOutput>();
let mut embedded = field(
c"m_Touching",
sys::_fieldtypes_FIELD_EMBEDDED,
EMBEDDED_OFFSET,
);
embedded.fieldSize = 1;
embedded.td = data_map(
c"CTouching",
vec![key(c"OnStartTouch", CUSTOM, 8, FTYPEDESC_OUTPUT)],
null_mut(),
);
let mut base_fields = base_entity_fields().to_vec();
base_fields.extend([
key(c"ONCAPTEAM1", CUSTOM, BASE_OUTPUTS_OFFSET, FTYPEDESC_OUTPUT),
key(
c"OnUser1",
CUSTOM,
BASE_OUTPUTS_OFFSET + output_size,
FTYPEDESC_OUTPUT,
),
key(
c"OnUser2",
CUSTOM,
BASE_OUTPUTS_OFFSET + 2 * output_size + 4,
FTYPEDESC_OUTPUT,
),
]);
set_datamap(data_map(
c"CTriggerMultiple",
vec![
embedded,
key(c"OnCapTeam1", CUSTOM, OUTPUTS_OFFSET, FTYPEDESC_OUTPUT),
key(c"point_printname", STRING, STRING_OFFSET, 0),
key(
c"onstarttouch",
CUSTOM,
OUTPUTS_OFFSET + output_size,
FTYPEDESC_OUTPUT,
),
],
data_map(c"CBaseEntity", base_fields, null_mut()),
));
let kill = action(c"!activator", c"Kill", None, (0.0, 1), null_mut());
unsafe {
let output = mock
.as_ptr()
.byte_add(EMBEDDED_OFFSET + 8)
.cast::<sys::CBaseEntityOutput>();
(&raw mut (*output).m_ActionList).write(kill);
}
let entity = mock.entity();
let outputs = entity.outputs();
assert_eq!(
outputs,
[
Output {
name: c"OnStartTouch",
actions: vec![OutputAction {
target: c"!activator".into(),
input: c"Kill".into(),
parameter: CString::default(),
delay: 0.0,
times_to_fire: Some(1),
}],
},
Output {
name: c"OnCapTeam1",
actions: Vec::new(),
},
Output {
name: c"OnUser1",
actions: Vec::new(),
},
]
);
for output in &outputs {
assert_eq!(
entity.output_actions(output.name).as_ref(),
Some(&output.actions)
);
}
assert_eq!(entity.output_actions(c"OnUser2"), None);
}
fn key(
name: &'static CStr,
field_type: sys::fieldtype_t,
offset: usize,
flags: c_short,
) -> sys::typedescription_t {
let mut key = field(name, field_type, offset);
key.fieldSize = 1;
key.flags = FTYPEDESC_KEY | flags;
key.externalName = name.as_ptr();
key
}
#[test]
fn outputs_are_listed_up_to_the_most() {
let mut mock = MockEntity::new(3);
let fields = (0..=MAX_OUTPUTS)
.map(|index| {
let name = CString::new(format!("OnCase{index:04}")).unwrap();
key(
Box::leak(name.into_boxed_c_str()),
sys::_fieldtypes_FIELD_CUSTOM,
OUTPUTS_OFFSET,
FTYPEDESC_OUTPUT,
)
})
.collect();
set_datamap(data_map(c"CLogicCase", fields, null_mut()));
let outputs = mock.entity().outputs();
assert_eq!(outputs.len(), MAX_OUTPUTS);
assert_eq!(
outputs.last().map(|output| output.name),
Some(c"OnCase1023")
);
}
#[test]
fn outputs_list_their_actions_in_order() {
let mut mock = MockEntity::new(3);
let mut fields = base_entity_fields().to_vec();
let output_size = size_of::<sys::CBaseEntityOutput>();
fields.extend([
key(
c"OnCapTeam1",
sys::_fieldtypes_FIELD_CUSTOM,
OUTPUTS_OFFSET,
FTYPEDESC_OUTPUT,
),
key(
c"OnCapReset",
sys::_fieldtypes_FIELD_CUSTOM,
OUTPUTS_OFFSET + output_size,
FTYPEDESC_OUTPUT,
),
key(
c"point_printname",
sys::_fieldtypes_FIELD_STRING,
STRING_OFFSET,
0,
),
]);
set_datamap(data_map(c"CTeamControlPoint", fields, null_mut()));
let last = action(c"cap_lights*", c"Skin", None, (0.0, -1), null_mut());
let first = action(c"cap_base", c"Skin", Some(c"1"), (0.5, 2), last);
unsafe {
let output = mock
.as_ptr()
.byte_add(OUTPUTS_OFFSET)
.cast::<sys::CBaseEntityOutput>();
(&raw mut (*output).m_ActionList).write(first);
}
let entity = mock.entity();
assert_eq!(
entity.output_actions(c"oncapteam1"),
Some(vec![
OutputAction {
target: c"cap_base".into(),
input: c"Skin".into(),
parameter: c"1".into(),
delay: 0.5,
times_to_fire: Some(2),
},
OutputAction {
target: c"cap_lights*".into(),
input: c"Skin".into(),
parameter: CString::default(),
delay: 0.0,
times_to_fire: None,
},
])
);
assert_eq!(entity.output_actions(c"OnCapReset"), Some(Vec::new()));
assert_eq!(entity.output_actions(c"point_printname"), None);
assert_eq!(entity.output_actions(c"OnCapTeam2"), None);
}