use super::leak;
use sdk_raw::players::LIFE_ALIVE;
use sdk_raw::test_support::entities::field;
use sdk_raw::test_support::{mock_vtable, unexpected_call};
use std::alloc::{Layout, alloc_zeroed, handle_alloc_error};
use std::cell::{Cell, RefCell};
use std::ffi::{CStr, CString, c_char, c_int, c_void};
use std::mem::offset_of;
use std::ptr::{self, null_mut};
pub const MOCK_EFLAGS_OFFSET: usize = 32;
const MOCK_HAMMER_ID_OFFSET: usize = 56;
const MOCK_HANDLE_OFFSET: usize = 40;
pub const MOCK_HEALTH_OFFSET: usize = 64;
pub const MOCK_LIFE_STATE_OFFSET: usize = 72;
pub const MOCK_MAX_HEALTH_BONUS: c_int = 25;
pub const MOCK_MAX_HEALTH_OFFSET: usize = 68;
pub const MOCK_NAME_OFFSET: usize = 48;
pub const MOCK_STATE_OFFSET: usize = 128;
pub const MOCK_TAKE_DAMAGE_OFFSET: usize = 73;
#[derive(Debug, Clone, PartialEq)]
pub struct ReceivedInput {
pub target: *mut sys::CBaseEntity,
pub name: CString,
pub activator: *mut sys::CBaseEntity,
pub caller: *mut sys::CBaseEntity,
pub field_type: sys::fieldtype_t,
pub payload: [u8; 12],
pub string: *const c_char,
pub handle: u32,
pub output_id: c_int,
}
thread_local! {
static ACCEPTS: Cell<bool> = const { Cell::new(true) };
static ACTIVATIONS: RefCell<Vec<*mut sys::CBaseEntity>> = const { RefCell::new(Vec::new()) };
static ACTIVATION_REMOVES: Cell<bool> = const { Cell::new(false) };
static COLLIDEABLE: Cell<*mut sys::ICollideable> = const { Cell::new(null_mut()) };
static COLLISION_GROUP: Cell<c_int> = const { Cell::new(sdk_raw::entities::COLLISION_GROUP_NONE) };
static DATA_MAP: Cell<*mut sys::datamap_t> = const { Cell::new(null_mut()) };
static EDICT: Cell<*mut sys::edict_t> = const { Cell::new(null_mut()) };
static INPUTS: RefCell<Vec<ReceivedInput>> = const { RefCell::new(Vec::new()) };
static MODELS_SET: RefCell<Vec<(*mut sys::CBaseEntity, CString)>> = const { RefCell::new(Vec::new()) };
static NETWORKABLE: Cell<*mut sys::IServerNetworkable> = const { Cell::new(null_mut()) };
static OBB_MAXS: Cell<sys::Vector> = const { Cell::new(sys::Vector { x: 1.0, y: 1.0, z: 1.0 }) };
static OBB_MINS: Cell<sys::Vector> = const { Cell::new(sys::Vector { x: -1.0, y: -1.0, z: -1.0 }) };
static ORIGIN: Cell<sys::Vector> = const { Cell::new(sys::Vector { x: 1.0, y: 2.0, z: 3.0 }) };
static SERVER_CLASS: Cell<*mut sys::ServerClass> = const { Cell::new(null_mut()) };
static SOLID_TYPE: Cell<sys::SolidType_t> = const { Cell::new(sys::SolidType_t_SOLID_BBOX) };
static TAKE_HEALTH_CALLS: RefCell<Vec<(f32, c_int)>> = const { RefCell::new(Vec::new()) };
static TELEPORTS: Cell<usize> = const { Cell::new(0) };
static TRANSMIT_STATE_UPDATES: Cell<usize> = const { Cell::new(0) };
}
pub struct MockEntity {
storage: *mut usize,
}
#[repr(C)]
pub struct MockState {
pub debug_overlays: c_int,
pub effects: c_int,
pub flags: c_int,
pub friction: f32,
pub gravity: f32,
pub move_parent: u32,
pub team: c_int,
pub spawn_flags: c_int,
pub render_color: sys::color32,
pub model_index: i16,
pub move_type: u8,
pub render_mode: u8,
pub transmit_state_owners: u8,
pub parent_name: sys::string_t,
pub model_name: sys::string_t,
pub abs_velocity: sys::Vector,
pub velocity: sys::Vector,
pub move_collide: u8,
pub parent_attachment: u8,
pub physics_object: *mut c_void,
pub abs_rotation: sys::QAngle,
pub origin: sys::Vector,
pub rotation: sys::QAngle,
pub move_child: u32,
pub move_peer: u32,
}
impl MockEntity {
pub fn vtable_slots() -> usize {
sdk_raw::entities::TF2_TELEPORT_SLOT
.max(sdk_raw::entities::GET_DATA_DESC_MAP_SLOT)
.max(sdk_raw::entities::ACCEPT_INPUT_SLOT)
.max(sdk_raw::entities::health::TF2_GET_MAX_HEALTH_SLOT)
.max(sdk_raw::entities::health::TAKE_HEALTH_SLOT)
.max(sdk_raw::entities::health::IS_ALIVE_SLOT)
.max(sdk_raw::transmit::UPDATE_TRANSMIT_STATE_SLOT)
.max(sdk_raw::entities::spawn::ACTIVATE_SLOT)
.max(sdk_raw::entities::spawn::SET_MODEL_SLOT)
+ 1
}
pub fn new(handle: u32) -> Self {
Self::with_layout(handle, Layout::new::<[usize; 64]>())
}
pub fn with_layout(handle: u32, layout: Layout) -> Self {
let mut vtable = vec![unexpected_call as *const (); Self::vtable_slots()];
let slot = |field: usize| field / size_of::<usize>();
vtable[slot(offset_of!(
sys::IServerEntity__bindgen_vtable,
IServerEntity_GetCollideable
))] = get_collideable as *const ();
vtable[slot(offset_of!(
sys::IServerEntity__bindgen_vtable,
IServerEntity_GetNetworkable
))] = get_networkable as *const ();
vtable[slot(offset_of!(
sys::IServerEntity__bindgen_vtable,
IServerEntity_GetRefEHandle
))] = get_handle as *const ();
vtable[sdk_raw::entities::TF2_TELEPORT_SLOT] = teleport_entity as *const ();
vtable[sdk_raw::entities::GET_DATA_DESC_MAP_SLOT] = get_datamap as *const ();
vtable[sdk_raw::entities::ACCEPT_INPUT_SLOT] = accept_input as *const ();
vtable[sdk_raw::entities::health::TF2_GET_MAX_HEALTH_SLOT] = get_max_health as *const ();
vtable[sdk_raw::entities::health::TAKE_HEALTH_SLOT] = take_health as *const ();
vtable[sdk_raw::entities::health::IS_ALIVE_SLOT] = is_alive as *const ();
vtable[sdk_raw::transmit::UPDATE_TRANSMIT_STATE_SLOT] = update_transmit_state as *const ();
vtable[sdk_raw::entities::spawn::ACTIVATE_SLOT] = activate as *const ();
vtable[sdk_raw::entities::spawn::SET_MODEL_SLOT] = set_model as *const ();
#[cfg(feature = "tf2")]
{
use super::tf2::script_binding::{SCRIPT_DESCRIPTION_SLOT, script_description};
vtable[SCRIPT_DESCRIPTION_SLOT] = script_description as *const ();
}
let layout = Layout::from_size_align(
layout.size().max(size_of::<[usize; 64]>()),
layout.align().max(align_of::<usize>()),
)
.unwrap();
let storage = unsafe { alloc_zeroed(layout) }.cast::<usize>();
if storage.is_null() {
handle_alloc_error(layout);
}
let vtable = vtable.leak();
unsafe {
storage.cast::<*const *const ()>().write(vtable.as_ptr());
storage
.cast::<usize>()
.add(MOCK_HANDLE_OFFSET / size_of::<usize>())
.write(handle as usize);
}
let fields = Vec::from(base_entity_fields());
let map = sdk_raw::test_support::entities::data_map(c"CBaseEntity", fields, null_mut());
let collideable_vtable = unsafe {
mock_vtable::<sys::ICollideable__bindgen_vtable>(
unexpected_call as *const (),
|vtable| {
(&raw mut (*vtable).ICollideable_GetCollisionOrigin).write(get_origin);
(&raw mut (*vtable).ICollideable_GetCollisionGroup).write(get_collision_group);
(&raw mut (*vtable).ICollideable_WorldSpaceSurroundingBounds)
.write(get_surrounding_bounds);
(&raw mut (*vtable).ICollideable_GetSolid).write(get_solid);
(&raw mut (*vtable).ICollideable_OBBMins).write(get_obb_mins);
(&raw mut (*vtable).ICollideable_OBBMaxs).write(get_obb_maxs);
},
)
};
let collideable = leak(sys::ICollideable {
vtable_: Box::leak(collideable_vtable),
});
let networkable_vtable = unsafe {
mock_vtable::<sys::IServerNetworkable__bindgen_vtable>(
unexpected_call as *const (),
|vtable| {
(&raw mut (*vtable).IServerNetworkable_GetClassName).write(get_class_name);
(&raw mut (*vtable).IServerNetworkable_GetServerClass).write(get_server_class);
(&raw mut (*vtable).IServerNetworkable_GetEdict).write(get_edict);
},
)
};
let networkable = leak(sys::IServerNetworkable {
vtable_: Box::leak(networkable_vtable),
});
DATA_MAP.set(map);
COLLIDEABLE.set(collideable);
COLLISION_GROUP.set(sdk_raw::entities::COLLISION_GROUP_NONE);
NETWORKABLE.set(networkable);
ORIGIN.set(sys::Vector {
x: 1.0,
y: 2.0,
z: 3.0,
});
set_local_bounds(
sys::Vector {
x: -1.0,
y: -1.0,
z: -1.0,
},
sys::Vector {
x: 1.0,
y: 1.0,
z: 1.0,
},
);
SOLID_TYPE.set(sys::SolidType_t_SOLID_BBOX);
TELEPORTS.set(0);
TRANSMIT_STATE_UPDATES.set(0);
TAKE_HEALTH_CALLS.take();
ACTIVATIONS.take();
ACTIVATION_REMOVES.set(false);
MODELS_SET.take();
set_networking(null_mut(), null_mut());
Self { storage }
}
pub fn as_ptr(&mut self) -> *mut sys::CBaseEntity {
self.storage.cast()
}
pub fn byte(&mut self, offset: usize) -> u8 {
assert!(offset < size_of::<[usize; 64]>());
unsafe { self.as_ptr().byte_add(offset).cast::<u8>().read() }
}
#[cfg(test)]
pub(crate) fn entity(&mut self) -> crate::entities::Entity<'_> {
let entity = std::ptr::NonNull::new(self.as_ptr()).unwrap();
unsafe { crate::entities::Entity::from_raw(entity) }
}
pub fn int(&mut self, offset: usize) -> c_int {
assert!(offset + size_of::<c_int>() <= size_of::<[usize; 64]>());
assert!(offset.is_multiple_of(align_of::<c_int>()));
unsafe { self.as_ptr().byte_add(offset).cast::<c_int>().read() }
}
pub fn name(&mut self) -> sys::string_t {
unsafe {
self.as_ptr()
.byte_add(MOCK_NAME_OFFSET)
.cast::<sys::string_t>()
.read()
}
}
pub fn set_byte(&mut self, offset: usize, value: u8) {
assert!(offset < size_of::<[usize; 64]>());
unsafe { self.as_ptr().byte_add(offset).cast::<u8>().write(value) };
}
pub fn set_eflags(&mut self, flags: c_int) {
unsafe {
self.as_ptr()
.byte_add(MOCK_EFLAGS_OFFSET)
.cast::<c_int>()
.write(flags)
};
}
pub fn set_hammer_id(&mut self, id: c_int) {
unsafe {
self.as_ptr()
.byte_add(MOCK_HAMMER_ID_OFFSET)
.cast::<c_int>()
.write(id)
};
}
pub fn set_int(&mut self, offset: usize, value: c_int) {
assert!(offset + size_of::<c_int>() <= size_of::<[usize; 64]>());
assert!(offset.is_multiple_of(align_of::<c_int>()));
unsafe { self.as_ptr().byte_add(offset).cast::<c_int>().write(value) };
}
pub fn set_name(&mut self, name: sys::string_t) {
unsafe {
self.as_ptr()
.byte_add(MOCK_NAME_OFFSET)
.cast::<sys::string_t>()
.write(name)
};
}
pub fn state(&mut self) -> &mut MockState {
unsafe {
&mut *self
.as_ptr()
.byte_add(MOCK_STATE_OFFSET)
.cast::<MockState>()
}
}
}
unsafe extern "C" fn accept_input(
this: *mut sys::CBaseEntity,
name: *const c_char,
activator: *mut sys::CBaseEntity,
caller: *mut sys::CBaseEntity,
value: *mut sys::variant_t,
output_id: c_int,
) -> bool {
let received = unsafe {
ReceivedInput {
target: this,
name: CStr::from_ptr(name).to_owned(),
activator,
caller,
field_type: (&raw const (*value).fieldType).read(),
payload: value.cast::<[u8; 12]>().read(),
string: if (&raw const (*value).fieldType).read() == sys::_fieldtypes_FIELD_STRING {
(&raw const (*value).__bindgen_anon_1.iszVal.pszValue).read()
} else {
ptr::null()
},
handle: (&raw const (*value).eVal._base.m_Index).read(),
output_id,
}
};
unsafe { (&raw mut (*value).fieldType).write(sys::_fieldtypes_FIELD_VOID) };
INPUTS.with_borrow_mut(|inputs| inputs.push(received));
ACCEPTS.get()
}
unsafe extern "C" fn activate(this: *mut sys::CBaseEntity) {
ACTIVATIONS.with_borrow_mut(|activations| activations.push(this));
if ACTIVATION_REMOVES.get() {
unsafe {
this.byte_add(MOCK_EFLAGS_OFFSET)
.cast::<c_int>()
.write(sdk_raw::entities::EFL_KILLME)
};
}
}
pub fn activations() -> Vec<*mut sys::CBaseEntity> {
ACTIVATIONS.with_borrow(Clone::clone)
}
pub fn base_entity_fields() -> [sys::typedescription_t; 3] {
let mut flags = field(
c"m_iEFlags",
sys::_fieldtypes_FIELD_INTEGER,
MOCK_EFLAGS_OFFSET,
);
flags.fieldSizeInBytes = size_of::<c_int>() as c_int;
let mut name = field(c"m_iName", sys::_fieldtypes_FIELD_STRING, MOCK_NAME_OFFSET);
name.fieldSizeInBytes = size_of::<sys::string_t>() as c_int;
let mut hammer_id = field(
c"m_iHammerID",
sys::_fieldtypes_FIELD_INTEGER,
MOCK_HAMMER_ID_OFFSET,
);
hammer_id.fieldSizeInBytes = size_of::<c_int>() as c_int;
[flags, name, hammer_id]
}
unsafe extern "C" fn get_class_name(_: *const sys::IServerNetworkable) -> *const c_char {
c"tf_player".as_ptr()
}
unsafe extern "C" fn get_collideable(_: *mut sys::IServerEntity) -> *mut sys::ICollideable {
COLLIDEABLE.get()
}
unsafe extern "C" fn get_collision_group(_: *const sys::ICollideable) -> c_int {
COLLISION_GROUP.get()
}
pub unsafe extern "C" fn get_datamap(_: *mut sys::CBaseEntity) -> *mut sys::datamap_t {
DATA_MAP.get()
}
unsafe extern "C" fn get_edict(_: *const sys::IServerNetworkable) -> *mut sys::edict_t {
EDICT.get()
}
unsafe extern "C" fn get_handle(this: *const sys::IServerEntity) -> *const sys::CBaseHandle {
unsafe { this.byte_add(MOCK_HANDLE_OFFSET).cast() }
}
unsafe extern "C" fn get_max_health(this: *const sys::CBaseEntity) -> c_int {
let stored = unsafe { this.byte_add(MOCK_MAX_HEALTH_OFFSET).cast::<c_int>().read() };
stored + MOCK_MAX_HEALTH_BONUS
}
unsafe extern "C" fn get_networkable(_: *mut sys::IServerEntity) -> *mut sys::IServerNetworkable {
NETWORKABLE.get()
}
unsafe extern "C" fn get_obb_maxs(_: *const sys::ICollideable) -> *const sys::Vector {
OBB_MAXS.with(Cell::as_ptr).cast_const()
}
unsafe extern "C" fn get_obb_mins(_: *const sys::ICollideable) -> *const sys::Vector {
OBB_MINS.with(Cell::as_ptr).cast_const()
}
unsafe extern "C" fn get_origin(_: *const sys::ICollideable) -> *const sys::Vector {
ORIGIN.with(Cell::as_ptr).cast_const()
}
unsafe extern "C" fn get_surrounding_bounds(
_: *mut sys::ICollideable,
mins: *mut sys::Vector,
maxs: *mut sys::Vector,
) {
let origin = ORIGIN.get();
unsafe {
mins.write(sys::Vector {
x: origin.x - 1.0,
y: origin.y - 1.0,
z: origin.z - 1.0,
});
maxs.write(sys::Vector {
x: origin.x + 1.0,
y: origin.y + 1.0,
z: origin.z + 1.0,
});
}
}
unsafe extern "C" fn get_server_class(_: *mut sys::IServerNetworkable) -> *mut sys::ServerClass {
SERVER_CLASS.get()
}
unsafe extern "C" fn get_solid(_: *const sys::ICollideable) -> sys::SolidType_t {
SOLID_TYPE.get()
}
pub fn health_fields() -> [sys::typedescription_t; 4] {
let member = |name, field_type, offset, size: usize| {
let mut member = field(name, field_type, offset);
member.fieldSizeInBytes = c_int::try_from(size).unwrap();
member
};
[
member(
c"m_iHealth",
sys::_fieldtypes_FIELD_INTEGER,
MOCK_HEALTH_OFFSET,
size_of::<c_int>(),
),
member(
c"m_iMaxHealth",
sys::_fieldtypes_FIELD_INTEGER,
MOCK_MAX_HEALTH_OFFSET,
size_of::<c_int>(),
),
member(
c"m_lifeState",
sys::_fieldtypes_FIELD_CHARACTER,
MOCK_LIFE_STATE_OFFSET,
1,
),
member(
c"m_takedamage",
sys::_fieldtypes_FIELD_CHARACTER,
MOCK_TAKE_DAMAGE_OFFSET,
1,
),
]
}
pub fn input(name: &'static CStr, field_type: sys::fieldtype_t) -> sys::typedescription_t {
let mut input: sys::typedescription_t = unsafe { std::mem::zeroed() };
input.fieldType = field_type;
input.externalName = name.as_ptr();
input.flags = sdk_raw::entities::datamap::FTYPEDESC_INPUT;
input
}
unsafe extern "C" fn is_alive(this: *mut sys::CBaseEntity) -> bool {
unsafe { this.byte_add(MOCK_LIFE_STATE_OFFSET).cast::<u8>().read() == LIFE_ALIVE }
}
pub fn models_set() -> Vec<(*mut sys::CBaseEntity, CString)> {
MODELS_SET.with_borrow(Clone::clone)
}
pub fn set_accepts(accepts: bool) {
ACCEPTS.set(accepts);
}
pub fn set_activation_removes(removes: bool) {
ACTIVATION_REMOVES.set(removes);
}
pub fn set_collision_group(group: c_int) {
COLLISION_GROUP.set(group);
}
pub fn set_datamap(map: *mut sys::datamap_t) {
DATA_MAP.set(map);
}
pub fn set_local_bounds(mins: sys::Vector, maxs: sys::Vector) {
OBB_MINS.set(mins);
OBB_MAXS.set(maxs);
}
unsafe extern "C" fn set_model(this: *mut sys::CBaseEntity, model: *const c_char) {
let model = unsafe { CStr::from_ptr(model) }.to_owned();
MODELS_SET.with_borrow_mut(|models| models.push((this, model)));
}
pub fn set_networking(class: *mut sys::ServerClass, edict: *mut sys::edict_t) {
SERVER_CLASS.set(class);
EDICT.set(edict);
}
pub fn set_solid_type(solid: sys::SolidType_t) {
SOLID_TYPE.set(solid);
}
pub fn state_fields() -> [sys::typedescription_t; 25] {
let member = |name, field_type, offset: usize, size: usize| {
let mut member = field(name, field_type, MOCK_STATE_OFFSET + offset);
member.fieldSizeInBytes = c_int::try_from(size).unwrap();
member.fieldSize = 1;
member
};
let int = size_of::<c_int>();
let string = size_of::<sys::string_t>();
let vector = size_of::<sys::Vector>();
[
member(
c"m_debugOverlays",
sys::_fieldtypes_FIELD_INTEGER,
offset_of!(MockState, debug_overlays),
int,
),
member(
c"m_fEffects",
sys::_fieldtypes_FIELD_INTEGER,
offset_of!(MockState, effects),
int,
),
member(
c"m_fFlags",
sys::_fieldtypes_FIELD_INTEGER,
offset_of!(MockState, flags),
int,
),
member(
c"m_flFriction",
sys::_fieldtypes_FIELD_FLOAT,
offset_of!(MockState, friction),
int,
),
member(
c"m_flGravity",
sys::_fieldtypes_FIELD_FLOAT,
offset_of!(MockState, gravity),
int,
),
member(
c"m_hMoveParent",
sys::_fieldtypes_FIELD_EHANDLE,
offset_of!(MockState, move_parent),
int,
),
member(
c"m_iTeamNum",
sys::_fieldtypes_FIELD_INTEGER,
offset_of!(MockState, team),
int,
),
member(
c"m_spawnflags",
sys::_fieldtypes_FIELD_INTEGER,
offset_of!(MockState, spawn_flags),
int,
),
member(
c"m_clrRender",
sys::_fieldtypes_FIELD_COLOR32,
offset_of!(MockState, render_color),
size_of::<sys::color32>(),
),
member(
c"m_nModelIndex",
sys::_fieldtypes_FIELD_SHORT,
offset_of!(MockState, model_index),
size_of::<i16>(),
),
member(
c"m_MoveType",
sys::_fieldtypes_FIELD_CHARACTER,
offset_of!(MockState, move_type),
1,
),
member(
c"m_nRenderMode",
sys::_fieldtypes_FIELD_CHARACTER,
offset_of!(MockState, render_mode),
1,
),
member(
c"m_nTransmitStateOwnedCounter",
sys::_fieldtypes_FIELD_CHARACTER,
offset_of!(MockState, transmit_state_owners),
1,
),
member(
c"m_iParent",
sys::_fieldtypes_FIELD_STRING,
offset_of!(MockState, parent_name),
string,
),
member(
c"m_ModelName",
sys::_fieldtypes_FIELD_MODELNAME,
offset_of!(MockState, model_name),
string,
),
member(
c"m_vecAbsVelocity",
sys::_fieldtypes_FIELD_VECTOR,
offset_of!(MockState, abs_velocity),
vector,
),
member(
c"m_vecVelocity",
sys::_fieldtypes_FIELD_VECTOR,
offset_of!(MockState, velocity),
vector,
),
member(
c"m_MoveCollide",
sys::_fieldtypes_FIELD_CHARACTER,
offset_of!(MockState, move_collide),
1,
),
member(
c"m_iParentAttachment",
sys::_fieldtypes_FIELD_CHARACTER,
offset_of!(MockState, parent_attachment),
1,
),
member(
c"m_pPhysicsObject",
sys::_fieldtypes_FIELD_CUSTOM,
offset_of!(MockState, physics_object),
0,
),
member(
c"m_angAbsRotation",
sys::_fieldtypes_FIELD_VECTOR,
offset_of!(MockState, abs_rotation),
size_of::<sys::QAngle>(),
),
member(
c"m_vecOrigin",
sys::_fieldtypes_FIELD_VECTOR,
offset_of!(MockState, origin),
vector,
),
member(
c"m_angRotation",
sys::_fieldtypes_FIELD_VECTOR,
offset_of!(MockState, rotation),
size_of::<sys::QAngle>(),
),
member(
c"m_hMoveChild",
sys::_fieldtypes_FIELD_EHANDLE,
offset_of!(MockState, move_child),
int,
),
member(
c"m_hMovePeer",
sys::_fieldtypes_FIELD_EHANDLE,
offset_of!(MockState, move_peer),
int,
),
]
}
pub fn state_maps(
classes: Vec<(&'static CStr, Vec<sys::typedescription_t>)>,
) -> *mut sys::datamap_t {
let mut fields = Vec::from(base_entity_fields());
fields.extend(state_fields());
let base = sdk_raw::test_support::entities::data_map(c"CBaseEntity", fields, null_mut());
classes
.into_iter()
.rev()
.fold(base, |base, (class, fields)| {
sdk_raw::test_support::entities::data_map(class, fields, base)
})
}
unsafe extern "C" fn take_health(
this: *mut sys::CBaseEntity,
amount: f32,
damage_type: c_int,
) -> c_int {
TAKE_HEALTH_CALLS.with_borrow_mut(|calls| calls.push((amount, damage_type)));
let gained = amount as c_int;
unsafe {
let health = this.byte_add(MOCK_HEALTH_OFFSET).cast::<c_int>();
health.write(health.read() + gained);
}
gained
}
pub fn take_health_calls() -> Vec<(f32, c_int)> {
TAKE_HEALTH_CALLS.with_borrow(Clone::clone)
}
pub fn take_inputs() -> Vec<ReceivedInput> {
INPUTS.take()
}
unsafe extern "C" fn teleport_entity(
_: *mut sys::CBaseEntity,
position: *const sys::Vector,
angles: *const sys::QAngle,
velocity: *const sys::Vector,
) {
assert!(angles.is_null() && velocity.is_null());
ORIGIN.set(unsafe { *position });
TELEPORTS.set(TELEPORTS.get() + 1);
}
pub fn teleports() -> usize {
TELEPORTS.get()
}
pub fn transmit_state_updates() -> usize {
TRANSMIT_STATE_UPDATES.get()
}
unsafe extern "C" fn update_transmit_state(_: *mut sys::CBaseEntity) -> c_int {
TRANSMIT_STATE_UPDATES.set(TRANSMIT_STATE_UPDATES.get() + 1);
0
}