X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=blobdiff_plain;f=cmd.c;h=cab160b4220a10811d322d70d960b1d3474c95cf;hp=00aa78ba00bda6298494e3cfc82bf98c3da610de;hb=2075ae43356d724bae305ce8fd36ea570718b14a;hpb=5ed1c3533b68ecc28947896ce646f2dda1e82c66 diff --git a/cmd.c b/cmd.c index 00aa78ba..cab160b4 100644 --- a/cmd.c +++ b/cmd.c @@ -20,21 +20,15 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // cmd.c -- Quake script command processing module #include "quakedef.h" - -#define MAX_ALIAS_NAME 32 -// this is the largest script file that can be executed in one step -// LordHavoc: inreased this from 8192 to 32768 -#define CMDBUFSIZE 32768 -// maximum number of parameters to a command -#define MAX_ARGS 80 -// maximum tokenizable commandline length (counting NUL terminations) -#define CMD_TOKENIZELENGTH (MAX_INPUTLINE + 80) +#include "thread.h" typedef struct cmdalias_s { struct cmdalias_s *next; char name[MAX_ALIAS_NAME]; char *value; + qboolean initstate; // indicates this command existed at init + char *initialvalue; // backup copy of value at init } cmdalias_t; static cmdalias_t *cmd_alias; @@ -62,6 +56,115 @@ static void Cmd_Wait_f (void) cmd_wait = true; } +typedef struct cmddeferred_s +{ + struct cmddeferred_s *next; + char *value; + double delay; +} cmddeferred_t; + +static cmddeferred_t *cmd_deferred_list = NULL; + +/* +============ +Cmd_Defer_f + +Cause a command to be executed after a delay. +============ +*/ +static void Cmd_Defer_f (void) +{ + if(Cmd_Argc() == 1) + { + cmddeferred_t *next = cmd_deferred_list; + if(!next) + Con_Printf("No commands are pending.\n"); + while(next) + { + Con_Printf("-> In %9.2f: %s\n", next->delay, next->value); + next = next->next; + } + } else if(Cmd_Argc() == 2 && !strcasecmp("clear", Cmd_Argv(1))) + { + while(cmd_deferred_list) + { + cmddeferred_t *cmd = cmd_deferred_list; + cmd_deferred_list = cmd->next; + Mem_Free(cmd->value); + Mem_Free(cmd); + } + } else if(Cmd_Argc() == 3) + { + const char *value = Cmd_Argv(2); + cmddeferred_t *defcmd = (cmddeferred_t*)Mem_Alloc(tempmempool, sizeof(*defcmd)); + size_t len = strlen(value); + + defcmd->delay = atof(Cmd_Argv(1)); + defcmd->value = (char*)Mem_Alloc(tempmempool, len+1); + memcpy(defcmd->value, value, len+1); + defcmd->next = NULL; + + if(cmd_deferred_list) + { + cmddeferred_t *next = cmd_deferred_list; + while(next->next) + next = next->next; + next->next = defcmd; + } else + cmd_deferred_list = defcmd; + /* Stupid me... this changes the order... so commands with the same delay go blub :S + defcmd->next = cmd_deferred_list; + cmd_deferred_list = defcmd;*/ + } else { + Con_Printf("usage: defer \n" + " defer clear\n"); + return; + } +} + +/* +============ +Cmd_Centerprint_f + +Print something to the center of the screen using SCR_Centerprint +============ +*/ +static void Cmd_Centerprint_f (void) +{ + char msg[MAX_INPUTLINE]; + unsigned int i, c, p; + c = Cmd_Argc(); + if(c >= 2) + { + strlcpy(msg, Cmd_Argv(1), sizeof(msg)); + for(i = 2; i < c; ++i) + { + strlcat(msg, " ", sizeof(msg)); + strlcat(msg, Cmd_Argv(i), sizeof(msg)); + } + c = (unsigned int)strlen(msg); + for(p = 0, i = 0; i < c; ++i) + { + if(msg[i] == '\\') + { + if(msg[i+1] == 'n') + msg[p++] = '\n'; + else if(msg[i+1] == '\\') + msg[p++] = '\\'; + else { + msg[p++] = '\\'; + msg[p++] = msg[i+1]; + } + ++i; + } else { + msg[p++] = msg[i]; + } + } + msg[p] = '\0'; + SCR_CenterPrint(msg); + } +} + /* ============================================================================= @@ -72,6 +175,7 @@ static void Cmd_Wait_f (void) static sizebuf_t cmd_text; static unsigned char cmd_text_buf[CMDBUFSIZE]; +void *cmd_text_mutex = NULL; /* ============ @@ -84,15 +188,14 @@ void Cbuf_AddText (const char *text) { int l; - l = (int)strlen (text); + l = (int)strlen(text); + Cbuf_LockThreadMutex(); if (cmd_text.cursize + l >= cmd_text.maxsize) - { Con_Print("Cbuf_AddText: overflow\n"); - return; - } - - SZ_Write (&cmd_text, (const unsigned char *)text, (int)strlen (text)); + else + SZ_Write(&cmd_text, (const unsigned char *)text, l); + Cbuf_UnlockThreadMutex(); } @@ -107,28 +210,60 @@ FIXME: actually change the command buffer to do less copying */ void Cbuf_InsertText (const char *text) { - char *temp; - int templen; - - // copy off any commands still remaining in the exec buffer - templen = cmd_text.cursize; - if (templen) + size_t l = strlen(text); + Cbuf_LockThreadMutex(); + // we need to memmove the existing text and stuff this in before it... + if (cmd_text.cursize + l >= (size_t)cmd_text.maxsize) + Con_Print("Cbuf_InsertText: overflow\n"); + else { - temp = (char *)Mem_Alloc (tempmempool, templen); - memcpy (temp, cmd_text.data, templen); - SZ_Clear (&cmd_text); + // we don't have a SZ_Prepend, so... + memmove(cmd_text.data + l, cmd_text.data, cmd_text.cursize); + cmd_text.cursize += (int)l; + memcpy(cmd_text.data, text, l); } - else - temp = NULL; - - // add the entire text of the file - Cbuf_AddText (text); + Cbuf_UnlockThreadMutex(); +} - // add the copied off data - if (temp != NULL) +/* +============ +Cbuf_Execute_Deferred --blub +============ +*/ +static void Cbuf_Execute_Deferred (void) +{ + static double oldrealtime = 0; + cmddeferred_t *cmd, *prev; + double eat; + if (realtime - oldrealtime < 0 || realtime - oldrealtime > 1800) oldrealtime = realtime; + eat = realtime - oldrealtime; + if (eat < (1.0 / 120.0)) + return; + oldrealtime = realtime; + prev = NULL; + cmd = cmd_deferred_list; + while(cmd) { - SZ_Write (&cmd_text, (const unsigned char *)temp, templen); - Mem_Free (temp); + cmd->delay -= eat; + if(cmd->delay <= 0) + { + Cbuf_AddText(cmd->value); + Cbuf_AddText(";\n"); + Mem_Free(cmd->value); + + if(prev) { + prev->next = cmd->next; + Mem_Free(cmd); + cmd = prev->next; + } else { + cmd_deferred_list = cmd->next; + Mem_Free(cmd); + cmd = cmd_deferred_list; + } + continue; + } + prev = cmd; + cmd = cmd->next; } } @@ -137,14 +272,16 @@ void Cbuf_InsertText (const char *text) Cbuf_Execute ============ */ -static void Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ); +static qboolean Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ); void Cbuf_Execute (void) { int i; char *text; char line[MAX_INPUTLINE]; char preprocessed[MAX_INPUTLINE]; - int quotes; + char *firstchar; + qboolean quotes; + char *comment; // LordHavoc: making sure the tokenizebuffer doesn't get filled up by repeated crashes cmd_tokenizebufferpos = 0; @@ -154,21 +291,46 @@ void Cbuf_Execute (void) // find a \n or ; line break text = (char *)cmd_text.data; - quotes = 0; - for (i=0 ; i< cmd_text.cursize ; i++) + quotes = false; + comment = NULL; + for (i=0 ; i < cmd_text.cursize ; i++) { - if (text[i] == '"') - quotes ^= 1; - if (text[i] == '\\' && text[i+1] == '"') - i++; - if ( !quotes && text[i] == ';') - break; // don't break if inside a quoted string + 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 < (cmd_text.cursize-1) && (text[i] == '\\' && (text[i+1] == '"' || text[i+1] == '\\'))) + i++; + } + else + { + if(text[i] == '/' && text[i + 1] == '/' && (i == 0 || ISWHITESPACE(text[i-1]))) + comment = &text[i]; + if(text[i] == ';') + break; // don't break if inside a quoted string or comment + } + } + if (text[i] == '\r' || text[i] == '\n') break; } - memcpy (line, text, i); - line[i] = 0; + // better than CRASHING on overlong input lines that may SOMEHOW enter the buffer + if(i >= MAX_INPUTLINE) + { + Con_Printf("Warning: console input buffer had an overlong line. Ignored.\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, alias) can insert data at the @@ -180,12 +342,28 @@ void Cbuf_Execute (void) { i++; cmd_text.cursize -= i; - memcpy (cmd_text.data, text+i, cmd_text.cursize); + memmove (cmd_text.data, text+i, cmd_text.cursize); } // execute the command line - Cmd_PreprocessString( line, preprocessed, sizeof(preprocessed), NULL ); - Cmd_ExecuteString (preprocessed, src_command); + firstchar = line; + while(*firstchar && ISWHITESPACE(*firstchar)) + ++firstchar; + if( + (strncmp(firstchar, "alias", 5) || !ISWHITESPACE(firstchar[5])) + && + (strncmp(firstchar, "bind", 4) || !ISWHITESPACE(firstchar[4])) + && + (strncmp(firstchar, "in_bind", 7) || !ISWHITESPACE(firstchar[7])) + ) + { + if(Cmd_PreprocessString( line, preprocessed, sizeof(preprocessed), NULL )) + Cmd_ExecuteString (preprocessed, src_command, false); + } + else + { + Cmd_ExecuteString (line, src_command, false); + } if (cmd_wait) { // skip out while text still remains in buffer, leaving it @@ -196,6 +374,17 @@ void Cbuf_Execute (void) } } +void Cbuf_Frame(void) +{ + Cbuf_Execute_Deferred(); + if (cmd_text.cursize) + { + SV_LockThreadMutex(); + Cbuf_Execute(); + SV_UnlockThreadMutex(); + } +} + /* ============================================================================== @@ -215,7 +404,7 @@ quake -nosound +cmd amlev1 =============== */ qboolean host_stuffcmdsrun = false; -void Cmd_StuffCmds_f (void) +static void Cmd_StuffCmds_f (void) { int i, j, l; // this is for all commandline options combined (and is bounds checked) @@ -268,34 +457,33 @@ void Cmd_StuffCmds_f (void) Cbuf_InsertText (build); } - -/* -=============== -Cmd_Exec_f -=============== -*/ -static void Cmd_Exec_f (void) +static void Cmd_Exec(const char *filename) { char *f; + size_t filenameLen = strlen(filename); + qboolean isdefaultcfg = + !strcmp(filename, "default.cfg") || + (filenameLen >= 12 && !strcmp(filename + filenameLen - 12, "/default.cfg")); - if (Cmd_Argc () != 2) + if (!strcmp(filename, "config.cfg")) { - Con_Print("exec : execute a script file\n"); - return; + filename = CONFIGFILENAME; + if (COM_CheckParm("-noconfig")) + return; // don't execute config.cfg } - f = (char *)FS_LoadFile (Cmd_Argv(1), tempmempool, false, NULL); + f = (char *)FS_LoadFile (filename, tempmempool, false, NULL); if (!f) { - Con_Printf("couldn't exec %s\n",Cmd_Argv(1)); + Con_Printf("couldn't exec %s\n",filename); return; } - Con_DPrintf("execing %s\n",Cmd_Argv(1)); + Con_Printf("execing %s\n",filename); // if executing default.cfg for the first time, lock the cvar defaults // it may seem backwards to insert this text BEFORE the default.cfg // but Cbuf_InsertText inserts before, so this actually ends up after it. - if (!strcmp(Cmd_Argv(1), "default.cfg")) + if (isdefaultcfg) Cbuf_InsertText("\ncvar_lockdefaults\n"); // insert newline after the text to make sure the last line is terminated (some text editors omit the trailing newline) @@ -303,6 +491,240 @@ static void Cmd_Exec_f (void) Cbuf_InsertText ("\n"); Cbuf_InsertText (f); Mem_Free(f); + + if (isdefaultcfg) + { + // special defaults for specific games go here, these execute before default.cfg + // Nehahra pushable crates malfunction in some levels if this is on + // Nehahra NPC AI is confused by blowupfallenzombies + switch(gamemode) + { + case GAME_NORMAL: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 0\n" +"sv_gameplayfix_findradiusdistancetobox 0\n" +"sv_gameplayfix_grenadebouncedownslopes 0\n" +"sv_gameplayfix_slidemoveprojectiles 0\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" +"sv_gameplayfix_setmodelrealbox 0\n" +"sv_gameplayfix_droptofloorstartsolid 0\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" +"sv_gameplayfix_noairborncorpse 0\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" +"sv_gameplayfix_easierwaterjump 0\n" +"sv_gameplayfix_delayprojectiles 0\n" +"sv_gameplayfix_multiplethinksperframe 0\n" +"sv_gameplayfix_fixedcheckwatertransition 0\n" +"sv_gameplayfix_q1bsptracelinereportstexture 0\n" +"sv_gameplayfix_swiminbmodels 0\n" +"sv_gameplayfix_downtracesupportsongroundflag 0\n" +"sys_ticrate 0.01388889\n" +"r_shadow_gloss 1\n" +"r_shadow_bumpscale_basetexture 0\n" + ); + break; + case GAME_NEHAHRA: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 0\n" +"sv_gameplayfix_findradiusdistancetobox 0\n" +"sv_gameplayfix_grenadebouncedownslopes 0\n" +"sv_gameplayfix_slidemoveprojectiles 0\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" +"sv_gameplayfix_setmodelrealbox 0\n" +"sv_gameplayfix_droptofloorstartsolid 0\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" +"sv_gameplayfix_noairborncorpse 0\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" +"sv_gameplayfix_easierwaterjump 0\n" +"sv_gameplayfix_delayprojectiles 0\n" +"sv_gameplayfix_multiplethinksperframe 0\n" +"sv_gameplayfix_fixedcheckwatertransition 0\n" +"sv_gameplayfix_q1bsptracelinereportstexture 0\n" +"sv_gameplayfix_swiminbmodels 0\n" +"sv_gameplayfix_downtracesupportsongroundflag 0\n" +"sys_ticrate 0.01388889\n" +"r_shadow_gloss 1\n" +"r_shadow_bumpscale_basetexture 0\n" + ); + break; + // hipnotic mission pack has issues in their 'friendly monster' ai, which seem to attempt to attack themselves for some reason when findradius() returns non-solid entities. + // hipnotic mission pack has issues with bobbing water entities 'jittering' between different heights on alternate frames at the default 0.0138889 ticrate, 0.02 avoids this issue + // hipnotic mission pack has issues in their proximity mine sticking code, which causes them to bounce off. + case GAME_HIPNOTIC: + case GAME_QUOTH: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 0\n" +"sv_gameplayfix_findradiusdistancetobox 0\n" +"sv_gameplayfix_grenadebouncedownslopes 0\n" +"sv_gameplayfix_slidemoveprojectiles 0\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" +"sv_gameplayfix_setmodelrealbox 0\n" +"sv_gameplayfix_droptofloorstartsolid 0\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" +"sv_gameplayfix_noairborncorpse 0\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" +"sv_gameplayfix_easierwaterjump 0\n" +"sv_gameplayfix_delayprojectiles 0\n" +"sv_gameplayfix_multiplethinksperframe 0\n" +"sv_gameplayfix_fixedcheckwatertransition 0\n" +"sv_gameplayfix_q1bsptracelinereportstexture 0\n" +"sv_gameplayfix_swiminbmodels 0\n" +"sv_gameplayfix_downtracesupportsongroundflag 0\n" +"sys_ticrate 0.02\n" +"r_shadow_gloss 1\n" +"r_shadow_bumpscale_basetexture 0\n" + ); + break; + // rogue mission pack has a guardian boss that does not wake up if findradius returns one of the entities around its spawn area + case GAME_ROGUE: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 0\n" +"sv_gameplayfix_findradiusdistancetobox 0\n" +"sv_gameplayfix_grenadebouncedownslopes 0\n" +"sv_gameplayfix_slidemoveprojectiles 0\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" +"sv_gameplayfix_setmodelrealbox 0\n" +"sv_gameplayfix_droptofloorstartsolid 0\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" +"sv_gameplayfix_noairborncorpse 0\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" +"sv_gameplayfix_easierwaterjump 0\n" +"sv_gameplayfix_delayprojectiles 0\n" +"sv_gameplayfix_multiplethinksperframe 0\n" +"sv_gameplayfix_fixedcheckwatertransition 0\n" +"sv_gameplayfix_q1bsptracelinereportstexture 0\n" +"sv_gameplayfix_swiminbmodels 0\n" +"sv_gameplayfix_downtracesupportsongroundflag 0\n" +"sys_ticrate 0.01388889\n" +"r_shadow_gloss 1\n" +"r_shadow_bumpscale_basetexture 0\n" + ); + break; + case GAME_TENEBRAE: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 0\n" +"sv_gameplayfix_findradiusdistancetobox 0\n" +"sv_gameplayfix_grenadebouncedownslopes 0\n" +"sv_gameplayfix_slidemoveprojectiles 0\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" +"sv_gameplayfix_setmodelrealbox 0\n" +"sv_gameplayfix_droptofloorstartsolid 0\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" +"sv_gameplayfix_noairborncorpse 0\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" +"sv_gameplayfix_easierwaterjump 0\n" +"sv_gameplayfix_delayprojectiles 0\n" +"sv_gameplayfix_multiplethinksperframe 0\n" +"sv_gameplayfix_fixedcheckwatertransition 0\n" +"sv_gameplayfix_q1bsptracelinereportstexture 0\n" +"sv_gameplayfix_swiminbmodels 0\n" +"sv_gameplayfix_downtracesupportsongroundflag 0\n" +"sys_ticrate 0.01388889\n" +"r_shadow_gloss 2\n" +"r_shadow_bumpscale_basetexture 4\n" + ); + break; + case GAME_NEXUIZ: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 1\n" +"sv_gameplayfix_findradiusdistancetobox 1\n" +"sv_gameplayfix_grenadebouncedownslopes 1\n" +"sv_gameplayfix_slidemoveprojectiles 1\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 1\n" +"sv_gameplayfix_setmodelrealbox 1\n" +"sv_gameplayfix_droptofloorstartsolid 1\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 1\n" +"sv_gameplayfix_noairborncorpse 1\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 1\n" +"sv_gameplayfix_easierwaterjump 1\n" +"sv_gameplayfix_delayprojectiles 1\n" +"sv_gameplayfix_multiplethinksperframe 1\n" +"sv_gameplayfix_fixedcheckwatertransition 1\n" +"sv_gameplayfix_q1bsptracelinereportstexture 1\n" +"sv_gameplayfix_swiminbmodels 1\n" +"sv_gameplayfix_downtracesupportsongroundflag 1\n" +"sys_ticrate 0.01388889\n" +"sv_gameplayfix_q2airaccelerate 1\n" +"sv_gameplayfix_stepmultipletimes 1\n" + ); + break; + // Steel Storm: Burning Retribution csqc misinterprets CSQC_InputEvent if type is a value other than 0 or 1 + case GAME_STEELSTORM: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 1\n" +"sv_gameplayfix_findradiusdistancetobox 1\n" +"sv_gameplayfix_grenadebouncedownslopes 1\n" +"sv_gameplayfix_slidemoveprojectiles 1\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 1\n" +"sv_gameplayfix_setmodelrealbox 1\n" +"sv_gameplayfix_droptofloorstartsolid 1\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 1\n" +"sv_gameplayfix_noairborncorpse 1\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 1\n" +"sv_gameplayfix_easierwaterjump 1\n" +"sv_gameplayfix_delayprojectiles 1\n" +"sv_gameplayfix_multiplethinksperframe 1\n" +"sv_gameplayfix_fixedcheckwatertransition 1\n" +"sv_gameplayfix_q1bsptracelinereportstexture 1\n" +"sv_gameplayfix_swiminbmodels 1\n" +"sv_gameplayfix_downtracesupportsongroundflag 1\n" +"sys_ticrate 0.01388889\n" +"cl_csqc_generatemousemoveevents 0\n" + ); + break; + default: + Cbuf_InsertText("\n" +"sv_gameplayfix_blowupfallenzombies 1\n" +"sv_gameplayfix_findradiusdistancetobox 1\n" +"sv_gameplayfix_grenadebouncedownslopes 1\n" +"sv_gameplayfix_slidemoveprojectiles 1\n" +"sv_gameplayfix_upwardvelocityclearsongroundflag 1\n" +"sv_gameplayfix_setmodelrealbox 1\n" +"sv_gameplayfix_droptofloorstartsolid 1\n" +"sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 1\n" +"sv_gameplayfix_noairborncorpse 1\n" +"sv_gameplayfix_noairborncorpse_allowsuspendeditems 1\n" +"sv_gameplayfix_easierwaterjump 1\n" +"sv_gameplayfix_delayprojectiles 1\n" +"sv_gameplayfix_multiplethinksperframe 1\n" +"sv_gameplayfix_fixedcheckwatertransition 1\n" +"sv_gameplayfix_q1bsptracelinereportstexture 1\n" +"sv_gameplayfix_swiminbmodels 1\n" +"sv_gameplayfix_downtracesupportsongroundflag 1\n" +"sys_ticrate 0.01388889\n" + ); + break; + } + } +} + +/* +=============== +Cmd_Exec_f +=============== +*/ +static void Cmd_Exec_f (void) +{ + fssearch_t *s; + int i; + + if (Cmd_Argc () != 2) + { + Con_Print("exec : execute a script file\n"); + return; + } + + s = FS_Search(Cmd_Argv(1), true, true); + if(!s || !s->numfilenames) + { + Con_Printf("couldn't exec %s\n",Cmd_Argv(1)); + return; + } + + for(i = 0; i < s->numfilenames; ++i) + Cmd_Exec(s->filenames[i]); + + FS_FreeSearch(s); } @@ -397,7 +819,7 @@ static void Cmd_Toggle_f(void) } else { // Invalid CVar - Con_Printf("ERROR : CVar '%s' not found\n", Cmd_Argv(2) ); + Con_Printf("ERROR : CVar '%s' not found\n", Cmd_Argv(1) ); } } } @@ -421,7 +843,7 @@ static void Cmd_Alias_f (void) { Con_Print("Current alias commands:\n"); for (a = cmd_alias ; a ; a=a->next) - Con_Printf("%s : %s\n", a->name, a->value); + Con_Printf("%s : %s", a->name, a->value); return; } @@ -463,19 +885,64 @@ static void Cmd_Alias_f (void) // 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++) + for (i=2 ; i < c ; i++) { - strlcat (cmd, Cmd_Argv(i), sizeof (cmd)); - if (i != c) + if (i != 2) strlcat (cmd, " ", sizeof (cmd)); + strlcat (cmd, Cmd_Argv(i), sizeof (cmd)); } strlcat (cmd, "\n", sizeof (cmd)); alloclen = strlen (cmd) + 1; + if(alloclen >= 2) + cmd[alloclen - 2] = '\n'; // to make sure a newline is appended even if too long a->value = (char *)Z_Malloc (alloclen); memcpy (a->value, cmd, alloclen); } +/* +=============== +Cmd_UnAlias_f + +Remove existing aliases. +=============== +*/ +static void Cmd_UnAlias_f (void) +{ + cmdalias_t *a, *p; + int i; + const char *s; + + if(Cmd_Argc() == 1) + { + Con_Print("unalias: 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(!strcmp(s, a->name)) + { + if (a->initstate) // we can not remove init aliases + continue; + if(a == cmd_alias) + cmd_alias = a->next; + if(p) + p->next = a->next; + Z_Free(a->value); + Z_Free(a); + break; + } + } + if(!a) + Con_Printf("unalias: %s alias not found\n", s); + } +} + /* ============================================================================= @@ -492,6 +959,7 @@ typedef struct cmd_function_s xcommand_t consolefunction; xcommand_t clientfunction; qboolean csqcfunc; + qboolean initstate; // indicates this command existed at init } cmd_function_t; static int cmd_argc; @@ -503,96 +971,368 @@ cmd_source_t cmd_source; static cmd_function_t *cmd_functions; // possible commands to execute +static const char *Cmd_GetDirectCvarValue(const char *varname, cmdalias_t *alias, qboolean *is_multiple) +{ + cvar_t *cvar; + long argno; + char *endptr; + static char vabuf[1024]; // cmd_mutex + + if(is_multiple) + *is_multiple = false; + + if(!varname || !*varname) + return NULL; + + if(alias) + { + if(!strcmp(varname, "*")) + { + if(is_multiple) + *is_multiple = true; + return Cmd_Args(); + } + else if(!strcmp(varname, "#")) + { + return va(vabuf, sizeof(vabuf), "%d", Cmd_Argc()); + } + else if(varname[strlen(varname) - 1] == '-') + { + argno = strtol(varname, &endptr, 10); + if(endptr == varname + strlen(varname) - 1) + { + // whole string is a number, apart from the - + const char *p = Cmd_Args(); + for(; argno > 1; --argno) + if(!COM_ParseToken_Console(&p)) + break; + if(p) + { + if(is_multiple) + *is_multiple = true; + + // kill pre-argument whitespace + for (;*p && ISWHITESPACE(*p);p++) + ; + + return p; + } + } + } + else + { + argno = strtol(varname, &endptr, 10); + if(*endptr == 0) + { + // whole string is a number + // NOTE: we already made sure we don't have an empty cvar name! + if(argno >= 0 && argno < Cmd_Argc()) + return Cmd_Argv(argno); + } + } + } + + if((cvar = Cvar_FindVar(varname)) && !(cvar->flags & CVAR_PRIVATE)) + return cvar->string; + + return NULL; +} + +qboolean Cmd_QuoteString(char *out, size_t outlen, const char *in, const char *quoteset, qboolean putquotes) +{ + qboolean quote_quot = !!strchr(quoteset, '"'); + qboolean quote_backslash = !!strchr(quoteset, '\\'); + qboolean quote_dollar = !!strchr(quoteset, '$'); + + if(putquotes) + { + if(outlen <= 2) + { + *out++ = 0; + return false; + } + *out++ = '"'; --outlen; + --outlen; + } + + while(*in) + { + if(*in == '"' && quote_quot) + { + if(outlen <= 2) + goto fail; + *out++ = '\\'; --outlen; + *out++ = '"'; --outlen; + } + else if(*in == '\\' && quote_backslash) + { + if(outlen <= 2) + goto fail; + *out++ = '\\'; --outlen; + *out++ = '\\'; --outlen; + } + else if(*in == '$' && quote_dollar) + { + if(outlen <= 2) + goto fail; + *out++ = '$'; --outlen; + *out++ = '$'; --outlen; + } + else + { + if(outlen <= 1) + goto fail; + *out++ = *in; --outlen; + } + ++in; + } + if(putquotes) + *out++ = '"'; + *out++ = 0; + return true; +fail: + if(putquotes) + *out++ = '"'; + *out++ = 0; + return false; +} + +static const char *Cmd_GetCvarValue(const char *var, size_t varlen, cmdalias_t *alias) +{ + static char varname[MAX_INPUTLINE]; // cmd_mutex + static char varval[MAX_INPUTLINE]; // cmd_mutex + const char *varstr = NULL; + char *varfunc; + qboolean required = false; + qboolean optional = false; + static char asis[] = "asis"; // just to suppress const char warnings + + if(varlen >= MAX_INPUTLINE) + varlen = MAX_INPUTLINE - 1; + memcpy(varname, var, varlen); + varname[varlen] = 0; + varfunc = strchr(varname, ' '); + + if(varfunc) + { + *varfunc = 0; + ++varfunc; + } + + if(*var == 0) + { + // empty cvar name? + if(alias) + Con_Printf("Warning: Could not expand $ in alias %s\n", alias->name); + else + Con_Printf("Warning: Could not expand $\n"); + return "$"; + } + + if(varfunc) + { + char *p; + // ? means optional + while((p = strchr(varfunc, '?'))) + { + optional = true; + memmove(p, p+1, strlen(p)); // with final NUL + } + // ! means required + while((p = strchr(varfunc, '!'))) + { + required = true; + memmove(p, p+1, strlen(p)); // with final NUL + } + // kill spaces + while((p = strchr(varfunc, ' '))) + { + memmove(p, p+1, strlen(p)); // with final NUL + } + // if no function is left, NULL it + if(!*varfunc) + varfunc = NULL; + } + + if(varname[0] == '$') + varstr = Cmd_GetDirectCvarValue(Cmd_GetDirectCvarValue(varname + 1, alias, NULL), alias, NULL); + else + { + qboolean is_multiple = false; + // Exception: $* and $n- don't use the quoted form by default + varstr = Cmd_GetDirectCvarValue(varname, alias, &is_multiple); + if(is_multiple) + if(!varfunc) + varfunc = asis; + } + + if(!varstr) + { + if(required) + { + if(alias) + Con_Printf("Error: Could not expand $%s in alias %s\n", varname, alias->name); + else + Con_Printf("Error: Could not expand $%s\n", varname); + return NULL; + } + else if(optional) + { + return ""; + } + else + { + if(alias) + Con_Printf("Warning: Could not expand $%s in alias %s\n", varname, alias->name); + else + Con_Printf("Warning: Could not expand $%s\n", varname); + dpsnprintf(varval, sizeof(varval), "$%s", varname); + return varval; + } + } + + if(!varfunc || !strcmp(varfunc, "q")) // note: quoted form is default, use "asis" to override! + { + // quote it so it can be used inside double quotes + // we just need to replace " by \", and of course, double backslashes + Cmd_QuoteString(varval, sizeof(varval), varstr, "\"\\", false); + return varval; + } + else if(!strcmp(varfunc, "asis")) + { + return varstr; + } + else + Con_Printf("Unknown variable function %s\n", varfunc); + + return varstr; +} + /* Cmd_PreprocessString -Preprocesses strings and replaces $*, $param#, $cvar accordingly +Preprocesses strings and replaces $*, $param#, $cvar accordingly. Also strips comments. */ -static void Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ) { +static qboolean Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ) { const char *in; + size_t eat, varlen; unsigned outlen; - int inquote; + const char *val; // don't crash if there's no room in the outtext buffer if( maxoutlen == 0 ) { - return; + return false; } maxoutlen--; // because of \0 in = intext; outlen = 0; - inquote = 0; while( *in && outlen < maxoutlen ) { - if( *in == '$' && !inquote ) { + if( *in == '$' ) { // this is some kind of expansion, see what comes after the $ in++; - // replacements that can always be used: - // $$ is replaced with $, to allow escaping $ - // $ is replaced with the contents of the cvar + + // The console does the following preprocessing: // - // the following can be used in aliases only: - // $* is replaced with all formal parameters (including name of the alias - this probably is not desirable) - // $0 is replaced with the name of this alias - // $ is replaced with an argument to this alias (or copied as-is if no such parameter exists), can be multiple digits + // - $$ is transformed to a single dollar sign. + // - $var or ${var} are expanded to the contents of the named cvar, + // with quotation marks and backslashes quoted so it can safely + // be used inside quotation marks (and it should always be used + // that way) + // - ${var asis} inserts the cvar value as is, without doing this + // quoting + // - ${var ?} silently expands to the empty string if + // $var does not exist + // - ${var !} fails expansion and executes nothing if + // $var does not exist + // - prefix the cvar name with a dollar sign to do indirection; + // for example, if $x has the value timelimit, ${$x} will return + // the value of $timelimit + // - when expanding an alias, the special variable name $* refers + // to all alias parameters, and a number refers to that numbered + // alias parameter, where the name of the alias is $0, the first + // parameter is $1 and so on; as a special case, $* inserts all + // parameters, without extra quoting, so one can use $* to just + // pass all parameters around. All parameters starting from $n + // can be referred to as $n- (so $* is equivalent to $1-). + // - ${* q} and ${n- q} force quoting anyway + // + // Note: when expanding an alias, cvar expansion is done in the SAME step + // as alias expansion so that alias parameters or cvar values containing + // dollar signs have no unwanted bad side effects. However, this needs to + // be accounted for when writing complex aliases. For example, + // alias foo "set x NEW; echo $x" + // actually expands to + // "set x NEW; echo OLD" + // and will print OLD! To work around this, use a second alias: + // alias foo "set x NEW; foo2" + // alias foo2 "echo $x" + // + // Also note: lines starting with alias are exempt from cvar expansion. + // If you want cvar expansion, write "alias" instead: + // + // set x 1 + // alias foo "echo $x" + // "alias" bar "echo $x" + // set x 2 + // + // foo will print 2, because the variable $x will be expanded when the alias + // gets expanded. bar will print 1, because the variable $x was expanded + // at definition time. foo can be equivalently defined as + // + // "alias" foo "echo $$x" + // + // because at definition time, $$ will get replaced to a single $. + if( *in == '$' ) { - outtext[outlen++] = *in++; - } else if( *in == '*' && alias ) { - const char *linein = Cmd_Args(); - - // include all parameters - if (linein) { - while( *linein && outlen < maxoutlen ) { - outtext[outlen++] = *linein++; - } + val = "$"; + eat = 1; + } else if(*in == '{') { + varlen = strcspn(in + 1, "}"); + if(in[varlen + 1] == '}') + { + val = Cmd_GetCvarValue(in + 1, varlen, alias); + if(!val) + return false; + eat = varlen + 2; } - - in++; - } else if( '0' <= *in && *in <= '9' && alias ) { - char *nexttoken; - int argnum; - - argnum = strtol( in, &nexttoken, 10 ); - - if( 0 <= argnum && argnum < Cmd_Argc() ) { - const char *param = Cmd_Argv( argnum ); - while( *param && outlen < maxoutlen ) { - outtext[outlen++] = *param++; - } - in = nexttoken; - } else if( argnum >= Cmd_Argc() ) { - Con_Printf( "Warning: Not enough parameters passed to alias '%s', at least %i expected:\n %s\n", alias->name, argnum, alias->value ); - outtext[outlen++] = '$'; + else + { + // ran out of data? + val = NULL; + eat = varlen + 1; } } else { - cvar_t *cvar; - const char *tempin = in; - - COM_ParseToken_Console( &tempin ); - // don't expand rcon_password or similar cvars (CVAR_PRIVATE flag) - if ((cvar = Cvar_FindVar(&com_token[0])) && !(cvar->flags & CVAR_PRIVATE)) { - const char *cvarcontent = cvar->string; - while( *cvarcontent && outlen < maxoutlen ) { - outtext[outlen++] = *cvarcontent++; - } - in = tempin; - } else { - if( alias ) { - Con_Printf( "Warning: could not find cvar %s when expanding alias %s\n %s\n", com_token, alias->name, alias->value ); - } else { - Con_Printf( "Warning: could not find cvar %s\n", com_token ); - } - outtext[outlen++] = '$'; - } + varlen = strspn(in, "#*0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-"); + val = Cmd_GetCvarValue(in, varlen, alias); + if(!val) + return false; + eat = varlen; } - } else { - if( *in == '"' ) { - inquote ^= 1; + if(val) + { + // insert the cvar value + while(*val && outlen < maxoutlen) + outtext[outlen++] = *val++; + in += eat; + } + else + { + // copy the unexpanded text + outtext[outlen++] = '$'; + while(eat && outlen < maxoutlen) + { + outtext[outlen++] = *in++; + --eat; + } } - outtext[outlen++] = *in++; } + else + outtext[outlen++] = *in++; } outtext[outlen] = 0; + return true; } /* @@ -604,11 +1344,19 @@ Called for aliases and fills in the alias into the cbuffer */ static void Cmd_ExecuteAlias (cmdalias_t *alias) { - static char buffer[ MAX_INPUTLINE + 2 ]; - Cmd_PreprocessString( alias->value, buffer, sizeof(buffer) - 2, alias ); + static char buffer[ MAX_INPUTLINE ]; // cmd_mutex + static char buffer2[ MAX_INPUTLINE ]; // cmd_mutex + qboolean ret = Cmd_PreprocessString( alias->value, buffer, sizeof(buffer) - 2, alias ); + if(!ret) + return; // insert at start of command buffer, so that aliases execute in order // (fixes bug introduced by Black on 20050705) - Cbuf_InsertText( buffer ); + + // Note: Cbuf_PreprocessString will be called on this string AGAIN! So we + // have to make sure that no second variable expansion takes place, otherwise + // alias parameters containing dollar signs can have bad effects. + Cmd_QuoteString(buffer2, sizeof(buffer2), buffer, "$", false); + Cbuf_InsertText( buffer2 ); } /* @@ -624,34 +1372,94 @@ static void Cmd_List_f (void) { cmd_function_t *cmd; const char *partial; - int len, count; + size_t len; + int count; + qboolean ispattern; if (Cmd_Argc() > 1) { partial = Cmd_Argv (1); - len = (int)strlen(partial); + len = strlen(partial); + ispattern = (strchr(partial, '*') || strchr(partial, '?')); } else { partial = NULL; len = 0; + ispattern = false; } count = 0; for (cmd = cmd_functions; cmd; cmd = cmd->next) { - if (partial && strncmp(partial, cmd->name, len)) + if (partial && (ispattern ? !matchpattern_with_separator(cmd->name, partial, false, "", false) : strncmp(partial, cmd->name, len))) continue; Con_Printf("%s : %s\n", cmd->name, cmd->description); count++; } - if (partial) - Con_Printf("%i Command%s beginning with \"%s\"\n\n", count, (count > 1) ? "s" : "", partial); + if (len) + { + if(ispattern) + Con_Printf("%i Command%s matching \"%s\"\n\n", count, (count > 1) ? "s" : "", partial); + else + Con_Printf("%i Command%s beginning with \"%s\"\n\n", count, (count > 1) ? "s" : "", partial); + } else Con_Printf("%i Command%s\n\n", count, (count > 1) ? "s" : ""); } +static void Cmd_Apropos_f(void) +{ + cmd_function_t *cmd; + cvar_t *cvar; + cmdalias_t *alias; + const char *partial; + int count; + qboolean ispattern; + char vabuf[1024]; + + if (Cmd_Argc() > 1) + partial = Cmd_Args(); + else + { + Con_Printf("usage: apropos \n"); + return; + } + + ispattern = partial && (strchr(partial, '*') || strchr(partial, '?')); + if(!ispattern) + partial = va(vabuf, sizeof(vabuf), "*%s*", partial); + + count = 0; + for (cvar = cvar_vars; cvar; cvar = cvar->next) + { + if (!matchpattern_with_separator(cvar->name, partial, true, "", false)) + if (!matchpattern_with_separator(cvar->description, partial, true, "", false)) + continue; + Con_Printf ("cvar ^3%s^7 is \"%s\" [\"%s\"] %s\n", cvar->name, cvar->string, cvar->defstring, cvar->description); + count++; + } + for (cmd = cmd_functions; cmd; cmd = cmd->next) + { + if (!matchpattern_with_separator(cmd->name, partial, true, "", false)) + if (!matchpattern_with_separator(cmd->description, partial, true, "", false)) + continue; + Con_Printf("command ^2%s^7: %s\n", cmd->name, cmd->description); + count++; + } + for (alias = cmd_alias; alias; alias = alias->next) + { + // procede here a bit differently as an alias value always got a final \n + if (!matchpattern_with_separator(alias->name, partial, true, "", false)) + if (!matchpattern_with_separator(alias->value, partial, true, "\n", false)) // when \n is as separator wildcards don't match it + continue; + Con_Printf("alias ^5%s^7: %s", alias->name, alias->value); // do not print an extra \n + count++; + } + Con_Printf("%i result%s\n\n", count, (count > 1) ? "s" : ""); +} + /* ============ Cmd_Init @@ -664,6 +1472,9 @@ void Cmd_Init (void) cmd_text.data = cmd_text_buf; cmd_text.maxsize = sizeof(cmd_text_buf); cmd_text.cursize = 0; + + if (Thread_HasThreads()) + cmd_text_mutex = Thread_CreateMutex(); } void Cmd_Init_Commands (void) @@ -674,22 +1485,31 @@ void Cmd_Init_Commands (void) Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f, "execute commandline parameters (must be present in quake.rc script)"); Cmd_AddCommand ("exec",Cmd_Exec_f, "execute a script file"); Cmd_AddCommand ("echo",Cmd_Echo_f, "print a message to the console (useful in scripts)"); - Cmd_AddCommand ("alias",Cmd_Alias_f, "create a script function (parameters are passed in as $1 through $9, and $* for all parameters)"); + Cmd_AddCommand ("alias",Cmd_Alias_f, "create a script function (parameters are passed in as $X (being X a number), $* for all parameters, $X- for all parameters starting from $X). Without arguments show the list of all alias"); + Cmd_AddCommand ("unalias",Cmd_UnAlias_f, "remove an alias"); Cmd_AddCommand ("cmd", Cmd_ForwardToServer, "send a console commandline to the server (used by some mods)"); Cmd_AddCommand ("wait", Cmd_Wait_f, "make script execution wait for next rendered frame"); Cmd_AddCommand ("set", Cvar_Set_f, "create or change the value of a console variable"); Cmd_AddCommand ("seta", Cvar_SetA_f, "create or change the value of a console variable that will be saved to config.cfg"); + Cmd_AddCommand ("unset", Cvar_Del_f, "delete a cvar (does not work for static ones like _cl_name, or read-only ones)"); +#ifdef FILLALLCVARSWITHRUBBISH + Cmd_AddCommand ("fillallcvarswithrubbish", Cvar_FillAll_f, "fill all cvars with a specified number of characters to provoke buffer overruns"); +#endif /* FILLALLCVARSWITHRUBBISH */ // 2000-01-09 CmdList, CvarList commands By Matthias "Maddes" Buecher // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com - Cmd_AddCommand ("cmdlist", Cmd_List_f, "lists all console commands beginning with the specified prefix"); - Cmd_AddCommand ("cvarlist", Cvar_List_f, "lists all console variables beginning with the specified prefix"); + Cmd_AddCommand ("cmdlist", Cmd_List_f, "lists all console commands beginning with the specified prefix or matching the specified wildcard pattern"); + Cmd_AddCommand ("cvarlist", Cvar_List_f, "lists all console variables beginning with the specified prefix or matching the specified wildcard pattern"); + Cmd_AddCommand ("apropos", Cmd_Apropos_f, "lists all console variables/commands/aliases containing the specified string in the name or description"); Cmd_AddCommand ("cvar_lockdefaults", Cvar_LockDefaults_f, "stores the current values of all cvars into their default values, only used once during startup after parsing default.cfg"); Cmd_AddCommand ("cvar_resettodefaults_all", Cvar_ResetToDefaults_All_f, "sets all cvars to their locked default values"); Cmd_AddCommand ("cvar_resettodefaults_nosaveonly", Cvar_ResetToDefaults_NoSaveOnly_f, "sets all non-saved cvars to their locked default values (variables that will not be saved to config.cfg)"); Cmd_AddCommand ("cvar_resettodefaults_saveonly", Cvar_ResetToDefaults_SaveOnly_f, "sets all saved cvars to their locked default values (variables that will be saved to config.cfg)"); + Cmd_AddCommand ("cprint", Cmd_Centerprint_f, "print something at the screen center"); + Cmd_AddCommand ("defer", Cmd_Defer_f, "execute a command in the future"); + // DRESK - 5/14/06 // Support Doom3-style Toggle Command Cmd_AddCommand( "toggle", Cmd_Toggle_f, "toggles a console variable's values (use for more info)"); @@ -702,6 +1522,14 @@ Cmd_Shutdown */ void Cmd_Shutdown(void) { + if (cmd_text_mutex) + { + // we usually have this locked when we get here from Host_Quit_f + Cbuf_UnlockThreadMutex(); + Thread_DestroyMutex(cmd_text_mutex); + } + cmd_text_mutex = NULL; + Mem_FreePool(&cmd_mempool); } @@ -756,7 +1584,7 @@ static void Cmd_TokenizeString (const char *text) while (1) { // skip whitespace up to a /n - while (*text && *text <= ' ' && *text != '\r' && *text != '\n') + while (*text && ISWHITESPACE(*text) && *text != '\r' && *text != '\n') text++; // line endings: @@ -963,7 +1791,7 @@ void Cmd_CompleteCommandPrint (const char *partial) // Loop through the command list and print all matches for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strncasecmp(partial, cmd->name, len)) - Con_Printf("%s : %s\n", cmd->name, cmd->description); + Con_Printf("^2%s^7: %s\n", cmd->name, cmd->description); } /* @@ -1001,7 +1829,7 @@ void Cmd_CompleteAliasPrint (const char *partial) // Loop through the alias list and print all matches for (alias = cmd_alias; alias; alias = alias->next) if (!strncasecmp(partial, alias->name, len)) - Con_Printf("%s : %s\n", alias->name, alias->value); + Con_Printf("^5%s^7: %s", alias->name, alias->value); } @@ -1070,7 +1898,6 @@ void Cmd_ClearCsqcFuncs (void) cmd->csqcfunc = false; } -qboolean CL_VM_ConsoleCommand (const char *cmd); /* ============ Cmd_ExecuteString @@ -1079,23 +1906,23 @@ A complete command line has been parsed, so try to execute it FIXME: lookupnoadd the token to speed search? ============ */ -void Cmd_ExecuteString (const char *text, cmd_source_t src) +void Cmd_ExecuteString (const char *text, cmd_source_t src, qboolean lockmutex) { int oldpos; + int found; cmd_function_t *cmd; cmdalias_t *a; - + if (lockmutex) + Cbuf_LockThreadMutex(); oldpos = cmd_tokenizebufferpos; cmd_source = src; + found = false; Cmd_TokenizeString (text); // execute the command line if (!Cmd_Argc()) - { - cmd_tokenizebufferpos = oldpos; - return; // no tokens - } + goto done; // no tokens // check functions for (cmd=cmd_functions ; cmd ; cmd=cmd->next) @@ -1103,7 +1930,7 @@ void Cmd_ExecuteString (const char *text, cmd_source_t src) if (!strcasecmp (cmd_argv[0],cmd->name)) { if (cmd->csqcfunc && CL_VM_ConsoleCommand (text)) //[515]: csqc - return; + goto done; switch (src) { case src_command: @@ -1121,26 +1948,26 @@ void Cmd_ExecuteString (const char *text, cmd_source_t src) } else Con_Printf("Command \"%s\" can not be executed\n", Cmd_Argv(0)); - cmd_tokenizebufferpos = oldpos; - return; + found = true; + goto command_found; case src_client: if (cmd->clientfunction) { cmd->clientfunction (); - cmd_tokenizebufferpos = oldpos; - return; + goto done; } break; } break; } } +command_found: // if it's a client command and no command was found, say so. if (cmd_source == src_client) { Con_Printf("player \"%s\" tried to %s\n", host_client->name, text); - return; + goto done; } // check alias @@ -1149,16 +1976,21 @@ void Cmd_ExecuteString (const char *text, cmd_source_t src) if (!strcasecmp (cmd_argv[0], a->name)) { Cmd_ExecuteAlias(a); - cmd_tokenizebufferpos = oldpos; - return; + goto done; } } + if(found) // if the command was hooked and found, all is good + goto done; + // check cvars if (!Cvar_Command () && host_framecount > 0) Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0)); +done: cmd_tokenizebufferpos = oldpos; + if (lockmutex) + Cbuf_UnlockThreadMutex(); } @@ -1276,7 +2108,7 @@ void Cmd_ForwardStringToServer (const char *s) break; } // write the resulting text - SZ_Write(&cls.netcon->message, (unsigned char *)temp, strlen(temp)); + SZ_Write(&cls.netcon->message, (unsigned char *)temp, (int)strlen(temp)); s += 2; continue; } @@ -1299,6 +2131,7 @@ Sends the entire command line over to the server void Cmd_ForwardToServer (void) { const char *s; + char vabuf[1024]; if (!strcasecmp(Cmd_Argv(0), "cmd")) { // we want to strip off "cmd", so just send the args @@ -1307,7 +2140,7 @@ void Cmd_ForwardToServer (void) else { // we need to keep the command name, so send Cmd_Argv(0), a space and then Cmd_Args() - s = va("%s %s", Cmd_Argv(0), Cmd_Argc() > 1 ? Cmd_Args() : ""); + s = va(vabuf, sizeof(vabuf), "%s %s", Cmd_Argv(0), Cmd_Argc() > 1 ? Cmd_Args() : ""); } // don't send an empty forward message if the user tries "cmd" by itself if (!s || !*s) @@ -1342,3 +2175,61 @@ int Cmd_CheckParm (const char *parm) return 0; } + + +void Cmd_SaveInitState(void) +{ + cmd_function_t *f; + cmdalias_t *a; + for (f = cmd_functions;f;f = f->next) + f->initstate = true; + for (a = cmd_alias;a;a = a->next) + { + a->initstate = true; + a->initialvalue = Mem_strdup(zonemempool, a->value); + } + Cvar_SaveInitState(); +} + +void Cmd_RestoreInitState(void) +{ + cmd_function_t *f, **fp; + cmdalias_t *a, **ap; + for (fp = &cmd_functions;(f = *fp);) + { + if (f->initstate) + fp = &f->next; + else + { + // destroy this command, it didn't exist at init + Con_DPrintf("Cmd_RestoreInitState: Destroying command %s\n", f->name); + *fp = f->next; + Z_Free(f); + } + } + for (ap = &cmd_alias;(a = *ap);) + { + if (a->initstate) + { + // restore this alias, it existed at init + if (strcmp(a->value ? a->value : "", a->initialvalue ? a->initialvalue : "")) + { + Con_DPrintf("Cmd_RestoreInitState: Restoring alias %s\n", a->name); + if (a->value) + Z_Free(a->value); + a->value = Mem_strdup(zonemempool, a->initialvalue); + } + ap = &a->next; + } + else + { + // free this alias, it didn't exist at init... + Con_DPrintf("Cmd_RestoreInitState: Destroying alias %s\n", a->name); + *ap = a->next; + if (a->value) + Z_Free(a->value); + Z_Free(a); + } + } + Cvar_RestoreInitState(); +}