diff options
Diffstat (limited to 'source/cmd.c')
-rw-r--r-- | source/cmd.c | 1599 |
1 files changed, 1599 insertions, 0 deletions
diff --git a/source/cmd.c b/source/cmd.c new file mode 100644 index 0000000..c697b74 --- /dev/null +++ b/source/cmd.c @@ -0,0 +1,1599 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +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 2 +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cmd.c -- Quake script command processing module + +#include "com_local.h" +#include "q_list.h" + +#define Cmd_Malloc( size ) Z_TagMalloc( size, TAG_CMD ) +#define Cmd_CopyString( string ) Z_TagCopyString( string, TAG_CMD ) + +cmdAPI_t cmd; + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +char cmd_buffer_text[CMD_BUFFER_SIZE]; +cmdbuf_t 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 "impulse 5 ; +attack ; wait ; -attack ; impulse 2" +============ +*/ +static void Cmd_Wait_f( void ) { + if( Cmd_Argc() > 1 ) { + cmd_buffer.waitCount = atoi( Cmd_Argv( 1 ) ); + } else { + cmd_buffer.waitCount = 1; + } +} + +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init( void ) { + memset( &cmd_buffer, 0, sizeof( cmd_buffer ) ); + cmd_buffer.text = cmd_buffer_text; + cmd_buffer.maxsize = sizeof( cmd_buffer_text ); + cmd_buffer.exec = Cmd_ExecuteString; +} + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer +============ +*/ +void Cbuf_AddTextEx( cmdbuf_t *buf, const char *text ) { + int l = strlen( text ); + + if( buf->cursize + l > buf->maxsize ) { + Com_WPrintf( "Cbuf_AddText: overflow\n" ); + return; + } + memcpy( buf->text + buf->cursize, text, l ); + buf->cursize += l; +} + +char *Cbuf_Alloc( cmdbuf_t *buf, int length ) { + char *text; + + if( buf->cursize + length > buf->maxsize ) { + return NULL; + } + text = buf->text + buf->cursize; + buf->cursize += length; + + return text; +} + +/* +============ +Cbuf_InsertText + +Adds command text at the beginning of command buffer. +Adds a \n to the text. +============ +*/ +void Cbuf_InsertTextEx( cmdbuf_t *buf, const char *text ) { + int l = strlen( text ); + +// add the entire text of the file + if( !l ) { + return; + } + if( buf->cursize + l + 1 > buf->maxsize ) { + Com_WPrintf( "Cbuf_InsertText: overflow\n" ); + return; + } + + memmove( buf->text + l + 1, buf->text, buf->cursize ); + memcpy( buf->text, text, l ); + buf->text[l] = '\n'; + buf->cursize += l + 1; +} + +/* +============ +Cbuf_ExecuteText +============ +*/ +void Cbuf_ExecuteText( cbufExecWhen_t exec_when, const char *text ) { + switch( exec_when ) { + case EXEC_NOW: + Cmd_ExecuteString( text ); + 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_ExecuteEx( cmdbuf_t *buf ) { + int i; + char *text; + char line[MAX_STRING_CHARS]; + int quotes; + + while( buf->cursize ) { + if( buf->waitCount > 0 ) { + // skip out while text still remains in buffer, leaving it + // for next frame (counter is decremented externally now) + return; + } + +// find a \n or ; line break + text = buf->text; + + quotes = 0; + for( i = 0; i < buf->cursize; i++ ) { + if( text[i] == '"' ) + quotes++; + if( !( quotes & 1 ) && text[i] == ';' ) + break; // don't break if inside a quoted string + if( text[i] == '\n' ) + break; + } + + // check for overflow + if( i > sizeof( line ) - 1 ) { + i = sizeof( line ) - 1; + } + + 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, alias) can insert data at the +// beginning of the text buffer + if( i == buf->cursize ) { + buf->cursize = 0; + } else { + i++; + buf->cursize -= i; + memmove( text, text + i, buf->cursize ); + } + +// execute the command line + buf->exec( line ); + + } + + buf->aliasCount = 0; // don't allow infinite alias loops +} + +/* +============================================================================== + + SCRIPT COMMANDS + +============================================================================== +*/ + +typedef struct cmdalias_s { + list_t hashEntry; + list_t listEntry; + char *value; + char name[1]; +} cmdalias_t; + +#define ALIAS_HASH_SIZE 64 + +static list_t cmd_alias; +static list_t cmd_aliasHash[ALIAS_HASH_SIZE]; + +/* +=============== +Cmd_AliasFind +=============== +*/ +cmdalias_t *Cmd_AliasFind( const char *name ) { + uint32 hash; + cmdalias_t *alias; + + hash = Com_HashString( name, ALIAS_HASH_SIZE ); + LIST_FOR_EACH( cmdalias_t, alias, &cmd_aliasHash[hash], hashEntry ) { + if( !strcmp( name, alias->name ) ) { + return alias; + } + } + + return NULL; +} + +char *Cmd_AliasCommand( const char *name ) { + cmdalias_t *a; + + a = Cmd_AliasFind( name ); + if( !a ) { + return NULL; + } + + return a->value; +} + +void Cmd_AliasSet( const char *name, const char *cmd ) { + cmdalias_t *a; + uint32 hash; + + // if the alias already exists, reuse it + a = Cmd_AliasFind( name ); + if( a ) { + Z_Free( a->value ); + a->value = Cmd_CopyString( cmd ); + return; + } + + a = Cmd_Malloc( sizeof( cmdalias_t ) + strlen( name ) ); + strcpy( a->name, name ); + a->value = Cmd_CopyString( cmd ); + + List_Append( &cmd_alias, &a->listEntry ); + + hash = Com_HashString( name, ALIAS_HASH_SIZE ); + List_Append( &cmd_aliasHash[hash], &a->hashEntry ); +} + +/* +=============== +Cmd_Alias_f + +Creates a new command that executes a command string (possibly ; seperated) +=============== +*/ +void Cmd_Alias_f( void ) { + cmdalias_t *a; + char *s, *cmd; + + if( Cmd_Argc() < 2 ) { + if( LIST_EMPTY( &cmd_alias ) ) { + Com_Printf( "No alias commands registered\n" ); + return; + } + Com_Printf( "Current alias commands:\n" ); + LIST_FOR_EACH( cmdalias_t, a, &cmd_alias, listEntry ) { + Com_Printf( "\"%s\" = \"%s\"\n", a->name, a->value ); + } + return; + } + + s = Cmd_Argv( 1 ); + if( Cmd_Exists( s ) ) { + Com_Printf( "\"%s\" already defined as a command\n", s ); + return; + } + + if( Cvar_Exists( s ) ) { + Com_Printf( "\"%s\" already defined as a cvar\n", s ); + return; + } + + if( Cmd_Argc() < 3 ) { + a = Cmd_AliasFind( s ); + if( a ) { + Com_Printf( "\"%s\" = \"%s\"\n", a->name, a->value ); + } else { + Com_Printf( "\"%s\" is undefined\n", s ); + } + return; + } + + // copy the rest of the command line + cmd = Cmd_ArgsFrom( 2 ); + Cmd_AliasSet( s, cmd ); +} + +static void Cmd_UnAlias_f( void ) { + char *s; + cmdalias_t *a, *n; + uint32 hash; + + if( Cmd_CheckParam( "-h", "--help" ) ) { +usage: + Com_Printf( "Usage: %s [-h] [-a] [alias_name]\n" + "-h|--help : display this message\n" + "-a|--all : delete everything\n" + "Either -a or alias_name should be given\n", Cmd_Argv( 0 ) ); + return; + } + + if( Cmd_CheckParam( "a", "all" ) ) { + LIST_FOR_EACH_SAFE( cmdalias_t, a, n, &cmd_alias, listEntry ) { + Z_Free( a->value ); + Z_Free( a ); + } + for( hash = 0; hash < ALIAS_HASH_SIZE; hash++ ) { + List_Init( &cmd_aliasHash[hash] ); + } + List_Init( &cmd_alias ); + Com_Printf( "Removed all aliases\n" ); + return; + } + + if( Cmd_Argc() < 2 ) { + goto usage; + } + s = Cmd_Argv( 1 ); + a = Cmd_AliasFind( s ); + if( !a ) { + Com_Printf( "\"%s\" is undefined\n", s ); + return; + } + + List_Delete( &a->listEntry ); + List_Delete( &a->hashEntry ); + + Z_Free( a->value ); + Z_Free( a ); +} + +/* +============================================================================= + + MESSAGE TRIGGERS + +============================================================================= +*/ + +typedef struct cmd_trigger_s { + list_t entry; + trigChannel_t chan; + char *match; + char *command; +} cmd_trigger_t; + +static list_t cmd_triggers; + +static struct { + const char *name; + const char *desc; + trigChannel_t chan; +} triggers[] = { + { "client", "misc client events", TRIG_CLIENT_SYSTEM }, + { "chat", "client chat messages", TRIG_CLIENT_CHAT }, + { "print", "client print messages (default)", TRIG_CLIENT_PRINT }, + { "cprint", "client centerprint messages", TRIG_CLIENT_CENTERPRINT }, + { "server", "misc server events", TRIG_SERVER_SYSTEM }, + { "bprint", "game DLL broadcast messages", TRIG_SERVER_BPRINT }, + { "dprint", "game DLL console messages", TRIG_SERVER_DPRINT }, + { NULL, 0 } +}; + +static const char *chan2string( trigChannel_t chan ) { + int i; + + for( i = 0; triggers[i].name; i++ ) { + if( triggers[i].chan == chan ) { + return triggers[i].name; + } + } + return "unknown"; +} + +/* +============ +Cmd_Trigger_f +============ +*/ +static void Cmd_Trigger_f( void ) { + cmd_trigger_t *trigger; + char *command, *match, *name; + int cmdLength, matchLength; + trigChannel_t chan; + int i; + + if( Cmd_Argc() == 1 ) { + if( LIST_EMPTY( &cmd_triggers ) ) { + Com_Printf( "No triggers registered\n" ); + return; + } + Com_Printf( "Current message triggers:\n" ); + LIST_FOR_EACH( cmd_trigger_t, trigger, &cmd_triggers, entry ) { + Com_Printf( "\"%s\" = \"%s\" [%s]\n", + trigger->match, trigger->command, + chan2string( trigger->chan ) ); + } + return; + } + + if( Cmd_Argc() < 3 ) { +usage: + Com_Printf( "Usage: %s <command> <match> [channel]\n", Cmd_Argv( 0 ) ); + Com_Printf( "Valid channels:\n" ); + for( i = 0; triggers[i].name; i++ ) { + Com_Printf( "%6s - %s\n", triggers[i].name, triggers[i].desc ); + } + return; + } + + command = Cmd_Argv( 1 ); + match = Cmd_Argv( 2 ); + + chan = TRIG_CLIENT_PRINT; + if( Cmd_Argc() > 3 ) { + name = Cmd_Argv( 3 ); + for( i = 0; triggers[i].name; i++ ) { + if( !strcmp( name, triggers[i].name ) ) { + chan = triggers[i].chan; + break; + } + } + if( !triggers[i].name ) { + Com_Printf( "Unknown channel '%s'\n", name ); + goto usage; + } + } + + // don't create the same trigger twice + LIST_FOR_EACH( cmd_trigger_t, trigger, &cmd_triggers, entry ) { + if( trigger->chan == chan && + !strcmp( trigger->command, command ) && + !strcmp( trigger->match, match ) ) + { + return; + } + } + + cmdLength = strlen( command ) + 1; + matchLength = strlen( match ) + 1; + + trigger = Cmd_Malloc( sizeof( cmd_trigger_t ) + + cmdLength + matchLength ); + trigger->chan = chan; + trigger->command = ( char * )( trigger + 1 ); + trigger->match = trigger->command + cmdLength; + List_Append( &cmd_triggers, &trigger->entry ); + + strcpy( trigger->command, command ); + strcpy( trigger->match, match ); +} + +/* +============ +Cmd_UnTrigger_f +============ +*/ +static void Cmd_UnTrigger_f( void ) { + cmd_trigger_t *trigger, *next; + char *command, *match; + + if( Cmd_CheckParam( "-h", "--help" ) ) { +usage: + Com_Printf( "Usage: %s [-h] [-a] [-c <command>] [-m <match>]\n" + "-h|--help : display this message\n" + "-a|--all : delete everything\n" + "-c|--command : delete by command\n" + "-m|--match : delete by match\n" + "Either -a or combination of -c and -m should be given\n", + Cmd_Argv( 0 ) ); + return; + } + + if( Cmd_CheckParam( "a", "all" ) ) { + LIST_FOR_EACH_SAFE( cmd_trigger_t, trigger, next, &cmd_triggers, entry ) { + Z_Free( trigger ); + } + List_Init( &cmd_triggers ); + Com_Printf( "Removed all triggers\n" ); + return; + } + + command = Cmd_FindParam( "c", "command" ); + match = Cmd_FindParam( "m", "match" ); + if( !command && !match ) { + goto usage; + } + + LIST_FOR_EACH_SAFE( cmd_trigger_t, trigger, next, &cmd_triggers, entry ) { + if( command && strcmp( trigger->command, command ) ) { + continue; + } + if( match && strcmp( trigger->match, match ) ) { + continue; + } + + Com_Printf( "Removed \"%s\" = \"%s\" [%s]\n", + trigger->match, trigger->command, + chan2string( trigger->chan ) ); + + List_Delete( &trigger->entry ); + Z_Free( trigger ); + } +} + +/* +============ +Cmd_ExecTrigger +============ +*/ +void Cmd_ExecTrigger( trigChannel_t chan, const char *string ) { + cmd_trigger_t *trigger; + char *text; + + // execute matching triggers + LIST_FOR_EACH( cmd_trigger_t, trigger, &cmd_triggers, entry ) { + if( trigger->chan != chan ) { + continue; + } + text = Cmd_MacroExpandString( trigger->match, qfalse ); + if( text && Com_WildCmp( text, string, qtrue ) ) { + Cbuf_AddText( trigger->command ); + Cbuf_AddText( "\n" ); + } + } +} + +/* +============================================================================= + + MACRO EXECUTION + +============================================================================= +*/ + +typedef struct cmd_macro_s { + struct cmd_macro_s *next; + struct cmd_macro_s *hashNext; + + const char *name; + xmacro_t function; +} cmd_macro_t; + +#define MACRO_HASH_SIZE 64 + +static cmd_macro_t *cmd_macros; +static cmd_macro_t *cmd_macroHash[MACRO_HASH_SIZE]; + +/* +============ +Cmd_FindMacro +============ +*/ +static cmd_macro_t *Cmd_FindMacro( const char *name ) { + cmd_macro_t *macro; + uint32 hash; + + hash = Com_HashString( name, MACRO_HASH_SIZE ); + for( macro = cmd_macroHash[hash]; macro ; macro = macro->hashNext ) { + if( !strcmp( macro->name, name ) ) { + return macro; + } + } + + return NULL; +} + +xmacro_t Cmd_FindMacroFunction( const char *name ) { + cmd_macro_t *macro; + + macro = Cmd_FindMacro( name ); + if( !macro ) { + return NULL; + } + + return macro->function; +} + +/* +============ +Cmd_AddMacro +============ +*/ +void Cmd_AddMacro( const char *name, xmacro_t function ) { + cmd_macro_t *macro; + cvar_t *var; + uint32 hash; + + var = Cvar_FindVar( name ); + if( var && !( var->flags & CVAR_USER_CREATED ) ) { + Com_WPrintf( "Cmd_AddMacro: %s already defined as a cvar\n", name ); + return; + } + +// fail if the macro already exists + if( Cmd_FindMacro( name ) ) { + Com_WPrintf( "Cmd_AddMacro: %s already defined\n", name ); + return; + } + + hash = Com_HashString( name, MACRO_HASH_SIZE ); + + macro = Cmd_Malloc( sizeof( cmd_macro_t ) ); + macro->name = name; + macro->function = function; + macro->next = cmd_macros; + cmd_macros = macro; + macro->hashNext = cmd_macroHash[hash]; + cmd_macroHash[hash] = macro; +} + + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ + +#define CMD_HASH_SIZE 128 + +typedef struct cmd_function_s { + list_t hashEntry; + list_t listEntry; + + xcommand_t function; + xgenerator_t generator1; + xgenerator_t generator2; + xgenerator_t generator3; + const char *name; +} cmd_function_t; + +static list_t cmd_functions; /* possible commands to execute */ +static list_t cmd_hash[CMD_HASH_SIZE]; + +static int cmd_argc; +static char *cmd_argv[MAX_STRING_TOKENS]; +static char *cmd_null_string = ""; + +/* complete command string, quotes preserved */ +static char cmd_args[MAX_STRING_CHARS]; + +/* offsets of individual tokens in cmd_args */ +static int cmd_offsets[MAX_STRING_TOKENS]; + +/* sequence of NULL-terminated tokens, each cmd_argv[] points here */ +static char cmd_data[MAX_STRING_CHARS]; + +int Cmd_ArgOffset( int arg ) { + if( arg < 0 ) { + return 0; + } + if( arg >= cmd_argc ) { + return strlen( cmd_args ); + } + return cmd_offsets[arg]; +} + +int Cmd_FindArgForOffset( int offset ) { + int i; + + for( i = 1; i < cmd_argc; i++ ) { + if( offset < cmd_offsets[i] ) { + return i - 1; + } + } + return i - 1; +} + +/* +============ +Cmd_Argc +============ +*/ +int Cmd_Argc( void ) { + return cmd_argc; +} + +/* +============ +Cmd_Argv +============ +*/ +char *Cmd_Argv( int arg ) { + if( arg < 0 || arg >= cmd_argc ) { + return cmd_null_string; + } + return cmd_argv[arg]; +} + +/* +============ +Cmd_ArgvBuffer +============ +*/ +void Cmd_ArgvBuffer( int arg, char *buffer, int bufferSize ) { + char *s; + + if( arg < 0 || arg >= cmd_argc ) { + s = cmd_null_string; + } else { + s = cmd_argv[arg]; + } + + Q_strncpyz( buffer, s, bufferSize ); +} + + +/* +============ +Cmd_Args + +Returns a single string containing argv(1) to argv(argc()-1) +============ +*/ +char *Cmd_Args( void ) { + static char args[MAX_STRING_CHARS]; + int i; + + if( cmd_argc < 2 ) { + return cmd_null_string; + } + + args[0] = 0; + for( i = 1; i < cmd_argc - 1; i++ ) { + strcat( args, cmd_argv[i] ); + strcat( args, " " ); + } + strcat( args, cmd_argv[i] ); + + return args; +} + +char *Cmd_RawArgs( void ) { + if( cmd_argc < 2 ) { + return cmd_null_string; + } + return cmd_args + cmd_offsets[1]; +} + +char *Cmd_RawString( void ) { + return cmd_args; +} + + + +/* +============ +Cmd_ArgsBuffer +============ +*/ +void Cmd_ArgsBuffer( char *buffer, int bufferSize ) { + Q_strncpyz( buffer, Cmd_Args(), bufferSize ); +} + +/* +============ +Cmd_ArgsFrom + +Returns a single string containing argv(1) to argv(from-1) +============ +*/ +char *Cmd_ArgsFrom( int from ) { + static char args[MAX_STRING_CHARS]; + int i; + + if( from < 0 || from >= cmd_argc ) { + return cmd_null_string; + } + + args[0] = 0; + for( i = from; i < cmd_argc - 1; i++ ) { + strcat( args, cmd_argv[i] ); + strcat( args, " " ); + } + strcat( args, cmd_argv[i] ); + + return args; +} + +char *Cmd_RawArgsFrom( int from ) { + int offset; + + if( from < 0 || from >= cmd_argc ) { + return cmd_null_string; + } + + offset = cmd_offsets[from]; + + return cmd_args + offset; +} + +/* +============ +Cmd_EnumParam +============ +*/ +int Cmd_EnumParam( int start, const char *sp, const char *lp ) { + int i; + char *s; + + if( start < 0 || start >= cmd_argc ) { + return 0; + } + for( i = start; i < cmd_argc; i++ ) { + if( *( s = cmd_argv[i] ) == '-' ) { + if( *( ++s ) == '-' ) { + if( !strcmp( s + 1, lp ) ) { + return i; + } + } else if( !strcmp( s, sp ) ) { + return i; + } + } + } + + return 0; +} + +/* +============ +Cmd_CheckParam +============ +*/ +int Cmd_CheckParam( const char *sp, const char *lp ) { + return Cmd_EnumParam( 1, sp, lp ); +} + +/* +============ +Cmd_FindParam +============ +*/ +char *Cmd_FindParam( const char *sp, const char *lp ) { + int i; + + if( ( i = Cmd_EnumParam( 1, sp, lp ) ) && ++i != cmd_argc ) { + return cmd_argv[i]; + } + + return NULL; +} + +/* +====================== +Cmd_MacroExpandString +====================== +*/ +char *Cmd_MacroExpandString( const char *text, qboolean aliasHack ) { + int i, j, count, len; + qboolean inquote; + char *scan, *start; + static char expanded[MAX_STRING_CHARS]; + char temporary[MAX_STRING_CHARS]; + char buffer[MAX_TOKEN_CHARS]; + char *token; + cmd_macro_t *macro; + cvar_t *var; + qboolean rescan; + + len = strlen( text ); + if( len >= MAX_STRING_CHARS ) { + Com_Printf( "Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS ); + return NULL; + } + + strcpy( expanded, text ); + scan = expanded; + + inquote = qfalse; + count = 0; + + for( i = 0; i < len; i++ ) { + if( !scan[i] ) { + break; + } + if( scan[i] == '"' ) { + inquote ^= 1; + } + if( inquote ) { + continue; /* don't expand inside quotes */ + } + if( scan[i] != '$' ) { + continue; + } + + /* scan out the complete macro */ + start = scan + i + 1; + + if( !start[0] ) { + break; /* end of string */ + } + + /* allow escape syntax */ + if( i && scan[i-1] == '\\' ) { + memmove( scan + i - 1, scan + i, len - i + 1 ); + i--; + continue; + } + + /* fix from jitspoe - skip leading spaces */ + while( *start && *start <= 32 ) { + start++; + } + + token = temporary; + + if( *start == '{' ) { + /* allow ${variable} syntax */ + start++; + if( *start == '$' ) { + start++; + } + while( *start ) { + if( *start == '}' ) { + start++; + break; + } + *token++ = *start++; + } + } else { + /* parse single word */ + while( *start > 32 ) { + *token++ = *start++; + } + } + + *token = 0; + + if( token == temporary ) { + continue; + } + + rescan = qfalse; + + if( aliasHack ) { + /* expand positional parameters only */ + if( temporary[1] ) { + continue; + } + if( Q_isdigit( temporary[0] ) ) { + token = Cmd_Argv( temporary[0] - '0' ); + } else if( temporary[0] == '@' ) { + token = Cmd_Args(); + } else { + continue; + } + } else { + /* check for macros first */ + macro = Cmd_FindMacro( temporary ); + if( macro ) { + macro->function( buffer, sizeof( buffer ) ); + token = buffer; + } else { + var = Cvar_FindVar( temporary ); + if( var && !( var->flags & CVAR_PRIVATE ) ) { + token = var->string; + rescan = qtrue; + } else if( !strcmp( temporary, "qt" ) ) { + token = "\""; + } else if( !strcmp( temporary, "sc" ) ) { + token = ";"; + } else { + token = ""; + } + } + } + + j = strlen( token ); + len += j; + if( len >= MAX_STRING_CHARS ) { + Com_Printf( "Expanded line exceeded %i chars, discarded.\n", + MAX_STRING_CHARS ); + return NULL; + } + + strncpy( temporary, scan, i ); + strcpy( temporary + i, token ); + strcpy( temporary + i + j, start ); + + strcpy( expanded, temporary ); + scan = expanded; + if( !rescan ) { + i += j; + } + i--; + + if( ++count == 100 ) { + Com_Printf( "Macro expansion loop, discarded.\n" ); + return NULL; + } + } + + if( inquote ) { + Com_Printf( "Line has unmatched quote, discarded.\n" ); + return NULL; + } + + return scan; +} + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +$Cvars will be expanded unless they are in a quoted token +============ +*/ +void Cmd_TokenizeString( const char *text, qboolean macroExpand ) { + int i; + char *data, *start, *dest; + +// clear the args from the last string + for( i = 0; i < cmd_argc; i++ ) { + cmd_argv[i] = NULL; + cmd_offsets[i] = 0; + } + + cmd_argc = 0; + cmd_args[0] = 0; + + if( !text[0] ) { + return; + } + +// macro expand the text + if( macroExpand ) { + text = Cmd_MacroExpandString( text, qfalse ); + if( !text ) { + return; + } + } + + Q_strncpyz( cmd_args, text, sizeof( cmd_args ) ); + + dest = cmd_data; + start = data = cmd_args; + while( cmd_argc < MAX_STRING_TOKENS ) { +// skip whitespace up to a /n + while( *data <= 32 ) { + if( *data == 0 ) { + return; // end of text + } + if( *data == '\n' ) { + return; // a newline seperates commands in the buffer + } + data++; + } + +// add new argument + cmd_offsets[cmd_argc] = data - start; + cmd_argv[cmd_argc] = dest; + cmd_argc++; + + if( *data == ';' ) { + data++; + *dest++ = ';'; + *dest++ = 0; + continue; + } + +// parse quoted string + if( *data == '\"' ) { + data++; + while( *data != '\"' ) { + if( *data == 0 ) { + return; // end of data + } + *dest++ = *data++; + } + data++; + *dest++ = 0; + continue; + } + +// parse reqular token + while( *data > 32 ) { + if( *data == '\"' ) { + break; + } + if( *data == ';' ) { + break; + } + *dest++ = *data++; + } + *dest++ = 0; + + if( *data == 0 ) { + return; // end of text + } + } +} + +/* +============ +Cmd_Find +============ +*/ +cmd_function_t *Cmd_Find( const char *name ) { + cmd_function_t *cmd; + uint32 hash; + + hash = Com_HashString( name, CMD_HASH_SIZE ); + LIST_FOR_EACH( cmd_function_t, cmd, &cmd_hash[hash], hashEntry ) { + if( !strcmp( cmd->name, name ) ) { + return cmd; + } + } + + return NULL; +} + +static void Cmd_RegCommand( const cmdreg_t *reg ) { + cmd_function_t *cmd; + cvar_t *var; + uint32 hash; + +// fail if the command is a variable name + var = Cvar_FindVar( reg->name ); + if( var && !( var->flags & CVAR_USER_CREATED ) ) { + Com_WPrintf( "Cmd_AddCommand: %s already defined as a cvar\n", + reg->name ); + return; + } + +// fail if the command already exists + cmd = Cmd_Find( reg->name ); + if( cmd ) { + Com_WPrintf( "Cmd_AddCommand: %s already defined\n", reg->name ); + return; + } + + cmd = Cmd_Malloc( sizeof( *cmd ) ); + cmd->name = reg->name; + cmd->function = reg->function; + cmd->generator1 = reg->generator1; + cmd->generator2 = reg->generator2; + cmd->generator3 = reg->generator3; + + List_Append( &cmd_functions, &cmd->listEntry ); + + hash = Com_HashString( reg->name, CMD_HASH_SIZE ); + List_Append( &cmd_hash[hash], &cmd->hashEntry ); +} + +/* +============ +Cmd_AddCommand +============ +*/ +void Cmd_AddCommand( const char *name, xcommand_t function ) { + cmdreg_t reg; + + reg.name = name; + reg.function = function; + reg.generator1 = NULL; + reg.generator2 = NULL; + reg.generator3 = NULL; + Cmd_RegCommand( ® ); +} + +void Cmd_Register( const cmdreg_t *reg ) { + while( reg->name ) { + Cmd_RegCommand( reg ); + reg++; + } +} + +void Cmd_Deregister( const cmdreg_t *reg ) { + while( reg->name ) { + Cmd_RemoveCommand( reg->name ); + reg++; + } +} + +/* +============ +Cmd_RemoveCommand +============ +*/ +void Cmd_RemoveCommand( const char *name ) { + cmd_function_t *cmd; + + cmd = Cmd_Find( name ); + if( !cmd ) { + Com_DPrintf( "Cmd_RemoveCommand: %s not added\n", name ); + return; + } + + List_Delete( &cmd->listEntry ); + List_Delete( &cmd->hashEntry ); + Z_Free( cmd ); + +} + +/* +============ +Cmd_Exists +============ +*/ +qboolean Cmd_Exists( const char *name ) { + cmd_function_t *cmd; + + cmd = Cmd_Find( name ); + if( !cmd ) { + return qfalse; + } + + return qtrue; +} + +xcommand_t Cmd_FindFunction( const char *name ) { + cmd_function_t *cmd; + + cmd = Cmd_Find( name ); + if( !cmd ) { + return NULL; + } + + return cmd->function; +} + +xgenerator_t Cmd_FindGenerator( const char *name, int index ) { + cmd_function_t *cmd = Cmd_Find( name ); + + if( !cmd ) { + return NULL; + } + + switch( index ) { + case 1: return cmd->generator1; + case 2: return cmd->generator2; + case 3: return cmd->generator3; + } + + return NULL; +} + +const char *Cmd_Command_g( const char *partial, int state ) { + static int length; + static cmd_function_t *cmd; + const char *name; + + if( !state ) { + length = strlen( partial ); + cmd = LIST_FIRST( cmd_function_t, &cmd_functions, listEntry ); + } + + while( !LIST_TERM( cmd, &cmd_functions, listEntry ) ) { + name = cmd->name; + cmd = LIST_NEXT( cmd_function_t, cmd, listEntry ); + if( !strncmp( partial, name, length ) ) { + return name; + } + } + + return NULL; +} + +const char *Cmd_Alias_g( const char *partial, int state ) { + static int length; + static cmdalias_t *alias; + const char *name; + + if( !state ) { + length = strlen( partial ); + alias = LIST_FIRST( cmdalias_t, &cmd_alias, listEntry ); + } + + while( !LIST_TERM( alias, &cmd_alias, listEntry ) ) { + name = alias->name; + alias = LIST_NEXT( cmdalias_t, alias, listEntry ); + if( !strncmp( partial, name, length ) ) { + return name; + } + } + + return NULL; +} + +const char *Cmd_Mixed_g( const char *partial, int state ) { + static xgenerator_t g; + const char *match; + + if( state == 2 ) { + g( partial, 2 ); + return NULL; + } + + if( !state ) { + g = Cmd_Command_g; + } + + match = g( partial, state ); + if( match ) { + return match; + } + + if( g == Cmd_Command_g ) { + g( partial, 2 ); + g = Cmd_Alias_g; + + match = g( partial, 0 ); + if( match ) { + return match; + } + } + + return NULL; +} + + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +============ +*/ +void Cmd_ExecuteString( const char *text ) { + cmd_function_t *cmd; + cmdalias_t *a; + cvar_t *v; + + Cmd_TokenizeString( text, qtrue ); + + // execute the command line + if( !cmd_argc ) { + return; // no tokens + } + + // check functions + cmd = Cmd_Find( cmd_argv[0] ); + if( cmd ) { + if( cmd->function ) { + cmd->function(); + } else { + Cmd_ForwardToServer(); + } + return; + } + + // check aliases + a = Cmd_AliasFind( cmd_argv[0] ); + if( a ) { + if( cmd_buffer.aliasCount == ALIAS_LOOP_COUNT ) { + Com_WPrintf( "Runaway alias loop\n" ); + return; + } + text = Cmd_MacroExpandString( a->value, qtrue ); + if( text ) { + cmd_buffer.aliasCount++; + Cbuf_InsertText( text ); + } + return; + } + + // check variables + v = Cvar_FindVar( cmd_argv[0] ); + if( v ) { + Cvar_Command( v ); + return; + } + + // send it as a server command if we are connected + Cmd_ForwardToServer(); +} + +/* +=============== +Cmd_Exec_f +=============== +*/ +static void Cmd_Exec_f( void ) { + char buffer[MAX_QPATH]; + char *f, *ext; + + if( Cmd_Argc() != 2 ) { + Com_Printf( "%s <filename> : execute a script file\n", Cmd_Argv( 0 ) ); + return; + } + + Cmd_ArgvBuffer( 1, buffer, sizeof( buffer ) ); + + FS_LoadFile( buffer, ( void ** )&f ); + if( !f ) { + ext = COM_FileExtension( buffer ); + if( !ext[0] ) { + // try with *.cfg extension + COM_DefaultExtension( buffer, ".cfg", sizeof( buffer ) ); + FS_LoadFile( buffer, ( void ** )&f ); + } + + if( !f ) { + Com_Printf( "Couldn't exec %s\n", buffer ); + return; + } + } + + Com_Printf( "Execing %s\n", buffer ); + + // FIXME: bad thing to do in place + COM_Compress( f ); + + Cbuf_InsertText( f ); + + FS_FreeFile( f ); +} + +static const char *Cmd_Exec_g( const char *partial, int state ) { + return Com_FileNameGeneratorByFilter( "", "*.cfg", partial, qtrue, state ); +} + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +static void Cmd_Echo_f( void ) { + Com_Printf( "%s\n", Cmd_RawArgs() ); +} + +static void Cmd_ColoredEcho_f( void ) { + char buffer[MAX_STRING_CHARS]; + char *src, *dst; + + src = Cmd_RawArgs(); + dst = buffer; + while( *src ) { + if( src[0] == '^' && src[1] ) { + if( src[1] == '^' ) { + *dst++ = '^'; + } else { + dst[0] = Q_COLOR_ESCAPE; + dst[1] = src[1]; + dst += 2; + } + src += 2; + } else { + *dst++ = *src++; + } + } + *dst = 0; + Com_Printf( "%s\n", buffer ); +} + +/* +============ +Cmd_List_f +============ +*/ +static void Cmd_List_f( void ) { + cmd_function_t *cmd; + int i, total; + char *filter = NULL; + + if( cmd_argc > 1 ) { + filter = cmd_argv[1]; + } + + i = total = 0; + LIST_FOR_EACH( cmd_function_t, cmd, &cmd_functions, listEntry ) { + total++; + if( filter && !Com_WildCmp( filter, cmd->name, qfalse ) ) { + continue; + } + Com_Printf( "%s\n", cmd->name ); + i++; + } + Com_Printf( "%i of %i commands\n", i, total ); +} + +/* +============ +Cmd_MacroList_f +============ +*/ +static void Cmd_MacroList_f( void ) { + cmd_macro_t *macro; + int i, total; + char *filter = NULL; + char buffer[MAX_QPATH]; + + if( cmd_argc > 1 ) { + filter = cmd_argv[1]; + } + + i = 0; + for( macro = cmd_macros, total = 0; macro; macro = macro->next, total++ ) { + if( filter && !Com_WildCmp( filter, macro->name, qfalse ) ) { + continue; + } + macro->function( buffer, sizeof( buffer ) ); + Com_Printf( "%-16s %s\n", macro->name, buffer ); + i++; + } + Com_Printf( "%i of %i macros\n", i, total ); +} + +/* +============ +Cmd_FillAPI +============ +*/ +void Cmd_FillAPI( cmdAPI_t *api ) { + api->AddCommand = Cmd_AddCommand; + api->Register = Cmd_Register; + api->Deregister = Cmd_Deregister; + api->RemoveCommand = Cmd_RemoveCommand; + api->Argc = Cmd_Argc; + api->Argv = Cmd_Argv; + api->ArgsFrom = Cmd_ArgsFrom; + api->ExecuteText = Cbuf_ExecuteText; + api->FindFunction = Cmd_FindFunction; + api->FindMacroFunction = Cmd_FindMacroFunction; + api->FindGenerator = Cmd_FindGenerator; +} + +static const cmdreg_t c_cmd[] = { + { "cmdlist", Cmd_List_f }, + { "macrolist", Cmd_MacroList_f }, + { "exec", Cmd_Exec_f, Cmd_Exec_g }, + { "echo", Cmd_Echo_f }, + { "_echo", Cmd_ColoredEcho_f }, + { "alias", Cmd_Alias_f, Cmd_Alias_g, Cmd_Mixed_g }, + { "unalias", Cmd_UnAlias_f, Cmd_Alias_g }, + { "wait", Cmd_Wait_f }, + { "trigger", Cmd_Trigger_f }, + { "untrigger", Cmd_UnTrigger_f }, + + { NULL } +}; + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init( void ) { + int i; + + List_Init( &cmd_functions ); + for( i = 0; i < CMD_HASH_SIZE; i++ ) { + List_Init( &cmd_hash[i] ); + } + + List_Init( &cmd_alias ); + for( i = 0; i < ALIAS_HASH_SIZE; i++ ) { + List_Init( &cmd_aliasHash[i] ); + } + + List_Init( &cmd_triggers ); + + Cmd_Register( c_cmd ); + Cmd_FillAPI( &cmd ); +} + |