use crate::MetamodApi;
use crate::hook::{
Handler, HookAction, HookCall, HookError, HookId, HookTarget, HookTiming, VirtualFunction,
};
use crate::sys::plugin as raw;
use source_sdk_2013::ServerBinding;
use source_sdk_2013::commands::{CommandRegistrar, UnlinksBeforeUnload, route_client_command};
use source_sdk_2013::interfaces::ServerGameClients;
use source_sdk_2013::raw::interfaces::server_game_clients::{
CLIENT_COMMAND_SLOT, ClientCommandFn as ClientCommand,
};
use source_sdk_2013::sys::ConCommandBase;
use std::cell::Cell;
use std::ffi::c_void;
use std::ptr::NonNull;
const CLIENT_COMMAND: VirtualFunction<ClientCommand> = VirtualFunction::new(CLIENT_COMMAND_SLOT);
static ROUTED_SERVER: RoutedServer = RoutedServer(Cell::new(None));
#[derive(Debug, Clone, Copy)]
pub struct MetamodRegistrar<'callback> {
api: MetamodApi<'callback>,
plugin: NonNull<c_void>,
}
unsafe impl CommandRegistrar for MetamodRegistrar<'_> {
unsafe fn link(&self, command: NonNull<ConCommandBase>) {
unsafe {
self.api
.register_con_command_base(self.plugin, command.cast())
};
}
unsafe fn unlink(&self, command: NonNull<ConCommandBase>) {
unsafe {
self.api
.unregister_con_command_base(self.plugin, command.cast())
};
}
}
unsafe impl UnlinksBeforeUnload for MetamodRegistrar<'_> {}
struct RoutedServer(Cell<Option<(HookId, ServerBinding)>>);
impl Handler<ClientCommand> for RoutedServer {
fn call(&self, call: &HookCall<'_, ClientCommand>) -> HookAction<()> {
if call.superseded() == Some(true) {
return HookAction::Ignore;
}
let (edict, command) = call.args();
let (Some((_, binding)), Some(edict), Some(command)) = (
self.0.get(),
NonNull::new(edict),
NonNull::new(command.cast_mut()),
) else {
return HookAction::Ignore;
};
match unsafe { route_client_command(&binding, edict, command) }.is_handled() {
true => HookAction::Supersede(()),
false => HookAction::Ignore,
}
}
}
unsafe impl Sync for RoutedServer {}
impl<'callback> MetamodApi<'callback> {
pub fn command_registrar(self) -> Option<MetamodRegistrar<'callback>> {
let version = self.version().plugin_api_version();
if !raw::cpp_metamod_plugin_is_loaded(version) {
return None;
}
Some(MetamodRegistrar {
api: self,
plugin: NonNull::new(raw::cpp_metamod_plugin_for_version(version))?,
})
}
pub fn route_client_commands(
self,
clients: ServerGameClients<'_>,
binding: ServerBinding,
) -> Result<(), HookError> {
if let Some((hook, _)) = ROUTED_SERVER.0.get()
&& self.has_hook(hook)
{
return Err(HookError::AlreadyInstalled);
}
let clients = NonNull::new(clients.as_ptr()).ok_or(HookError::InvalidArgument)?;
let hook = unsafe {
self.add_hook(
CLIENT_COMMAND,
HookTarget::instance(clients),
HookTiming::Pre,
&ROUTED_SERVER,
)
}?;
ROUTED_SERVER.0.set(Some((hook, binding)));
Ok(())
}
}