mod args;
mod error;
mod object;
mod registrar;
mod route;
mod variable;
#[cfg(test)]
#[path = "../tests/commands.rs"]
mod tests;
use crate::NotThreadSafe;
use crate::edicts::Edict;
use crate::server::{InterfaceError, Server};
use sdk_raw::commands as raw;
use std::borrow::Cow;
use std::ffi::{CStr, CString, c_int};
use std::fmt::Display;
use std::marker::PhantomData;
pub use args::{ArgError, CommandArgs};
pub use error::{
CommandBaseKind, InvalidCommandName, RegisterCommandError, RegisterCommandErrorKind,
UnregisterCommandError, UnregisterCommandErrorKind, validate_name,
};
pub use object::ConsoleCommand;
pub use registrar::{CommandRegistrar, UnlinksBeforeUnload};
pub use route::{ClientRoute, route_client_command};
pub use source_sdk_2013_declmacros::{commands, convars};
pub use variable::ConsoleVariable;
pub(crate) use route::drop_payload;
pub type CommandFn = for<'a, 'd> fn(&'a CommandContext<'d>) -> CommandResult;
pub type CommandResult = Result<(), CommandError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Client<'d> {
edict: Edict<'d>,
}
impl<'d> Client<'d> {
pub const fn edict(self) -> Edict<'d> {
self.edict
}
pub fn slot(self) -> c_int {
self.edict.index() - 1
}
}
#[derive(Debug, Clone, Copy)]
pub struct ClientFilter {
pub name: &'static str,
pub accepts: for<'d> fn(Server<'d>, Client<'d>) -> bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum CommandAccess {
#[default]
Server,
Clients,
Everyone,
}
impl CommandAccess {
pub const fn allows_clients(self) -> bool {
matches!(self, Self::Clients | Self::Everyone)
}
pub const fn allows_server(self) -> bool {
matches!(self, Self::Server | Self::Everyone)
}
}
pub struct CommandContext<'d> {
server: Server<'d>,
args: CommandArgs<'d>,
invoker: Invoker<'d>,
name: &'static CStr,
_not_thread_safe: NotThreadSafe,
}
impl<'d> CommandContext<'d> {
pub const fn args(&self) -> CommandArgs<'d> {
self.args
}
pub const fn invoker(&self) -> Invoker<'d> {
self.invoker
}
pub const fn name(&self) -> &'static CStr {
self.name
}
pub fn reply(&self, message: impl Display) -> Result<(), InterfaceError> {
let line = line_from(message);
match self.invoker {
Invoker::Server => self.server.console_print(&line),
Invoker::Client(client) => self
.server
.valve_engine()?
.client_print(client.edict, &line),
}
Ok(())
}
pub const fn server(&self) -> Server<'d> {
self.server
}
}
impl<'d> CommandContext<'d> {
pub(crate) const fn new(
server: Server<'d>,
args: CommandArgs<'d>,
invoker: Invoker<'d>,
name: &'static CStr,
) -> Self {
Self {
server,
args,
invoker,
name,
_not_thread_safe: PhantomData,
}
}
}
impl std::fmt::Debug for CommandContext<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CommandContext")
.field("name", &self.name)
.field("args", &self.args)
.field("invoker", &self.invoker)
.finish_non_exhaustive()
}
}
#[derive(Debug, thiserror::Error)]
pub enum CommandError {
#[error("usage: {0}")]
Usage(Cow<'static, str>),
#[error("{0}")]
Denied(Cow<'static, str>),
#[error(transparent)]
Argument(#[from] ArgError),
#[error(transparent)]
Interface(#[from] InterfaceError),
#[error(transparent)]
Other(Box<dyn std::error::Error>),
#[error("the command was left unhandled")]
Unhandled,
}
impl CommandError {
pub fn denied(reason: impl Into<Cow<'static, str>>) -> Self {
Self::Denied(reason.into())
}
pub fn other(error: impl std::error::Error + 'static) -> Self {
Self::Other(Box::new(error))
}
pub fn usage(usage: impl Into<Cow<'static, str>>) -> Self {
Self::Usage(usage.into())
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct CommandFlags: c_int {
#[doc(alias("FCVAR_ARCHIVE"))]
const ARCHIVE = raw::FCVAR_ARCHIVE;
#[doc(alias("FCVAR_CHEAT"))]
const CHEAT = raw::FCVAR_CHEAT;
#[doc(alias("FCVAR_CLIENTCMD_CAN_EXECUTE"))]
const CLIENT_CMD_CAN_EXECUTE = raw::FCVAR_CLIENTCMD_CAN_EXECUTE;
#[doc(alias("FCVAR_CLIENTDLL"))]
const CLIENT_DLL = raw::FCVAR_CLIENTDLL;
#[doc(alias("FCVAR_DEMO"))]
const DEMO = raw::FCVAR_DEMO;
#[doc(alias("FCVAR_DEVELOPMENTONLY"))]
const DEVELOPMENT_ONLY = raw::FCVAR_DEVELOPMENTONLY;
#[doc(alias("FCVAR_DONTRECORD"))]
const DONT_RECORD = raw::FCVAR_DONTRECORD;
#[doc(alias("FCVAR_GAMEDLL"))]
const GAME_DLL = raw::FCVAR_GAMEDLL;
#[doc(alias("FCVAR_HIDDEN"))]
const HIDDEN = raw::FCVAR_HIDDEN;
#[doc(alias("FCVAR_NEVER_AS_STRING"))]
const NEVER_AS_STRING = raw::FCVAR_NEVER_AS_STRING;
#[doc(alias("FCVAR_NONE"))]
const NONE = raw::FCVAR_NONE;
#[doc(alias("FCVAR_NOT_CONNECTED"))]
const NOT_CONNECTED = raw::FCVAR_NOT_CONNECTED;
#[doc(alias("FCVAR_NOTIFY"))]
const NOTIFY = raw::FCVAR_NOTIFY;
#[doc(alias("FCVAR_PRINTABLEONLY"))]
const PRINTABLE_ONLY = raw::FCVAR_PRINTABLEONLY;
#[doc(alias("FCVAR_PROTECTED"))]
const PROTECTED = raw::FCVAR_PROTECTED;
#[doc(alias("FCVAR_REPLICATED"))]
const REPLICATED = raw::FCVAR_REPLICATED;
#[doc(alias("FCVAR_SERVER_CAN_EXECUTE"))]
const SERVER_CAN_EXECUTE = raw::FCVAR_SERVER_CAN_EXECUTE;
#[doc(alias("FCVAR_SERVER_CANNOT_QUERY"))]
const SERVER_CANNOT_QUERY = raw::FCVAR_SERVER_CANNOT_QUERY;
#[doc(alias("FCVAR_SPONLY"))]
const SP_ONLY = raw::FCVAR_SPONLY;
#[doc(alias("FCVAR_UNLOGGED"))]
const UNLOGGED = raw::FCVAR_UNLOGGED;
#[doc(alias("FCVAR_USERINFO"))]
const USERINFO = raw::FCVAR_USERINFO;
const _ = !0;
}
}
pub trait CommandHandler: 'static {
fn dispatch(&self, command: &CommandContext<'_>) -> CommandResult;
}
impl<F> CommandHandler for F
where
F: Fn(&CommandContext<'_>) -> CommandResult + 'static,
{
fn dispatch(&self, command: &CommandContext<'_>) -> CommandResult {
self(command)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Invoker<'d> {
Server,
Client(Client<'d>),
}
fn line_from(message: impl Display) -> CString {
let mut bytes = message.to_string().into_bytes();
bytes.retain(|&byte| byte != 0);
bytes.push(b'\n');
unsafe { CString::from_vec_unchecked(bytes) }
}