use super::*;
use crate::test_support::entities::{
MOCK_EFLAGS_OFFSET, base_entity_fields, get_datamap, set_datamap,
};
use crate::test_support::server::{mock_server, null_server};
use sdk_raw::test_support::entities::data_map;
use sdk_raw::tf2::respawn::FORCE_RESPAWN_SLOT;
use std::cell::RefCell;
use std::ffi::{CStr, c_int, c_void};
use std::mem::offset_of;
use std::ptr::{NonNull, null, null_mut};
thread_local! {
static RESPAWNED: RefCell<Vec<*mut c_void>> = const { RefCell::new(Vec::new()) };
}
#[repr(C)]
struct FakePlayer {
vtable: *const *const (),
padding: [usize; 3],
eflags: c_int,
}
unsafe extern "C" fn force_respawn_entry(player: *mut sys::CTFPlayer) {
RESPAWNED.with_borrow_mut(|respawned| respawned.push(player.cast()));
}
fn maps(class: &'static CStr) -> *mut sys::datamap_t {
let base = data_map(c"CBaseEntity", Vec::from(base_entity_fields()), null_mut());
data_map(class, vec![], base)
}
#[test]
fn tf_players_respawn_through_the_generated_vtable() {
assert_eq!(offset_of!(FakePlayer, eflags), MOCK_EFLAGS_OFFSET);
let mut vtable = vec![null(); FORCE_RESPAWN_SLOT + 1];
vtable[sdk_raw::entities::GET_DATA_DESC_MAP_SLOT] = get_datamap as *const ();
vtable[FORCE_RESPAWN_SLOT] = force_respawn_entry as *const ();
let player = Box::into_raw(Box::new(FakePlayer {
vtable: vtable.as_ptr(),
padding: [0; 3],
eflags: 0,
}));
let entity = unsafe { Entity::from_raw(NonNull::new(player.cast()).unwrap()) };
let scope = ();
let server = mock_server(&scope);
set_datamap(maps(c"CTFPlayer"));
RESPAWNED.take();
assert_eq!(force_respawn(server, entity), Ok(()));
assert_eq!(RESPAWNED.take(), [player.cast()]);
set_datamap(data_map(c"CTFBot", vec![], maps(c"CTFPlayer")));
assert_eq!(force_respawn(server, entity), Ok(()));
assert_eq!(RESPAWNED.take(), [player.cast()]);
set_datamap(maps(c"CBaseCombatCharacter"));
assert_eq!(
force_respawn(server, entity),
Err(RespawnError::NotTfPlayer)
);
set_datamap(maps(c"CTFPlayer"));
assert_eq!(
force_respawn(null_server(Game::SourceSdk2013, &scope), entity),
Err(RespawnError::NotTfPlayer)
);
unsafe { (&raw mut (*player).eflags).write(1) };
assert_eq!(
force_respawn(server, entity),
Err(RespawnError::MarkedForDeletion)
);
assert!(RESPAWNED.take().is_empty());
unsafe { drop(Box::from_raw(player)) };
}