mirror of
https://github.com/Q3Rally-Team/rallyunlimited-engine.git
synced 2024-11-25 13:41:40 +00:00
1043 lines
21 KiB
C
1043 lines
21 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena source code 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 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Quake III Arena source code 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Quake III Arena source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
// cmd.c -- Quake script command processing module
|
|
|
|
#include "q_shared.h"
|
|
#include "qcommon.h"
|
|
|
|
#define MAX_CMD_BUFFER 65536
|
|
|
|
typedef struct {
|
|
byte *data;
|
|
int maxsize;
|
|
int cursize;
|
|
} cmd_t;
|
|
|
|
static int cmd_wait;
|
|
static cmd_t cmd_text;
|
|
static byte cmd_text_buf[MAX_CMD_BUFFER];
|
|
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
============
|
|
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"
|
|
============
|
|
*/
|
|
static void Cmd_Wait_f( void ) {
|
|
if ( Cmd_Argc() == 2 ) {
|
|
cmd_wait = atoi( Cmd_Argv( 1 ) );
|
|
if ( cmd_wait < 0 )
|
|
cmd_wait = 1; // ignore the argument
|
|
} else {
|
|
cmd_wait = 1;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
COMMAND BUFFER
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
/*
|
|
============
|
|
Cbuf_Init
|
|
============
|
|
*/
|
|
void Cbuf_Init( void )
|
|
{
|
|
cmd_text.data = cmd_text_buf;
|
|
cmd_text.maxsize = MAX_CMD_BUFFER;
|
|
cmd_text.cursize = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cbuf_AddText
|
|
|
|
Adds command text at the end of the buffer, does NOT add a final \n
|
|
============
|
|
*/
|
|
void Cbuf_AddText( const char *text ) {
|
|
|
|
const int l = (int)strlen( text );
|
|
|
|
if (cmd_text.cursize + l >= cmd_text.maxsize)
|
|
{
|
|
Com_Printf ("Cbuf_AddText: overflow\n");
|
|
return;
|
|
}
|
|
|
|
Com_Memcpy(&cmd_text.data[cmd_text.cursize], text, l);
|
|
cmd_text.cursize += l;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cbuf_Add
|
|
|
|
// Adds command text at the specified position of the buffer, adds \n when needed
|
|
============
|
|
*/
|
|
int Cbuf_Add( const char *text, int pos ) {
|
|
|
|
int len = (int)strlen( text );
|
|
qboolean separate = qfalse;
|
|
int i;
|
|
|
|
if ( len == 0 ) {
|
|
return cmd_text.cursize;
|
|
}
|
|
|
|
if ( pos > cmd_text.cursize || pos < 0 ) {
|
|
// insert at the text end
|
|
pos = cmd_text.cursize;
|
|
}
|
|
|
|
if ( text[len - 1] == '\n' || text[len - 1] == ';' ) {
|
|
// command already has separator
|
|
} else {
|
|
separate = qtrue;
|
|
len += 1;
|
|
}
|
|
|
|
if ( len + cmd_text.cursize > cmd_text.maxsize ) {
|
|
Com_Printf( S_COLOR_YELLOW "%s(%i) overflowed\n", __func__, pos );
|
|
return cmd_text.cursize;
|
|
}
|
|
|
|
// move the existing command text
|
|
for ( i = cmd_text.cursize - 1; i >= pos; i-- ) {
|
|
cmd_text.data[i + len] = cmd_text.data[i];
|
|
}
|
|
|
|
if ( separate ) {
|
|
// copy the new text in + add a \n
|
|
Com_Memcpy( cmd_text.data + pos, text, len - 1 );
|
|
cmd_text.data[pos + len - 1] = '\n';
|
|
} else {
|
|
// copy the new text in
|
|
Com_Memcpy( cmd_text.data + pos, text, len );
|
|
}
|
|
|
|
cmd_text.cursize += len;
|
|
|
|
return pos + len;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cbuf_InsertText
|
|
|
|
Adds command text immediately after the current command
|
|
Adds a \n to the text
|
|
============
|
|
*/
|
|
void Cbuf_InsertText( const char *text ) {
|
|
int len;
|
|
int i;
|
|
|
|
len = strlen( text ) + 1;
|
|
|
|
if ( len + cmd_text.cursize > cmd_text.maxsize ) {
|
|
Com_Printf( "Cbuf_InsertText overflowed\n" );
|
|
return;
|
|
}
|
|
|
|
// move the existing command text
|
|
for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) {
|
|
cmd_text.data[ i + len ] = cmd_text.data[ i ];
|
|
}
|
|
|
|
// copy the new text in
|
|
Com_Memcpy( cmd_text.data, text, len - 1 );
|
|
|
|
// add a \n
|
|
cmd_text.data[ len - 1 ] = '\n';
|
|
|
|
cmd_text.cursize += len;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cbuf_ExecuteText
|
|
============
|
|
*/
|
|
void Cbuf_ExecuteText( cbufExec_t exec_when, const char *text )
|
|
{
|
|
switch (exec_when)
|
|
{
|
|
case EXEC_NOW:
|
|
if ( text && text[0] != '\0' ) {
|
|
Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", text);
|
|
Cmd_ExecuteString (text);
|
|
} else {
|
|
Cbuf_Execute();
|
|
Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", cmd_text.data);
|
|
}
|
|
break;
|
|
case EXEC_INSERT:
|
|
Cbuf_InsertText (text);
|
|
break;
|
|
case EXEC_APPEND:
|
|
Cbuf_AddText (text);
|
|
break;
|
|
default:
|
|
Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when");
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cbuf_Execute
|
|
============
|
|
*/
|
|
void Cbuf_Execute( void )
|
|
{
|
|
int i;
|
|
char *text;
|
|
char line[MAX_CMD_LINE];
|
|
int quotes;
|
|
|
|
// This will keep // style comments all on one line by not breaking on
|
|
// a semicolon. It will keep /* ... */ style comments all on one line by not
|
|
// breaking it for semicolon or newline.
|
|
qboolean in_star_comment = qfalse;
|
|
qboolean in_slash_comment = qfalse;
|
|
while ( cmd_text.cursize > 0 )
|
|
{
|
|
if ( cmd_wait > 0 ) {
|
|
// skip out while text still remains in buffer, leaving it
|
|
// for next frame
|
|
cmd_wait--;
|
|
break;
|
|
}
|
|
|
|
// find a \n or ; line break or comment: // or /* */
|
|
text = (char *)cmd_text.data;
|
|
|
|
quotes = 0;
|
|
for ( i = 0 ; i< cmd_text.cursize ; i++ )
|
|
{
|
|
if (text[i] == '"')
|
|
quotes++;
|
|
|
|
if ( !(quotes&1)) {
|
|
if ( i < cmd_text.cursize - 1 ) {
|
|
if ( !in_star_comment && text[i] == '/' && text[i+1] == '/' )
|
|
in_slash_comment = qtrue;
|
|
else if ( !in_slash_comment && text[i] == '/' && text[i+1] == '*' )
|
|
in_star_comment = qtrue;
|
|
else if ( in_star_comment && text[i] == '*' && text[i+1] == '/' ) {
|
|
in_star_comment = qfalse;
|
|
// If we are in a star comment, then the part after it is valid
|
|
// Note: This will cause it to NUL out the terminating '/'
|
|
// but ExecuteString doesn't require it anyway.
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
if ( !in_slash_comment && !in_star_comment && text[i] == ';')
|
|
break;
|
|
}
|
|
if ( !in_star_comment && (text[i] == '\n' || text[i] == '\r') ) {
|
|
in_slash_comment = qfalse;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i >= (MAX_CMD_LINE - 1) )
|
|
i = MAX_CMD_LINE - 1;
|
|
|
|
Com_Memcpy( line, text, i );
|
|
line[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 == cmd_text.cursize )
|
|
cmd_text.cursize = 0;
|
|
else
|
|
{
|
|
i++;
|
|
cmd_text.cursize -= i;
|
|
// skip all repeating newlines/semicolons
|
|
while ( ( text[i] == '\n' || text[i] == '\r' || text[i] == ';' ) && cmd_text.cursize > 0 ) {
|
|
cmd_text.cursize--;
|
|
i++;
|
|
}
|
|
memmove( text, text+i, cmd_text.cursize );
|
|
}
|
|
|
|
// execute the command line
|
|
Cmd_ExecuteString( line );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
SCRIPT COMMANDS
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
===============
|
|
Cmd_Exec_f
|
|
===============
|
|
*/
|
|
static void Cmd_Exec_f( void ) {
|
|
qboolean quiet;
|
|
union {
|
|
char *c;
|
|
void *v;
|
|
} f;
|
|
char filename[MAX_QPATH];
|
|
|
|
quiet = !Q_stricmp(Cmd_Argv(0), "execq");
|
|
|
|
if (Cmd_Argc () != 2) {
|
|
Com_Printf ("exec%s <filename> : execute a script file%s\n",
|
|
quiet ? "q" : "", quiet ? " without notification" : "");
|
|
return;
|
|
}
|
|
|
|
Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
|
|
COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
|
|
FS_BypassPure();
|
|
FS_ReadFile( filename, &f.v );
|
|
FS_RestorePure();
|
|
if ( f.v == NULL ) {
|
|
Com_Printf( "couldn't exec %s\n", filename );
|
|
return;
|
|
}
|
|
if (!quiet)
|
|
Com_Printf ("execing %s\n", filename);
|
|
|
|
Cbuf_InsertText( f.c );
|
|
|
|
#ifdef DELAY_WRITECONFIG
|
|
if ( !Q_stricmp( filename, Q3CONFIG_CFG ) ) {
|
|
Com_WriteConfiguration(); // to avoid loading outdated values
|
|
}
|
|
#endif
|
|
|
|
FS_FreeFile( f.v );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
Cmd_ExecScript_f
|
|
===============
|
|
*/
|
|
static void Cmd_ExecScript_f( void ) {
|
|
union {
|
|
char *c;
|
|
void *v;
|
|
} f;
|
|
char filename[MAX_QPATH];
|
|
|
|
|
|
if (Cmd_Argc () != 2) {
|
|
Com_Printf ("execscript <filename> : execute a ArenaScript file\n");
|
|
return;
|
|
}
|
|
|
|
Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
|
|
COM_DefaultExtension( filename, sizeof( filename ), ".as" );
|
|
FS_BypassPure();
|
|
FS_ReadFile( filename, &f.v );
|
|
FS_RestorePure();
|
|
if ( f.v == NULL ) {
|
|
//Com_Printf( "couldn't exec %s\n", filename );
|
|
return;
|
|
}
|
|
|
|
if(cl_arenascriptDebug->integer){
|
|
Com_Printf( ".as| %s: Script %s loaded.\n", Cmd_Argv( 0 ), filename );
|
|
}
|
|
|
|
Cbuf_AddText( f.c );
|
|
|
|
FS_FreeFile( f.v );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
Cmd_Vstr_f
|
|
|
|
Inserts the current value of a variable as command text
|
|
===============
|
|
*/
|
|
static void Cmd_Vstr_f( void ) {
|
|
const char *v;
|
|
|
|
if ( Cmd_Argc () != 2 ) {
|
|
Com_Printf( "vstr <variablename> : execute a variable command\n" );
|
|
return;
|
|
}
|
|
|
|
v = Cvar_VariableString( Cmd_Argv( 1 ) );
|
|
Cbuf_InsertText( v );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
Cmd_Echo_f
|
|
|
|
Just prints the rest of the line to the console
|
|
===============
|
|
*/
|
|
static void Cmd_Echo_f( void )
|
|
{
|
|
Com_Printf( "%s\n", Cmd_ArgsFrom( 1 ) );
|
|
}
|
|
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
COMMAND EXECUTION
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
typedef struct cmd_function_s
|
|
{
|
|
struct cmd_function_s *next;
|
|
char *name;
|
|
xcommand_t function;
|
|
completionFunc_t complete;
|
|
} cmd_function_t;
|
|
|
|
|
|
static int cmd_argc;
|
|
static char *cmd_argv[MAX_STRING_TOKENS]; // points into cmd_tokenized
|
|
static char cmd_tokenized[BIG_INFO_STRING+MAX_STRING_TOKENS]; // will have 0 bytes inserted
|
|
static char cmd_cmd[BIG_INFO_STRING]; // the original command we received (no token processing)
|
|
|
|
static cmd_function_t *cmd_functions; // possible commands to execute
|
|
|
|
/*
|
|
============
|
|
Cmd_Argc
|
|
============
|
|
*/
|
|
int Cmd_Argc( void ) {
|
|
return cmd_argc;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_Clear
|
|
============
|
|
*/
|
|
void Cmd_Clear( void ) {
|
|
cmd_cmd[0] = '\0';
|
|
cmd_argc = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_Argv
|
|
============
|
|
*/
|
|
const char *Cmd_Argv( int arg ) {
|
|
if ( (unsigned)arg >= cmd_argc ) {
|
|
return "";
|
|
}
|
|
return cmd_argv[arg];
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_ArgvBuffer
|
|
|
|
The interpreted versions use this because
|
|
they can't have pointers returned to them
|
|
============
|
|
*/
|
|
void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) {
|
|
Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_Args
|
|
|
|
Returns a single string containing argv(arg) to argv(argc()-1)
|
|
============
|
|
*/
|
|
char *Cmd_ArgsFrom( int arg ) {
|
|
static char cmd_args[BIG_INFO_STRING], *s;
|
|
int i;
|
|
|
|
s = cmd_args;
|
|
*s = '\0';
|
|
if (arg < 0)
|
|
arg = 0;
|
|
for ( i = arg ; i < cmd_argc ; i++ ) {
|
|
s = Q_stradd( s, cmd_argv[i] );
|
|
if ( i != cmd_argc-1 ) {
|
|
s = Q_stradd( s, " " );
|
|
}
|
|
}
|
|
|
|
return cmd_args;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_ArgsBuffer
|
|
|
|
The interpreted versions use this because
|
|
they can't have pointers returned to them
|
|
============
|
|
*/
|
|
void Cmd_ArgsBuffer( char *buffer, int bufferLength ) {
|
|
Q_strncpyz( buffer, Cmd_ArgsFrom( 1 ), bufferLength );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_Cmd
|
|
|
|
Retrieve the unmodified command string
|
|
For rcon use when you want to transmit without altering quoting
|
|
https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
|
|
============
|
|
*/
|
|
char *Cmd_Cmd( void )
|
|
{
|
|
return cmd_cmd;
|
|
}
|
|
|
|
|
|
/*
|
|
Replace command separators with space to prevent interpretation
|
|
This is a hack to protect buggy qvms
|
|
https://bugzilla.icculus.org/show_bug.cgi?id=3593
|
|
https://bugzilla.icculus.org/show_bug.cgi?id=4769
|
|
*/
|
|
void Cmd_Args_Sanitize( const char *separators )
|
|
{
|
|
int i;
|
|
|
|
for( i = 1; i < cmd_argc; i++ )
|
|
{
|
|
char *c = cmd_argv[i];
|
|
|
|
while ( ( c = strpbrk( c, separators ) ) != NULL ) {
|
|
*c = ' ';
|
|
++c;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_TokenizeString
|
|
|
|
Parses the given string into command line tokens.
|
|
The text is copied to a separate buffer and 0 characters
|
|
are inserted in the appropriate place, The argv array
|
|
will point into this temporary buffer.
|
|
============
|
|
*/
|
|
// NOTE TTimo define that to track tokenization issues
|
|
//#define TKN_DBG
|
|
static void Cmd_TokenizeString2( const char *text_in, qboolean ignoreQuotes ) {
|
|
const char *text;
|
|
char *textOut;
|
|
|
|
#ifdef TKN_DBG
|
|
// FIXME TTimo blunt hook to try to find the tokenization of userinfo
|
|
Com_DPrintf("Cmd_TokenizeString: %s\n", text_in);
|
|
#endif
|
|
|
|
// clear previous args
|
|
cmd_argc = 0;
|
|
cmd_cmd[0] = '\0';
|
|
|
|
if ( !text_in ) {
|
|
return;
|
|
}
|
|
|
|
Q_strncpyz( cmd_cmd, text_in, sizeof( cmd_cmd ) );
|
|
|
|
text = cmd_cmd; // read from safe-length buffer
|
|
textOut = cmd_tokenized;
|
|
|
|
while ( 1 ) {
|
|
if ( cmd_argc >= ARRAY_LEN( cmd_argv ) ) {
|
|
return; // this is usually something malicious
|
|
}
|
|
|
|
while ( 1 ) {
|
|
// skip whitespace
|
|
while ( *text && *text <= ' ' ) {
|
|
text++;
|
|
}
|
|
if ( !*text ) {
|
|
return; // all tokens parsed
|
|
}
|
|
|
|
// skip // comments
|
|
if ( text[0] == '/' && text[1] == '/' ) {
|
|
// accept protocol headers (e.g. http://) in command lines that matching "*?[a-z]://" pattern
|
|
if ( text < cmd_cmd + 3 || text[-1] != ':' || text[-2] < 'a' || text[-2] > 'z' ) {
|
|
return; // all tokens parsed
|
|
}
|
|
}
|
|
|
|
// skip /* */ comments
|
|
if ( text[0] == '/' && text[1] =='*' ) {
|
|
while ( *text && ( text[0] != '*' || text[1] != '/' ) ) {
|
|
text++;
|
|
}
|
|
if ( !*text ) {
|
|
return; // all tokens parsed
|
|
}
|
|
text += 2;
|
|
} else {
|
|
break; // we are ready to parse a token
|
|
}
|
|
}
|
|
|
|
// handle quoted strings
|
|
// NOTE TTimo this doesn't handle \" escaping
|
|
if ( !ignoreQuotes && *text == '"' ) {
|
|
cmd_argv[cmd_argc] = textOut;
|
|
cmd_argc++;
|
|
text++;
|
|
while ( *text && *text != '"' ) {
|
|
*textOut++ = *text++;
|
|
}
|
|
*textOut++ = '\0';
|
|
if ( !*text ) {
|
|
return; // all tokens parsed
|
|
}
|
|
text++;
|
|
continue;
|
|
}
|
|
|
|
// regular token
|
|
cmd_argv[cmd_argc] = textOut;
|
|
cmd_argc++;
|
|
|
|
// skip until whitespace, quote, or command
|
|
while ( *text > ' ' ) {
|
|
if ( !ignoreQuotes && text[0] == '"' ) {
|
|
break;
|
|
}
|
|
|
|
if ( text[0] == '/' && text[1] == '/' ) {
|
|
// accept protocol headers (e.g. http://) in command lines that matching "*?[a-z]://" pattern
|
|
if ( text < cmd_cmd + 3 || text[-1] != ':' || text[-2] < 'a' || text[-2] > 'z' ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// skip /* */ comments
|
|
if ( text[0] == '/' && text[1] =='*' ) {
|
|
break;
|
|
}
|
|
|
|
*textOut++ = *text++;
|
|
}
|
|
|
|
*textOut++ = '\0';
|
|
|
|
if ( !*text ) {
|
|
return; // all tokens parsed
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_TokenizeString
|
|
============
|
|
*/
|
|
void Cmd_TokenizeString( const char *text_in ) {
|
|
Cmd_TokenizeString2( text_in, qfalse );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_TokenizeStringIgnoreQuotes
|
|
============
|
|
*/
|
|
void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ) {
|
|
Cmd_TokenizeString2( text_in, qtrue );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_FindCommand
|
|
============
|
|
*/
|
|
static cmd_function_t *Cmd_FindCommand( const char *cmd_name )
|
|
{
|
|
cmd_function_t *cmd;
|
|
for( cmd = cmd_functions; cmd; cmd = cmd->next )
|
|
if( !Q_stricmp( cmd_name, cmd->name ) )
|
|
return cmd;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_AddCommand
|
|
============
|
|
*/
|
|
void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) {
|
|
cmd_function_t *cmd;
|
|
|
|
// fail if the command already exists
|
|
if ( Cmd_FindCommand( cmd_name ) )
|
|
{
|
|
// allow completion-only commands to be silently doubled
|
|
if ( function != NULL )
|
|
Com_Printf( "Cmd_AddCommand: %s already defined\n", cmd_name );
|
|
return;
|
|
}
|
|
|
|
// use a small malloc to avoid zone fragmentation
|
|
cmd = S_Malloc( sizeof( *cmd ) );
|
|
cmd->name = CopyString( cmd_name );
|
|
cmd->function = function;
|
|
cmd->complete = NULL;
|
|
cmd->next = cmd_functions;
|
|
cmd_functions = cmd;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_SetCommandCompletionFunc
|
|
============
|
|
*/
|
|
void Cmd_SetCommandCompletionFunc( const char *command, completionFunc_t complete ) {
|
|
cmd_function_t *cmd;
|
|
|
|
for( cmd = cmd_functions; cmd; cmd = cmd->next ) {
|
|
if( !Q_stricmp( command, cmd->name ) ) {
|
|
cmd->complete = complete;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_RemoveCommand
|
|
============
|
|
*/
|
|
void Cmd_RemoveCommand( const char *cmd_name ) {
|
|
cmd_function_t *cmd, **back;
|
|
|
|
back = &cmd_functions;
|
|
while( 1 ) {
|
|
cmd = *back;
|
|
if ( !cmd ) {
|
|
// command wasn't active
|
|
return;
|
|
}
|
|
if ( !Q_stricmp( cmd_name, cmd->name ) ) {
|
|
*back = cmd->next;
|
|
if (cmd->name) {
|
|
Z_Free(cmd->name);
|
|
}
|
|
Z_Free (cmd);
|
|
return;
|
|
}
|
|
back = &cmd->next;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_RemoveCommandSafe
|
|
|
|
Only remove commands with no associated function
|
|
============
|
|
*/
|
|
void Cmd_RemoveCommandSafe( const char *cmd_name )
|
|
{
|
|
const cmd_function_t *cmd = Cmd_FindCommand( cmd_name );
|
|
|
|
if( !cmd )
|
|
return;
|
|
if( cmd->function )
|
|
{
|
|
Com_Error( ERR_DROP, "Restricted source tried to remove "
|
|
"system command \"%s\"", cmd_name );
|
|
return;
|
|
}
|
|
|
|
Cmd_RemoveCommand( cmd_name );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_RemoveCgameCommands
|
|
|
|
Remove cgame-created commands
|
|
============
|
|
*/
|
|
void Cmd_RemoveCgameCommands( void )
|
|
{
|
|
const cmd_function_t *cmd;
|
|
qboolean removed;
|
|
|
|
do {
|
|
removed = qfalse;
|
|
for ( cmd = cmd_functions ; cmd ; cmd = cmd->next ) {
|
|
if ( cmd->function == NULL ) {
|
|
Cmd_RemoveCommand( cmd->name );
|
|
removed = qtrue;
|
|
break;
|
|
}
|
|
}
|
|
} while ( removed );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_CommandCompletion
|
|
============
|
|
*/
|
|
void Cmd_CommandCompletion( void(*callback)(const char *s) ) {
|
|
const cmd_function_t *cmd;
|
|
|
|
for ( cmd = cmd_functions ; cmd ; cmd=cmd->next ) {
|
|
callback( cmd->name );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_CompleteArgument
|
|
============
|
|
*/
|
|
qboolean Cmd_CompleteArgument( const char *command, const char *args, int argNum ) {
|
|
const cmd_function_t *cmd;
|
|
|
|
for( cmd = cmd_functions; cmd; cmd = cmd->next ) {
|
|
if ( !Q_stricmp( command, cmd->name ) ) {
|
|
if ( cmd->complete ) {
|
|
cmd->complete( args, argNum );
|
|
}
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_ExecuteString
|
|
|
|
A complete command line has been parsed, so try to execute it
|
|
============
|
|
*/
|
|
void Cmd_ExecuteString( const char *text ) {
|
|
cmd_function_t *cmd, **prev;
|
|
|
|
// execute the command line
|
|
Cmd_TokenizeString( text );
|
|
if ( !Cmd_Argc() ) {
|
|
return; // no tokens
|
|
}
|
|
|
|
// check registered command functions
|
|
for ( prev = &cmd_functions ; *prev ; prev = &cmd->next ) {
|
|
cmd = *prev;
|
|
if ( !Q_stricmp( cmd_argv[0], cmd->name ) ) {
|
|
// rearrange the links so that the command will be
|
|
// near the head of the list next time it is used
|
|
*prev = cmd->next;
|
|
cmd->next = cmd_functions;
|
|
cmd_functions = cmd;
|
|
|
|
// perform the action
|
|
if ( !cmd->function ) {
|
|
// let the cgame or game handle it
|
|
break;
|
|
} else {
|
|
cmd->function();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check cvars
|
|
if ( Cvar_Command() ) {
|
|
return;
|
|
}
|
|
|
|
#ifndef DEDICATED
|
|
// check client game commands
|
|
if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// check server game commands
|
|
if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) {
|
|
return;
|
|
}
|
|
|
|
#ifndef DEDICATED
|
|
// check ui commands
|
|
if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) {
|
|
return;
|
|
}
|
|
|
|
// send it as a server command if we are connected
|
|
// this will usually result in a chat message
|
|
CL_ForwardCommandToServer( text );
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_List_f
|
|
============
|
|
*/
|
|
static void Cmd_List_f( void )
|
|
{
|
|
const cmd_function_t *cmd;
|
|
const char *match;
|
|
int i;
|
|
|
|
if ( Cmd_Argc() > 1 ) {
|
|
match = Cmd_Argv( 1 );
|
|
} else {
|
|
match = NULL;
|
|
}
|
|
|
|
i = 0;
|
|
for ( cmd = cmd_functions ; cmd ; cmd=cmd->next ) {
|
|
if ( match && !Com_Filter( match, cmd->name ) )
|
|
continue;
|
|
Com_Printf( "%s\n", cmd->name );
|
|
i++;
|
|
}
|
|
Com_Printf( "%i commands\n", i );
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
Cmd_CompleteCfgName
|
|
==================
|
|
*/
|
|
static void Cmd_CompleteCfgName( const char *args, int argNum ) {
|
|
if( argNum == 2 ) {
|
|
Field_CompleteFilename( "", "cfg", qfalse, FS_MATCH_ANY | FS_MATCH_STICK );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Cmd_CompleteScriptName
|
|
==================
|
|
*/
|
|
static void Cmd_CompleteScriptName( const char *args, int argNum ) {
|
|
if( argNum == 2 ) {
|
|
Field_CompleteFilename( "", "as", qfalse, FS_MATCH_ANY | FS_MATCH_STICK );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
Cmd_CompleteWriteCfgName
|
|
==================
|
|
*/
|
|
void Cmd_CompleteWriteCfgName( const char *args, int argNum ) {
|
|
if( argNum == 2 ) {
|
|
Field_CompleteFilename( "", "cfg", qfalse, FS_MATCH_EXTERN | FS_MATCH_STICK );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cmd_Init
|
|
============
|
|
*/
|
|
void Cmd_Init( void ) {
|
|
Cmd_AddCommand ("cmdlist",Cmd_List_f);
|
|
Cmd_AddCommand ("exec",Cmd_Exec_f);
|
|
Cmd_AddCommand ("execscript",Cmd_ExecScript_f);
|
|
Cmd_AddCommand ("execq",Cmd_Exec_f);
|
|
Cmd_SetCommandCompletionFunc( "exec", Cmd_CompleteCfgName );
|
|
Cmd_SetCommandCompletionFunc( "execscript", Cmd_CompleteScriptName );
|
|
Cmd_SetCommandCompletionFunc( "execq", Cmd_CompleteCfgName );
|
|
Cmd_AddCommand ("vstr",Cmd_Vstr_f);
|
|
Cmd_SetCommandCompletionFunc( "vstr", Cvar_CompleteCvarName );
|
|
Cmd_AddCommand ("echo",Cmd_Echo_f);
|
|
Cmd_AddCommand ("wait", Cmd_Wait_f);
|
|
}
|