use super::*;
use crate::test_support::harness::{Harness, on_both};
use crate::test_support::server::{no_interfaces, tf2_binding};
use std::cell::RefCell;
use std::ffi::c_void;
thread_local! {
static CALLS: RefCell<Vec<(&'static str, usize)>> = const { RefCell::new(Vec::new()) };
static REFUSED: Cell<usize> = const { Cell::new(0) };
}
#[repr(C)]
struct Player {
vtable: *mut *mut c_void,
}
impl Player {
fn of_new_class(respawn: ForceRespawn) -> Box<Self> {
let slots = Vec::leak(vec![respawn as *mut c_void; FORCE_RESPAWN_SLOT + 1]);
Box::new(Self {
vtable: slots.as_mut_ptr(),
})
}
fn ptr(&mut self) -> NonNull<sys::CBaseEntity> {
NonNull::from(self).cast()
}
}
unsafe extern "C" fn game_respawn(this: *mut sys::CBaseEntity) {
CALLS.with_borrow_mut(|calls| calls.push(("game", this.addr())));
}
fn on_respawn(_server: Server<'_>, player: Entity<'_>) -> RespawnAction {
let address = player.as_ptr().addr();
CALLS.with_borrow_mut(|calls| calls.push(("callback", address)));
if address == REFUSED.get() {
RespawnAction::Refuse
} else {
RespawnAction::Allow
}
}
fn respawn(harness: &Harness, player: &mut Player) -> Vec<(&'static str, usize)> {
CALLS.take();
harness.call::<ForceRespawn>(player.ptr().as_ptr(), FORCE_RESPAWN_SLOT, ());
CALLS.take()
}
#[test]
fn respawns_run_unless_a_hook_refuses_them() {
on_both(|harness| {
let api = harness.api();
let mut player = Player::of_new_class(game_respawn);
let mut bot = Player::of_new_class(game_respawn);
let mut blocked = Player::of_new_class(game_respawn);
let (player_address, bot_address) = (player.ptr().addr().get(), bot.ptr().addr().get());
for object in [player.ptr(), bot.ptr()] {
unsafe { api.install_respawn(object, tf2_binding(no_interfaces), on_respawn) }.unwrap();
}
assert!(matches!(
unsafe { api.install_respawn(player.ptr(), tf2_binding(no_interfaces), on_respawn) },
Err(RespawnHookError::Hook(HookError::AlreadyInstalled))
));
REFUSED.set(bot_address);
assert_eq!(
respawn(harness, &mut player),
[("callback", player_address), ("game", player_address)]
);
assert_eq!(respawn(harness, &mut bot), [("callback", bot_address)]);
fn refuse(_call: &HookCall<'_, ForceRespawn>) -> HookAction<()> {
HookAction::Supersede(())
}
unsafe {
api.add_hook(
FORCE_RESPAWN,
HookTarget::class_of(blocked.ptr()),
HookTiming::Pre,
&refuse,
)
.unwrap();
api.install_respawn(blocked.ptr(), tf2_binding(no_interfaces), on_respawn)
.unwrap();
}
assert_eq!(respawn(harness, &mut blocked), []);
});
}