mirror of https://github.com/FWGS/xash3d-fwgs
1463 lines
30 KiB
C
1463 lines
30 KiB
C
/*
|
|
cmd.c - script command processing module
|
|
Copyright (C) 2007 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "client.h"
|
|
#include "server.h"
|
|
#include "base_cmd.h"
|
|
|
|
#define MAX_CMD_BUFFER 32768
|
|
#define MAX_CMD_LINE 2048
|
|
#define MAX_ALIAS_NAME 32
|
|
|
|
typedef struct
|
|
{
|
|
byte *data;
|
|
int cursize;
|
|
int maxsize;
|
|
} cmdbuf_t;
|
|
|
|
qboolean cmd_wait;
|
|
cmdbuf_t cmd_text, filteredcmd_text;
|
|
byte cmd_text_buf[MAX_CMD_BUFFER];
|
|
byte filteredcmd_text_buf[MAX_CMD_BUFFER];
|
|
cmdalias_t *cmd_alias;
|
|
uint cmd_condition;
|
|
int cmd_condlevel;
|
|
static qboolean cmd_currentCommandIsPrivileged;
|
|
|
|
static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPrivileged );
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
COMMAND BUFFER
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
/*
|
|
============
|
|
Cbuf_Init
|
|
============
|
|
*/
|
|
void Cbuf_Init( void )
|
|
{
|
|
cmd_text.data = cmd_text_buf;
|
|
filteredcmd_text.data = filteredcmd_text_buf;
|
|
|
|
filteredcmd_text.maxsize = cmd_text.maxsize = MAX_CMD_BUFFER;
|
|
filteredcmd_text.cursize = cmd_text.cursize = 0;
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cbuf_Clear
|
|
============
|
|
*/
|
|
void Cbuf_Clear( void )
|
|
{
|
|
memset( cmd_text.data, 0, cmd_text.maxsize );
|
|
memset( filteredcmd_text.data, 0, filteredcmd_text.maxsize );
|
|
cmd_text.cursize = filteredcmd_text.cursize = 0;
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cbuf_GetSpace
|
|
============
|
|
*/
|
|
void *Cbuf_GetSpace( cmdbuf_t *buf, int length )
|
|
{
|
|
void *data;
|
|
|
|
if(( buf->cursize + length ) > buf->maxsize )
|
|
{
|
|
buf->cursize = 0;
|
|
Host_Error( "Cbuf_GetSpace: overflow\n" );
|
|
}
|
|
|
|
data = buf->data + buf->cursize;
|
|
buf->cursize += length;
|
|
|
|
return data;
|
|
}
|
|
|
|
static void Cbuf_AddTextToBuffer( cmdbuf_t *buf, const char *text )
|
|
{
|
|
int l = Q_strlen( text );
|
|
|
|
if(( buf->cursize + l ) >= buf->maxsize )
|
|
{
|
|
Con_Reportf( S_WARN "%s: overflow\n", __func__ );
|
|
return;
|
|
}
|
|
|
|
memcpy( Cbuf_GetSpace( buf, l ), text, l );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cbuf_AddText
|
|
|
|
Adds command text at the end of the buffer
|
|
============
|
|
*/
|
|
void Cbuf_AddText( const char *text )
|
|
{
|
|
Cbuf_AddTextToBuffer( &cmd_text, text );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cbuf_AddFilteredText
|
|
============
|
|
*/
|
|
void Cbuf_AddFilteredText( const char *text )
|
|
{
|
|
Cbuf_AddTextToBuffer( &filteredcmd_text, text );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cbuf_InsertText
|
|
|
|
Adds command text immediately after the current command
|
|
Adds a \n to the text
|
|
============
|
|
*/
|
|
static void Cbuf_InsertTextToBuffer( cmdbuf_t *buf, const char *text )
|
|
{
|
|
int l = Q_strlen( text );
|
|
|
|
if(( buf->cursize + l ) >= buf->maxsize )
|
|
{
|
|
Con_Reportf( S_WARN "Cbuf_InsertText: overflow\n" );
|
|
}
|
|
else
|
|
{
|
|
memmove( buf->data + l, buf->data, buf->cursize );
|
|
memcpy( buf->data, text, l );
|
|
buf->cursize += l;
|
|
}
|
|
}
|
|
|
|
void Cbuf_InsertText( const char *text )
|
|
{
|
|
Cbuf_InsertTextToBuffer( &cmd_text, text );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cbuf_Execute
|
|
============
|
|
*/
|
|
void Cbuf_ExecuteCommandsFromBuffer( cmdbuf_t *buf, qboolean isPrivileged, int cmdsToExecute )
|
|
{
|
|
char *text;
|
|
char line[MAX_CMD_LINE];
|
|
int i, quotes;
|
|
char *comment;
|
|
|
|
while( buf->cursize )
|
|
{
|
|
// limit amount of commands that can be issued
|
|
if( cmdsToExecute >= 0 )
|
|
{
|
|
if( !cmdsToExecute-- )
|
|
break;
|
|
}
|
|
|
|
// find a \n or ; line break
|
|
text = (char *)buf->data;
|
|
|
|
quotes = false;
|
|
comment = NULL;
|
|
|
|
for( i = 0; i < buf->cursize; i++ )
|
|
{
|
|
if( !comment )
|
|
{
|
|
if( text[i] == '"' ) quotes = !quotes;
|
|
|
|
if( quotes )
|
|
{
|
|
// make sure i doesn't get > cursize which causes a negative size in memmove, which is fatal --blub
|
|
if( i < ( buf->cursize - 1 ) && ( text[i+0] == '\\' && (text[i+1] == '"' || text[i+1] == '\\')))
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
if( text[i+0] == '/' && text[i+1] == '/' && ( i == 0 || (byte)text[i - 1] <= ' ' ))
|
|
comment = &text[i];
|
|
if( text[i] == ';' ) break; // don't break if inside a quoted string or comment
|
|
}
|
|
}
|
|
|
|
if( text[i] == '\n' || text[i] == '\r' )
|
|
break;
|
|
}
|
|
|
|
if( i >= ( MAX_CMD_LINE - 1 ))
|
|
{
|
|
Con_DPrintf( S_ERROR "Cbuf_Execute: command string owerflow\n" );
|
|
line[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
memcpy( line, text, comment ? (comment - text) : i );
|
|
line[comment ? (comment - text) : i] = 0;
|
|
}
|
|
|
|
// delete the text from the command buffer and move remaining commands down
|
|
// this is necessary because commands (exec) can insert data at the
|
|
// beginning of the text buffer
|
|
if( i == buf->cursize )
|
|
{
|
|
buf->cursize = 0;
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
buf->cursize -= i;
|
|
memmove( buf->data, text + i, buf->cursize );
|
|
}
|
|
|
|
// execute the command line
|
|
Cmd_ExecuteStringWithPrivilegeCheck( line, isPrivileged );
|
|
|
|
if( cmd_wait )
|
|
{
|
|
// skip out while text still remains in buffer,
|
|
// leaving it for next frame
|
|
cmd_wait = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cbuf_Execute
|
|
============
|
|
*/
|
|
void Cbuf_Execute( void )
|
|
{
|
|
Cbuf_ExecuteCommandsFromBuffer( &cmd_text, true, -1 );
|
|
// a1ba: unlimited commands for filtered buffer per frame
|
|
// I don't see any sense in restricting that at this moment
|
|
// but in future we may limit this
|
|
Cbuf_ExecuteCommandsFromBuffer( &filteredcmd_text, false, -1 );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Cbuf_ExecStuffCmds
|
|
|
|
execute commandline
|
|
===============
|
|
*/
|
|
void Cbuf_ExecStuffCmds( void )
|
|
{
|
|
char build[MAX_CMD_LINE]; // this is for all commandline options combined (and is bounds checked)
|
|
int i, j, l = 0;
|
|
|
|
// no reason to run the commandline arguments twice
|
|
if( !host.stuffcmds_pending )
|
|
return;
|
|
build[0] = 0;
|
|
|
|
for( i = 0; i < host.argc; i++ )
|
|
{
|
|
if( host.argv[i] && host.argv[i][0] == '+' && ( host.argv[i][1] < '0' || host.argv[i][1] > '9' ) && l + Q_strlen( host.argv[i] ) - 1 <= sizeof( build ) - 1 )
|
|
{
|
|
j = 1;
|
|
|
|
while( host.argv[i][j] )
|
|
build[l++] = host.argv[i][j++];
|
|
|
|
for( i++; i < host.argc; i++ )
|
|
{
|
|
if( !host.argv[i] ) continue;
|
|
if(( host.argv[i][0] == '+' || host.argv[i][0] == '-' ) && ( host.argv[i][1] < '0' || host.argv[i][1] > '9' ))
|
|
break;
|
|
if( l + Q_strlen( host.argv[i] ) + 4 > sizeof( build ) - 1 )
|
|
break;
|
|
build[l++] = ' ';
|
|
|
|
if( Q_strchr( host.argv[i], ' ' ))
|
|
build[l++] = '\"';
|
|
|
|
for( j = 0; host.argv[i][j]; j++ )
|
|
build[l++] = host.argv[i][j];
|
|
|
|
if( Q_strchr( host.argv[i], ' ' ))
|
|
build[l++] = '\"';
|
|
}
|
|
build[l++] = '\n';
|
|
i--;
|
|
}
|
|
}
|
|
|
|
// now terminate the combined string and prepend it to the command buffer
|
|
// we already reserved space for the terminator
|
|
build[l++] = 0;
|
|
Cbuf_InsertText( build );
|
|
Cbuf_Execute(); // apply now
|
|
|
|
// this command can be called only from .rc
|
|
Cmd_RemoveCommand( "stuffcmds" );
|
|
host.stuffcmds_pending = false;
|
|
}
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
SCRIPT COMMANDS
|
|
|
|
==============================================================================
|
|
*/
|
|
qboolean Cmd_CurrentCommandIsPrivileged( void )
|
|
{
|
|
return cmd_currentCommandIsPrivileged;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Cmd_StuffCmds_f
|
|
|
|
Adds command line parameters as script statements
|
|
Commands lead with a +, and continue until a - or another +
|
|
hl.exe -dev 3 +map c1a0d
|
|
hl.exe -nosound -game bshift
|
|
===============
|
|
*/
|
|
void Cmd_StuffCmds_f( void )
|
|
{
|
|
host.stuffcmds_pending = true;
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_Wait_f
|
|
|
|
Causes execution of the remainder of the command buffer to be delayed until
|
|
next frame. This allows commands like:
|
|
bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster"
|
|
============
|
|
*/
|
|
void Cmd_Wait_f( void )
|
|
{
|
|
cmd_wait = true;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Cmd_Echo_f
|
|
|
|
Just prints the rest of the line to the console
|
|
===============
|
|
*/
|
|
void Cmd_Echo_f( void )
|
|
{
|
|
int i;
|
|
|
|
for( i = 1; i < Cmd_Argc(); i++ )
|
|
Con_Printf( "%s", Cmd_Argv( i ));
|
|
Con_Printf( "\n" );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Cmd_Alias_f
|
|
|
|
Creates a new command that executes a command string (possibly ; seperated)
|
|
===============
|
|
*/
|
|
void Cmd_Alias_f( void )
|
|
{
|
|
cmdalias_t *a;
|
|
char cmd[MAX_CMD_LINE];
|
|
int i, c;
|
|
const char *s;
|
|
|
|
if( Cmd_Argc() == 1 )
|
|
{
|
|
Con_Printf( "Current alias commands:\n" );
|
|
for( a = cmd_alias; a; a = a->next )
|
|
Con_Printf( "^2%s^7 : ^3%s^7\n", a->name, a->value );
|
|
return;
|
|
}
|
|
|
|
s = Cmd_Argv( 1 );
|
|
|
|
if( Q_strlen( s ) >= MAX_ALIAS_NAME )
|
|
{
|
|
Con_Printf( "Alias name is too long\n" );
|
|
return;
|
|
}
|
|
|
|
// if the alias already exists, reuse it
|
|
for( a = cmd_alias; a; a = a->next )
|
|
{
|
|
if( !Q_strcmp( s, a->name ))
|
|
{
|
|
Z_Free( a->value );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !a )
|
|
{
|
|
cmdalias_t *cur, *prev;
|
|
|
|
a = Z_Malloc( sizeof( cmdalias_t ));
|
|
|
|
Q_strncpy( a->name, s, sizeof( a->name ));
|
|
|
|
// insert it at the right alphanumeric position
|
|
for( prev = NULL, cur = cmd_alias; cur && Q_strcmp( cur->name, a->name ) < 0; prev = cur, cur = cur->next );
|
|
|
|
if( prev ) prev->next = a;
|
|
else cmd_alias = a;
|
|
a->next = cur;
|
|
|
|
#if defined( XASH_HASHED_VARS )
|
|
BaseCmd_Insert( HM_CMDALIAS, a, a->name );
|
|
#endif
|
|
}
|
|
|
|
// copy the rest of the command line
|
|
cmd[0] = 0; // start out with a null string
|
|
|
|
c = Cmd_Argc();
|
|
|
|
for( i = 2; i < c; i++ )
|
|
{
|
|
if( i != 2 ) Q_strncat( cmd, " ", sizeof( cmd ));
|
|
Q_strncat( cmd, Cmd_Argv( i ), sizeof( cmd ));
|
|
}
|
|
|
|
Q_strncat( cmd, "\n", sizeof( cmd ));
|
|
a->value = copystring( cmd );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Cmd_UnAlias_f
|
|
|
|
Remove existing aliases.
|
|
===============
|
|
*/
|
|
static void Cmd_UnAlias_f ( void )
|
|
{
|
|
cmdalias_t *a, *p;
|
|
const char *s;
|
|
int i;
|
|
|
|
if( Cmd_Argc() == 1 )
|
|
{
|
|
Con_Printf( S_USAGE "unalias alias1 [alias2 ...]\n" );
|
|
return;
|
|
}
|
|
|
|
for( i = 1; i < Cmd_Argc(); i++ )
|
|
{
|
|
s = Cmd_Argv( i );
|
|
p = NULL;
|
|
|
|
for( a = cmd_alias; a; p = a, a = a->next )
|
|
{
|
|
if( !Q_strcmp( s, a->name ))
|
|
{
|
|
#if defined( XASH_HASHED_VARS )
|
|
BaseCmd_Remove( HM_CMDALIAS, a->name );
|
|
#endif
|
|
if( a == cmd_alias )
|
|
cmd_alias = a->next;
|
|
if( p ) p->next = a->next;
|
|
Mem_Free( a->value );
|
|
Mem_Free( a );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !a ) Con_Printf( "%s not found\n", s );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
COMMAND EXECUTION
|
|
|
|
=============================================================================
|
|
*/
|
|
typedef struct cmd_s
|
|
{
|
|
struct cmd_s *next;
|
|
char *name;
|
|
xcommand_t function;
|
|
char *desc;
|
|
int flags;
|
|
} cmd_t;
|
|
|
|
static int cmd_argc;
|
|
static const char *cmd_args = NULL;
|
|
static char *cmd_argv[MAX_CMD_TOKENS];
|
|
static char cmd_tokenized[MAX_CMD_BUFFER]; // will have 0 bytes inserted
|
|
static cmd_t *cmd_functions; // possible commands to execute
|
|
|
|
/*
|
|
============
|
|
Cmd_Argc
|
|
============
|
|
*/
|
|
int GAME_EXPORT Cmd_Argc( void )
|
|
{
|
|
return cmd_argc;
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_Argv
|
|
============
|
|
*/
|
|
const char *Cmd_Argv( int arg )
|
|
{
|
|
if((uint)arg >= cmd_argc )
|
|
return "";
|
|
return cmd_argv[arg];
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_Args
|
|
============
|
|
*/
|
|
const char *Cmd_Args( void )
|
|
{
|
|
return cmd_args;
|
|
}
|
|
|
|
|
|
/*
|
|
===========================
|
|
|
|
Client exports
|
|
|
|
===========================
|
|
*/
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_AliasGetList
|
|
============
|
|
*/
|
|
cmdalias_t *GAME_EXPORT Cmd_AliasGetList( void )
|
|
{
|
|
return cmd_alias;
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_GetList
|
|
============
|
|
*/
|
|
cmd_t *GAME_EXPORT Cmd_GetFirstFunctionHandle( void )
|
|
{
|
|
return cmd_functions;
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_GetNext
|
|
============
|
|
*/
|
|
cmd_t *GAME_EXPORT Cmd_GetNextFunctionHandle( cmd_t *cmd )
|
|
{
|
|
return (cmd) ? cmd->next : NULL;
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_GetName
|
|
============
|
|
*/
|
|
const char *GAME_EXPORT Cmd_GetName( cmd_t *cmd )
|
|
{
|
|
return cmd->name;
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_TokenizeString
|
|
|
|
Parses the given string into command line tokens.
|
|
The text is copied to a seperate buffer and 0 characters
|
|
are inserted in the apropriate place, The argv array
|
|
will point into this temporary buffer.
|
|
============
|
|
*/
|
|
void Cmd_TokenizeString( const char *text )
|
|
{
|
|
char cmd_token[MAX_CMD_BUFFER];
|
|
int i;
|
|
|
|
// clear the args from the last string
|
|
for( i = 0; i < cmd_argc; i++ )
|
|
Z_Free( cmd_argv[i] );
|
|
|
|
cmd_argc = 0; // clear previous args
|
|
cmd_args = NULL;
|
|
|
|
if( !text ) return;
|
|
|
|
while( 1 )
|
|
{
|
|
// skip whitespace up to a /n
|
|
while( *text && *text <= ' ' && *text != '\r' && *text != '\n' )
|
|
text++;
|
|
|
|
if( *text == '\n' || *text == '\r' )
|
|
{
|
|
// a newline seperates commands in the buffer
|
|
if( *text == '\r' && text[1] == '\n' )
|
|
text++;
|
|
text++;
|
|
break;
|
|
}
|
|
|
|
if( !*text )
|
|
return;
|
|
|
|
if( cmd_argc == 1 )
|
|
cmd_args = text;
|
|
|
|
text = _COM_ParseFileSafe( (char*)text, cmd_token, sizeof( cmd_token ), PFILE_IGNOREBRACKET, NULL );
|
|
|
|
if( !text ) return;
|
|
|
|
if( cmd_argc < MAX_CMD_TOKENS )
|
|
{
|
|
cmd_argv[cmd_argc] = copystring( cmd_token );
|
|
cmd_argc++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_AddCommandEx
|
|
============
|
|
*/
|
|
static int Cmd_AddCommandEx( const char *funcname, const char *cmd_name, xcommand_t function,
|
|
const char *cmd_desc, int iFlags )
|
|
{
|
|
cmd_t *cmd, *cur, *prev;
|
|
|
|
if( !COM_CheckString( cmd_name ))
|
|
{
|
|
Con_Reportf( S_ERROR "Cmd_AddCommand: NULL name\n" );
|
|
return 0;
|
|
}
|
|
|
|
// fail if the command is a variable name
|
|
if( Cvar_FindVar( cmd_name ))
|
|
{
|
|
Con_DPrintf( S_ERROR "Cmd_AddServerCommand: %s already defined as a var\n", cmd_name );
|
|
return 0;
|
|
}
|
|
|
|
// fail if the command already exists
|
|
if( Cmd_Exists( cmd_name ))
|
|
{
|
|
Con_DPrintf( S_ERROR "Cmd_AddServerCommand: %s already defined\n", cmd_name );
|
|
return 0;
|
|
}
|
|
|
|
// use a small malloc to avoid zone fragmentation
|
|
cmd = Z_Malloc( sizeof( cmd_t ) );
|
|
cmd->name = copystring( cmd_name );
|
|
cmd->desc = copystring( cmd_desc );
|
|
cmd->function = function;
|
|
cmd->flags = iFlags;
|
|
|
|
// insert it at the right alphanumeric position
|
|
for( prev = NULL, cur = cmd_functions; cur && Q_strcmp( cur->name, cmd_name ) < 0; prev = cur, cur = cur->next );
|
|
|
|
if( prev ) prev->next = cmd;
|
|
else cmd_functions = cmd;
|
|
cmd->next = cur;
|
|
|
|
#if defined(XASH_HASHED_VARS)
|
|
BaseCmd_Insert( HM_CMD, cmd, cmd->name );
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_AddCommand
|
|
============
|
|
*/
|
|
void Cmd_AddCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc )
|
|
{
|
|
Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, cmd_desc, 0 );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_AddRestrictedCommand
|
|
============
|
|
*/
|
|
void Cmd_AddRestrictedCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc )
|
|
{
|
|
Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, cmd_desc, CMD_PRIVILEGED );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_AddServerCommand
|
|
============
|
|
*/
|
|
void GAME_EXPORT Cmd_AddServerCommand( const char *cmd_name, xcommand_t function )
|
|
{
|
|
Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, "server command", CMD_SERVERDLL );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_AddClientCommand
|
|
============
|
|
*/
|
|
int GAME_EXPORT Cmd_AddClientCommand( const char *cmd_name, xcommand_t function )
|
|
{
|
|
int flags = CMD_CLIENTDLL;
|
|
|
|
// a1ba: try to mitigate outdated client.dll vulnerabilities
|
|
if( !Q_stricmp( cmd_name, "motd_write" ))
|
|
{
|
|
flags |= CMD_PRIVILEGED;
|
|
}
|
|
|
|
return Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, "client command", flags );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_AddGameUICommand
|
|
============
|
|
*/
|
|
int GAME_EXPORT Cmd_AddGameUICommand( const char *cmd_name, xcommand_t function )
|
|
{
|
|
return Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, "gameui command", CMD_GAMEUIDLL );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_AddRefCommand
|
|
============
|
|
*/
|
|
int Cmd_AddRefCommand( const char *cmd_name, xcommand_t function, const char *description )
|
|
{
|
|
return Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, description, CMD_REFDLL );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_RemoveCommand
|
|
============
|
|
*/
|
|
void GAME_EXPORT Cmd_RemoveCommand( const char *cmd_name )
|
|
{
|
|
cmd_t *cmd, **back;
|
|
|
|
if( !cmd_name || !*cmd_name )
|
|
return;
|
|
|
|
back = &cmd_functions;
|
|
while( 1 )
|
|
{
|
|
cmd = *back;
|
|
if( !cmd ) return;
|
|
|
|
if( !Q_strcmp( cmd_name, cmd->name ))
|
|
{
|
|
#if defined(XASH_HASHED_VARS)
|
|
BaseCmd_Remove( HM_CMD, cmd->name );
|
|
#endif
|
|
|
|
*back = cmd->next;
|
|
|
|
if( cmd->name )
|
|
Mem_Free( cmd->name );
|
|
|
|
if( cmd->desc )
|
|
Mem_Free( cmd->desc );
|
|
|
|
Mem_Free( cmd );
|
|
return;
|
|
}
|
|
back = &cmd->next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_LookupCmds
|
|
============
|
|
*/
|
|
void Cmd_LookupCmds( void *buffer, void *ptr, setpair_t callback )
|
|
{
|
|
cmd_t *cmd;
|
|
cmdalias_t *alias;
|
|
|
|
// nothing to process ?
|
|
if( !callback ) return;
|
|
|
|
for( cmd = cmd_functions; cmd; cmd = cmd->next )
|
|
{
|
|
if( !buffer ) callback( cmd->name, (char *)cmd->function, cmd->desc, ptr );
|
|
else callback( cmd->name, (char *)cmd->function, buffer, ptr );
|
|
}
|
|
|
|
// lookup an aliases too
|
|
for( alias = cmd_alias; alias; alias = alias->next )
|
|
callback( alias->name, alias->value, buffer, ptr );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_Exists
|
|
============
|
|
*/
|
|
qboolean Cmd_Exists( const char *cmd_name )
|
|
{
|
|
#if defined(XASH_HASHED_VARS)
|
|
return BaseCmd_Find( HM_CMD, cmd_name ) != NULL;
|
|
#else
|
|
cmd_t *cmd;
|
|
|
|
for( cmd = cmd_functions; cmd; cmd = cmd->next )
|
|
{
|
|
if( !Q_strcmp( cmd_name, cmd->name ))
|
|
return true;
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_If_f
|
|
|
|
Compare and et condition bit if true
|
|
============
|
|
*/
|
|
void Cmd_If_f( void )
|
|
{
|
|
// reset bit first
|
|
cmd_condition &= ~BIT( cmd_condlevel );
|
|
|
|
// usage
|
|
if( cmd_argc == 1 )
|
|
{
|
|
Con_Printf( S_USAGE "if <op1> [ <operator> <op2> ]\n");
|
|
Con_Printf( ":<action1>\n" );
|
|
Con_Printf( ":<action2>\n" );
|
|
Con_Printf( "else\n" );
|
|
Con_Printf( ":<action3>\n" );
|
|
Con_Printf( "operands are string or float values\n" );
|
|
Con_Printf( "and substituted cvars like '$cl_lw'\n" );
|
|
Con_Printf( "operator is '='', '==', '>', '<', '>=', '<=' or '!='\n" );
|
|
return;
|
|
}
|
|
|
|
// one argument - check if nonzero
|
|
if( cmd_argc == 2 )
|
|
{
|
|
if( Q_atof( cmd_argv[1] ))
|
|
cmd_condition |= BIT( cmd_condlevel );
|
|
}
|
|
else if( cmd_argc == 4 )
|
|
{
|
|
// simple compare
|
|
float f1 = Q_atof( cmd_argv[1] );
|
|
float f2 = Q_atof( cmd_argv[3] );
|
|
|
|
if( !cmd_argv[2][0] ) // this is wrong
|
|
return;
|
|
|
|
if(( cmd_argv[2][0] == '=' ) || ( cmd_argv[2][1] == '=' )) // =, ==, >=, <=
|
|
{
|
|
if( !Q_strcmp( cmd_argv[1], cmd_argv[3] ) || (( f1 || f2 ) && ( f1 == f2 )))
|
|
cmd_condition |= BIT( cmd_condlevel );
|
|
}
|
|
|
|
if( cmd_argv[2][0] == '!' ) // !=
|
|
{
|
|
cmd_condition ^= BIT( cmd_condlevel );
|
|
return;
|
|
}
|
|
|
|
if(( cmd_argv[2][0] == '>' ) && ( f1 > f2 )) // >, >=
|
|
cmd_condition |= BIT( cmd_condlevel );
|
|
|
|
if(( cmd_argv[2][0] == '<' ) && ( f1 < f2 )) // <, <=
|
|
cmd_condition |= BIT( cmd_condlevel );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_Else_f
|
|
|
|
Invert condition bit
|
|
============
|
|
*/
|
|
void Cmd_Else_f( void )
|
|
{
|
|
cmd_condition ^= BIT( cmd_condlevel );
|
|
}
|
|
|
|
static qboolean Cmd_ShouldAllowCommand( cmd_t *cmd, qboolean isPrivileged )
|
|
{
|
|
const char *prefixes[] = { "cl_", "gl_", "r_", "m_", "hud_" };
|
|
int i;
|
|
|
|
// always allow local commands
|
|
if( isPrivileged )
|
|
return true;
|
|
|
|
// never allow local only commands from remote
|
|
if( FBitSet( cmd->flags, CMD_PRIVILEGED ))
|
|
return false;
|
|
|
|
// allow engine commands if user don't mind
|
|
if( cl_filterstuffcmd.value <= 0.0f )
|
|
return true;
|
|
|
|
if( FBitSet( cmd->flags, CMD_FILTERABLE ))
|
|
return false;
|
|
|
|
for( i = 0; i < ARRAYSIZE( prefixes ); i++ )
|
|
{
|
|
if( !Q_strnicmp( cmd->name, prefixes[i], Q_strlen( prefixes[i] )))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_ExecuteString
|
|
|
|
A complete command line has been parsed, so try to execute it
|
|
============
|
|
*/
|
|
static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPrivileged )
|
|
{
|
|
cmd_t *cmd = NULL;
|
|
cmdalias_t *a = NULL;
|
|
convar_t *cvar = NULL;
|
|
char command[MAX_CMD_LINE];
|
|
char *pcmd = command;
|
|
int len = 0;
|
|
|
|
cmd_condlevel = 0;
|
|
|
|
// cvar value substitution
|
|
if( CVAR_TO_BOOL( cmd_scripting ))
|
|
{
|
|
while( *text )
|
|
{
|
|
// check for escape
|
|
if(( *text == '\\' || *text == '$' ) && (*( text + 1 ) == '$' ))
|
|
{
|
|
text ++;
|
|
}
|
|
else if( *text == '$' )
|
|
{
|
|
char token[MAX_CMD_LINE];
|
|
char *ptoken = token;
|
|
|
|
// check for correct cvar name
|
|
text++;
|
|
while(( *text >= '0' && *text <= '9' ) || ( *text >= 'A' && *text <= 'Z' ) || ( *text >= 'a' && *text <= 'z' ) || ( *text == '_' ))
|
|
*ptoken++ = *text++;
|
|
*ptoken = 0;
|
|
|
|
len += Q_strncpy( pcmd, Cvar_VariableString( token ), MAX_CMD_LINE - len );
|
|
pcmd = command + len;
|
|
|
|
if( !*text ) break;
|
|
}
|
|
|
|
*pcmd++ = *text++;
|
|
len++;
|
|
}
|
|
|
|
*pcmd = 0;
|
|
text = command;
|
|
|
|
while( *text == ':' )
|
|
{
|
|
if( !FBitSet( cmd_condition, BIT( cmd_condlevel )))
|
|
return;
|
|
cmd_condlevel++;
|
|
text++;
|
|
}
|
|
}
|
|
|
|
// execute the command line
|
|
Cmd_TokenizeString( text );
|
|
|
|
if( !Cmd_Argc( )) return; // no tokens
|
|
|
|
#if defined(XASH_HASHED_VARS)
|
|
BaseCmd_FindAll( cmd_argv[0],
|
|
(base_command_t**)&cmd,
|
|
(base_command_t**)&a,
|
|
(base_command_t**)&cvar );
|
|
#endif
|
|
|
|
if( !host.apply_game_config )
|
|
{
|
|
// check aliases
|
|
if( !a ) // if not found in basecmd
|
|
{
|
|
for( a = cmd_alias; a; a = a->next )
|
|
{
|
|
if( !Q_stricmp( cmd_argv[0], a->name ))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( a )
|
|
{
|
|
Cbuf_InsertTextToBuffer(
|
|
isPrivileged ? &cmd_text : &filteredcmd_text,
|
|
a->value );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// special mode for restore game.dll archived cvars
|
|
if( !host.apply_game_config || !Q_strcmp( cmd_argv[0], "exec" ))
|
|
{
|
|
if( !cmd || !cmd->function ) // if not found in basecmd
|
|
{
|
|
for( cmd = cmd_functions; cmd; cmd = cmd->next )
|
|
{
|
|
if( !Q_stricmp( cmd_argv[0], cmd->name ) && cmd->function )
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check functions
|
|
if( cmd && cmd->function )
|
|
{
|
|
if( Cmd_ShouldAllowCommand( cmd, isPrivileged ))
|
|
{
|
|
cmd_currentCommandIsPrivileged = isPrivileged;
|
|
cmd->function();
|
|
cmd_currentCommandIsPrivileged = true;
|
|
}
|
|
else
|
|
{
|
|
Con_Printf( S_WARN "Could not execute privileged command %s\n", cmd->name );
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check cvars
|
|
if( Cvar_CommandWithPrivilegeCheck( cvar, isPrivileged )) return;
|
|
|
|
if( host.apply_game_config )
|
|
return; // don't send nothing to server: we are a server!
|
|
|
|
// forward the command line to the server, so the entity DLL can parse it
|
|
if( host.type == HOST_NORMAL )
|
|
{
|
|
#if !XASH_DEDICATED
|
|
if( cls.state >= ca_connected )
|
|
{
|
|
Cmd_ForwardToServer();
|
|
}
|
|
else
|
|
#endif // XASH_DEDICATED
|
|
if( Cvar_VariableInteger( "host_gameloaded" ))
|
|
{
|
|
Con_Printf( S_WARN "Unknown command \"%s\"\n", Cmd_Argv( 0 ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void Cmd_ExecuteString( const char *text )
|
|
{
|
|
Cmd_ExecuteStringWithPrivilegeCheck( text, true );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Cmd_ForwardToServer
|
|
|
|
adds the current command line as a clc_stringcmd to the client message.
|
|
things like godmode, noclip, etc, are commands directed to the server,
|
|
so when they are typed in at the console, they will need to be forwarded.
|
|
===================
|
|
*/
|
|
#if !XASH_DEDICATED
|
|
void Cmd_ForwardToServer( void )
|
|
{
|
|
char str[MAX_CMD_BUFFER];
|
|
|
|
if( cls.demoplayback )
|
|
{
|
|
if( !Q_stricmp( Cmd_Argv( 0 ), "pause" ))
|
|
cl.paused ^= 1;
|
|
return;
|
|
}
|
|
|
|
if( cls.state < ca_connected || cls.state > ca_active )
|
|
{
|
|
if( Q_stricmp( Cmd_Argv( 0 ), "setinfo" ))
|
|
Con_Printf( "Can't \"%s\", not connected\n", Cmd_Argv( 0 ));
|
|
return; // not connected
|
|
}
|
|
|
|
MSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd );
|
|
|
|
str[0] = 0;
|
|
if( Q_stricmp( Cmd_Argv( 0 ), "cmd" ))
|
|
{
|
|
Q_strcat( str, Cmd_Argv( 0 ));
|
|
Q_strcat( str, " " );
|
|
}
|
|
|
|
if( Cmd_Argc() > 1 )
|
|
Q_strcat( str, Cmd_Args( ));
|
|
else Q_strcat( str, "\n" );
|
|
|
|
MSG_WriteString( &cls.netchan.message, str );
|
|
}
|
|
#endif // XASH_DEDICATED
|
|
|
|
/*
|
|
============
|
|
Cmd_List_f
|
|
============
|
|
*/
|
|
void Cmd_List_f( void )
|
|
{
|
|
cmd_t *cmd;
|
|
int i = 0;
|
|
const char *match;
|
|
|
|
if( Cmd_Argc() > 1 ) match = Cmd_Argv( 1 );
|
|
else match = NULL;
|
|
|
|
for( cmd = cmd_functions; cmd; cmd = cmd->next )
|
|
{
|
|
if( cmd->name[0] == '@' )
|
|
continue; // never show system cmds
|
|
|
|
if( match && !Q_stricmpext( match, cmd->name ))
|
|
continue;
|
|
|
|
Con_Printf( " %-*s ^3%s^7\n", 32, cmd->name, cmd->desc );
|
|
i++;
|
|
}
|
|
|
|
Con_Printf( "%i commands\n", i );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_Unlink
|
|
|
|
unlink all commands with specified flag
|
|
============
|
|
*/
|
|
void Cmd_Unlink( int group )
|
|
{
|
|
cmd_t *cmd;
|
|
cmd_t **prev;
|
|
int count = 0;
|
|
|
|
if( Cvar_VariableInteger( "host_gameloaded" ) && FBitSet( group, CMD_SERVERDLL ))
|
|
return;
|
|
|
|
if( Cvar_VariableInteger( "host_clientloaded" ) && FBitSet( group, CMD_CLIENTDLL ))
|
|
return;
|
|
|
|
if( Cvar_VariableInteger( "host_gameuiloaded" ) && FBitSet( group, CMD_GAMEUIDLL ))
|
|
return;
|
|
|
|
prev = &cmd_functions;
|
|
|
|
while( 1 )
|
|
{
|
|
cmd = *prev;
|
|
if( !cmd ) break;
|
|
|
|
// do filter by specified group
|
|
if( group && !FBitSet( cmd->flags, group ))
|
|
{
|
|
prev = &cmd->next;
|
|
continue;
|
|
}
|
|
|
|
#if defined(XASH_HASHED_VARS)
|
|
BaseCmd_Remove( HM_CMD, cmd->name );
|
|
#endif
|
|
|
|
*prev = cmd->next;
|
|
|
|
if( cmd->name ) Mem_Free( cmd->name );
|
|
if( cmd->desc ) Mem_Free( cmd->desc );
|
|
|
|
Mem_Free( cmd );
|
|
count++;
|
|
}
|
|
|
|
Con_Reportf( "unlink %i commands\n", count );
|
|
}
|
|
|
|
static void Cmd_Apropos_f( void )
|
|
{
|
|
cmd_t *cmd;
|
|
convar_t *var;
|
|
cmdalias_t *alias;
|
|
const char *partial;
|
|
int count = 0;
|
|
qboolean ispattern;
|
|
|
|
if( Cmd_Argc() > 1 )
|
|
{
|
|
partial = Cmd_Args();
|
|
}
|
|
else
|
|
{
|
|
Msg( "apropos what?\n" );
|
|
return;
|
|
}
|
|
|
|
ispattern = partial && Q_strpbrk( partial, "*?" );
|
|
if( !ispattern )
|
|
partial = va( "*%s*", partial );
|
|
|
|
for( var = (convar_t*)Cvar_GetList(); var; var = var->next )
|
|
{
|
|
if( !matchpattern_with_separator( var->name, partial, true, "", false ) )
|
|
{
|
|
const char *desc;
|
|
|
|
if( var->flags & FCVAR_EXTENDED )
|
|
desc = var->desc;
|
|
else desc = "game cvar";
|
|
|
|
if( !desc )
|
|
desc = "user cvar";
|
|
|
|
if( !matchpattern_with_separator( desc, partial, true, "", false ))
|
|
continue;
|
|
}
|
|
|
|
// TODO: maybe add flags output like cvarlist, also
|
|
// fix inconsistencies in output from different commands
|
|
Msg( "cvar ^3%s^7 is \"%s\" [\"%s\"] %s\n",
|
|
var->name, var->string,
|
|
( var->flags & FCVAR_EXTENDED ) ? var->def_string : "",
|
|
( var->flags & FCVAR_EXTENDED ) ? var->desc : "game cvar");
|
|
count++;
|
|
}
|
|
|
|
for( cmd = Cmd_GetFirstFunctionHandle(); cmd; cmd = Cmd_GetNextFunctionHandle( cmd ) )
|
|
{
|
|
if( cmd->name[0] == '@' )
|
|
continue; // never show system cmds
|
|
|
|
if( !matchpattern_with_separator( cmd->name, partial, true, "", false ) &&
|
|
!matchpattern_with_separator( cmd->desc, partial, true, "", false ))
|
|
continue;
|
|
|
|
Msg( "command ^2%s^7: %s\n", cmd->name, cmd->desc );
|
|
count++;
|
|
}
|
|
|
|
for( alias = Cmd_AliasGetList(); alias; alias = alias->next )
|
|
{
|
|
// proceed a bit differently here as an alias value always got a final \n
|
|
if( !matchpattern_with_separator( alias->name, partial, true, "", false ) &&
|
|
!matchpattern_with_separator( alias->value, partial, true, "\n", false )) // when \n is a separator, wildcards don't match it //-V666
|
|
continue;
|
|
|
|
Msg( "alias ^5%s^7: %s", alias->name, alias->value ); // do not print an extra \n
|
|
count++;
|
|
}
|
|
|
|
Msg( "\n%i result%s\n\n", count, (count > 1) ? "s" : "" );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_Null_f
|
|
|
|
null function for some cmd stubs
|
|
============
|
|
*/
|
|
void Cmd_Null_f( void )
|
|
{
|
|
}
|
|
|
|
/*
|
|
==========
|
|
Cmd_Escape
|
|
|
|
inserts escape sequences
|
|
==========
|
|
*/
|
|
void Cmd_Escape( char *newCommand, const char *oldCommand, int len )
|
|
{
|
|
int c;
|
|
int scripting = CVAR_TO_BOOL( cmd_scripting );
|
|
|
|
while( (c = *oldCommand++) && len > 1 )
|
|
{
|
|
if( c == '"' )
|
|
{
|
|
*newCommand++ = '\\';
|
|
len--;
|
|
}
|
|
|
|
if( scripting && c == '$')
|
|
{
|
|
*newCommand++ = '$';
|
|
len--;
|
|
}
|
|
|
|
*newCommand++ = c; len--;
|
|
}
|
|
|
|
*newCommand++ = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_Init
|
|
|
|
============
|
|
*/
|
|
void Cmd_Init( void )
|
|
{
|
|
Cbuf_Init();
|
|
|
|
cmd_functions = NULL;
|
|
cmd_condition = 0;
|
|
cmd_alias = NULL;
|
|
cmd_args = NULL;
|
|
cmd_argc = 0;
|
|
|
|
// register our commands
|
|
Cmd_AddCommand( "echo", Cmd_Echo_f, "print a message to the console (useful in scripts)" );
|
|
Cmd_AddCommand( "wait", Cmd_Wait_f, "make script execution wait for some rendered frames" );
|
|
Cmd_AddCommand( "cmdlist", Cmd_List_f, "display all console commands beginning with the specified prefix" );
|
|
Cmd_AddRestrictedCommand( "stuffcmds", Cmd_StuffCmds_f, "execute commandline parameters (must be present in .rc script)" );
|
|
Cmd_AddCommand( "apropos", Cmd_Apropos_f, "lists all console variables/commands/aliases containing the specified string in the name or description" );
|
|
#if !XASH_DEDICATED
|
|
Cmd_AddCommand( "cmd", Cmd_ForwardToServer, "send a console commandline to the server" );
|
|
#endif // XASH_DEDICATED
|
|
Cmd_AddRestrictedCommand( "alias", Cmd_Alias_f, "create a script function. Without arguments show the list of all alias" );
|
|
Cmd_AddRestrictedCommand( "unalias", Cmd_UnAlias_f, "remove a script function" );
|
|
Cmd_AddCommand( "if", Cmd_If_f, "compare and set condition bits" );
|
|
Cmd_AddCommand( "else", Cmd_Else_f, "invert condition bit" );
|
|
|
|
#if defined(XASH_HASHED_VARS)
|
|
Cmd_AddCommand( "basecmd_stats", BaseCmd_Stats_f, "print info about basecmd usage" );
|
|
Cmd_AddCommand( "basecmd_test", BaseCmd_Test_f, "test basecmd" );
|
|
#endif
|
|
}
|
|
|
|
#if XASH_ENGINE_TESTS
|
|
#include "tests.h"
|
|
|
|
enum
|
|
{
|
|
NO_CALL = 0,
|
|
PRIV = 1,
|
|
UNPRIV = 2
|
|
};
|
|
|
|
static int test_flags[3] = { NO_CALL, NO_CALL, NO_CALL };
|
|
|
|
static void Test_PrivilegedCommand_f( void )
|
|
{
|
|
test_flags[0] = Cmd_CurrentCommandIsPrivileged() ? PRIV : UNPRIV;
|
|
}
|
|
|
|
static void Test_UnprivilegedCommand_f( void )
|
|
{
|
|
test_flags[1] = Cmd_CurrentCommandIsPrivileged() ? PRIV : UNPRIV;
|
|
}
|
|
|
|
static void Test_FilteredCommand_f( void )
|
|
{
|
|
test_flags[2] = Cmd_CurrentCommandIsPrivileged() ? PRIV : UNPRIV;
|
|
}
|
|
|
|
void Test_RunCmd( void )
|
|
{
|
|
Cmd_AddCommand( "test_privileged", Test_PrivilegedCommand_f, "bark bark" );
|
|
Cmd_AddRestrictedCommand( "test_unprivileged", Test_UnprivilegedCommand_f, "meow meow" );
|
|
Cmd_AddCommand( "hud_filtered", Test_FilteredCommand_f, "dummy description" );
|
|
|
|
Cbuf_AddText( "test_privileged; test_unprivileged; hud_filtered\n" );
|
|
Cbuf_Execute();
|
|
TASSERT( test_flags[0] == PRIV );
|
|
TASSERT( test_flags[1] == PRIV );
|
|
TASSERT( test_flags[2] == PRIV );
|
|
|
|
VectorSet( test_flags, NO_CALL, NO_CALL, NO_CALL );
|
|
Cvar_DirectSet( &cl_filterstuffcmd, "0" );
|
|
Cbuf_AddFilteredText( "test_privileged; test_unprivileged; hud_filtered\n" );
|
|
Cbuf_Execute();
|
|
TASSERT( test_flags[0] == UNPRIV );
|
|
TASSERT( test_flags[1] == NO_CALL );
|
|
TASSERT( test_flags[2] == UNPRIV );
|
|
|
|
VectorSet( test_flags, NO_CALL, NO_CALL, NO_CALL );
|
|
Cvar_DirectSet( &cl_filterstuffcmd, "1" );
|
|
Cbuf_AddFilteredText( "test_privileged; test_unprivileged; hud_filtered\n" );
|
|
Cbuf_Execute();
|
|
TASSERT( test_flags[0] == UNPRIV );
|
|
TASSERT( test_flags[1] == NO_CALL );
|
|
TASSERT( test_flags[2] == NO_CALL );
|
|
|
|
Cmd_RemoveCommand( "hud_filtered" );
|
|
Cmd_RemoveCommand( "test_unprivileged" );
|
|
Cmd_RemoveCommand( "test_privileged" );
|
|
}
|
|
#endif
|