X-Git-Url: http://git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=blobdiff_plain;f=cmd.c;h=7b1562771e6ad8adb02690832581dbc3f76bec94;hp=3deabb0803e582597d7df7a425270781903cf09e;hb=97820be04836873cca98fddf52c1901e430d9bee;hpb=6faa19b9236acf0c8606529a0d46e00934e6154e diff --git a/cmd.c b/cmd.c index 3deabb08..a12fae8e 100644 --- a/cmd.c +++ b/cmd.c @@ -20,28 +20,39 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // cmd.c -- Quake script command processing module #include "quakedef.h" +#include "thread.h" -#define MAX_ALIAS_NAME 32 +cmd_state_t *cmd_local; +cmd_state_t *cmd_serverfromclient; -typedef struct cmdalias_s -{ - struct cmdalias_s *next; - char name[MAX_ALIAS_NAME]; - char *value; -} cmdalias_t; +cmd_userdefined_t cmd_userdefined_all; +cmd_userdefined_t cmd_userdefined_null; -static cmdalias_t *cmd_alias; +typedef struct cmd_iter_s { + cmd_state_t *cmd; +} +cmd_iter_t; -static qboolean cmd_wait; +static cmd_iter_t *cmd_iter_all; -static mempool_t *cmd_mempool; +mempool_t *cbuf_mempool; -#define CMD_TOKENIZELENGTH 4096 -static char cmd_tokenizebuffer[CMD_TOKENIZELENGTH]; -static int cmd_tokenizebufferpos = 0; +// we only run the +whatever commandline arguments once +qbool host_stuffcmdsrun = false; //============================================================================= +void Cbuf_Lock(cmd_buf_t *cbuf) +{ + Thread_LockMutex(cbuf->lock); +} + +void Cbuf_Unlock(cmd_buf_t *cbuf) +{ + Thread_UnlockMutex(cbuf->lock); +} + + /* ============ Cmd_Wait_f @@ -51,9 +62,106 @@ next frame. This allows commands like: bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2" ============ */ -static void Cmd_Wait_f (void) +static void Cmd_Wait_f (cmd_state_t *cmd) +{ + cmd->cbuf->wait = true; +} + +/* +============ +Cmd_Defer_f + +Cause a command to be executed after a delay. +============ +*/ +static cmd_input_t *Cbuf_LinkGet(cmd_buf_t *cbuf, cmd_input_t *existing); +static void Cmd_Defer_f (cmd_state_t *cmd) +{ + cmd_input_t *current; + cmd_buf_t *cbuf = cmd->cbuf; + + if(Cmd_Argc(cmd) == 1) + { + if(List_Is_Empty(&cbuf->deferred)) + Con_Printf("No commands are pending.\n"); + else + { + List_For_Each_Entry(current, &cbuf->deferred, cmd_input_t, list) + Con_Printf("-> In %9.2f: %s\n", current->delay, current->text); + } + } + else if(Cmd_Argc(cmd) == 2 && !strcasecmp("clear", Cmd_Argv(cmd, 1))) + { + while(!List_Is_Empty(&cbuf->deferred)) + List_Move_Tail(cbuf->deferred.next, &cbuf->free); + } + else if(Cmd_Argc(cmd) == 3) + { + const char *text = Cmd_Argv(cmd, 2); + current = Cbuf_LinkGet(cbuf, NULL); + current->length = strlen(text); + current->source = cmd; + current->delay = atof(Cmd_Argv(cmd, 1)); + + if(current->size < current->length) + { + current->text = (char *)Mem_Realloc(cbuf_mempool, current->text, current->length + 1); + current->size = current->length; + } + + strlcpy(current->text, text, current->length + 1); + + List_Move_Tail(¤t->list, &cbuf->deferred); + } + 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 (cmd_state_t *cmd) { - cmd_wait = true; + char msg[MAX_INPUTLINE]; + unsigned int i, c, p; + c = Cmd_Argc(cmd); + if(c >= 2) + { + strlcpy(msg, Cmd_Argv(cmd,1), sizeof(msg)); + for(i = 2; i < c; ++i) + { + strlcat(msg, " ", sizeof(msg)); + strlcat(msg, Cmd_Argv(cmd, 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); + } } /* @@ -64,9 +172,180 @@ static void Cmd_Wait_f (void) ============================================================================= */ - // LordHavoc: inreased this from 8192 to 32768 -static sizebuf_t cmd_text; -static unsigned char cmd_text_buf[32768]; +static cmd_input_t *Cbuf_LinkGet(cmd_buf_t *cbuf, cmd_input_t *existing) +{ + cmd_input_t *ret = NULL; + if(existing && existing->pending) + ret = existing; + else if(!List_Is_Empty(&cbuf->free)) + { + ret = List_Entry(cbuf->free.next, cmd_input_t, list); + ret->length = 0; + ret->pending = false; + } + return ret; +} + +static cmd_input_t *Cmd_AllocInputNode(void) +{ + cmd_input_t *node = (cmd_input_t *)Mem_Alloc(cbuf_mempool, sizeof(cmd_input_t)); + node->list.prev = node->list.next = &node->list; + node->size = node->length = node->pending = 0; + return node; +} + + +// Cloudwalk FIXME: The entire design of this thing is overly complicated. +// We could very much safely have one input node per line whether or not +// the command was terminated. We don't need to split up input nodes per command +// executed. +static size_t Cmd_ParseInput (cmd_input_t **output, char **input) +{ + size_t pos, cmdsize = 0, start = 0; + qbool command = false, lookahead = false; + qbool quotes = false, comment = false; + qbool escaped = false; + + /* + * The Quake command-line is super basic. It can be entered in the console + * or in config files. A semicolon is used to terminate a command and chain + * them together. Otherwise, a newline delineates command input. + * + * In most engines, the Quake command-line is a simple linear text buffer that + * is parsed when it executes. In Darkplaces, we use a linked list of command + * input and parse the input on the spot. + * + * This was done because Darkplaces allows multiple command interpreters on the + * same thread. Previously, each interpreter maintained its own buffer and this + * caused problems related to execution order, and maintaining a single simple + * buffer for all interpreters makes it non-trivial to keep track of which + * command should execute on which interpreter. + */ + + // Run until command and lookahead are both true, or until we run out of input. + for (pos = 0; (*input)[pos]; pos++) + { + // Look for newlines and semicolons. Ignore semicolons in quotes. + switch((*input)[pos]) + { + case '\r': + case '\n': + command = false; + comment = false; + break; + default: + if(!comment) // Not a newline so far. Still not a valid command yet. + { + if(!quotes && (*input)[pos] == ';') // Ignore semicolons in quotes. + command = false; + else if (ISCOMMENT((*input), pos)) // Comments + { + comment = true; + command = false; + } + else + { + command = true; + if(!lookahead) + { + if(!cmdsize) + start = pos; + cmdsize++; + } + + switch((*input)[pos]) + { + case '"': + if (!escaped) + quotes = !quotes; + else + escaped = false; + break; + case '\\': + if (!escaped && quotes) + escaped = true; + else if (escaped) + escaped = false; + break; + } + } + } + } + if(cmdsize && !command) + lookahead = true; + + if(command && lookahead) + break; + } + + if(cmdsize) + { + size_t offset = 0; + + if(!*output) + *output = Cmd_AllocInputNode(); + + // Append, since this input line hasn't closed yet. + if((*output)->pending) + offset = (*output)->length; + + (*output)->length += cmdsize; + + if((*output)->size < (*output)->length) + { + (*output)->text = (char *)Mem_Realloc(cbuf_mempool, (*output)->text, (*output)->length + 1); + (*output)->size = (*output)->length; + } + + strlcpy(&(*output)->text[offset], &(*input)[start], cmdsize + 1); + + /* + * If we were still looking ahead by the time we broke from the loop, the command input + * hasn't terminated yet and we're still expecting more, so keep this node open for appending later. + */ + (*output)->pending = !lookahead; + } + + // Set input to its new position. Can be NULL. + *input = &(*input)[pos]; + + return cmdsize; +} + +// Cloudwalk: Not happy with this, but it works. +static void Cbuf_LinkCreate(cmd_state_t *cmd, llist_t *head, cmd_input_t *existing, const char *text) +{ + char *in = (char *)&text[0]; + cmd_buf_t *cbuf = cmd->cbuf; + size_t totalsize = 0, newsize = 0; + cmd_input_t *current = NULL; + + // Slide the pointer down until we reach the end + while(*in) + { + // Check if the current node is still accepting input (input line hasn't terminated) + current = Cbuf_LinkGet(cbuf, existing); + newsize = Cmd_ParseInput(¤t, &in); + + // Valid command + if(newsize) + { + // current will match existing if the input line hasn't terminated yet + if(current != existing) + { + current->source = cmd; + List_Move_Tail(¤t->list, head); + } + + totalsize += newsize; + } + else if (current == existing && !totalsize) + current->pending = false; + current = NULL; + } + + cbuf->size += totalsize; +} /* ============ @@ -75,55 +354,81 @@ Cbuf_AddText Adds command text at the end of the buffer ============ */ -void Cbuf_AddText (const char *text) +void Cbuf_AddText (cmd_state_t *cmd, const char *text) { - int l; + size_t l = strlen(text); + cmd_buf_t *cbuf = cmd->cbuf; + llist_t llist = {&llist, &llist}; - l = (int)strlen (text); + Cbuf_Lock(cbuf); - if (cmd_text.cursize + l >= cmd_text.maxsize) - { + if (cbuf->maxsize - cbuf->size <= l) Con_Print("Cbuf_AddText: overflow\n"); - return; + else + { + Cbuf_LinkCreate(cmd, &llist, (List_Is_Empty(&cbuf->start) ? NULL : List_Entry(cbuf->start.prev, cmd_input_t, list)), text); + if(!List_Is_Empty(&llist)) + List_Splice_Tail(&llist, &cbuf->start); } - - SZ_Write (&cmd_text, (const unsigned char *)text, (int)strlen (text)); + Cbuf_Unlock(cbuf); } - /* ============ Cbuf_InsertText Adds command text immediately after the current command -Adds a \n to the text FIXME: actually change the command buffer to do less copying ============ */ -void Cbuf_InsertText (const char *text) +void Cbuf_InsertText (cmd_state_t *cmd, const char *text) { - char *temp; - int templen; + cmd_buf_t *cbuf = cmd->cbuf; + llist_t llist = {&llist, &llist}; + size_t l = strlen(text); + + Cbuf_Lock(cbuf); - // copy off any commands still remaining in the exec buffer - templen = cmd_text.cursize; - if (templen) + // we need to memmove the existing text and stuff this in before it... + if (cbuf->size + l >= cbuf->maxsize) + Con_Print("Cbuf_InsertText: overflow\n"); + else { - temp = (char *)Mem_Alloc (tempmempool, templen); - memcpy (temp, cmd_text.data, templen); - SZ_Clear (&cmd_text); + Cbuf_LinkCreate(cmd, &llist, (List_Is_Empty(&cbuf->start) ? NULL : List_Entry(cbuf->start.next, cmd_input_t, list)), text); + if(!List_Is_Empty(&llist)) + List_Splice(&llist, &cbuf->start); } - else - temp = NULL; - // add the entire text of the file - Cbuf_AddText (text); + Cbuf_Unlock(cbuf); +} + +/* +============ +Cbuf_Execute_Deferred --blub +============ +*/ +static void Cbuf_Execute_Deferred (cmd_buf_t *cbuf) +{ + cmd_input_t *current; + double eat; - // add the copied off data - if (temp != NULL) + if (host.realtime - cbuf->deferred_oldtime < 0 || host.realtime - cbuf->deferred_oldtime > 1800) + cbuf->deferred_oldtime = host.realtime; + eat = host.realtime - cbuf->deferred_oldtime; + if (eat < (1.0 / 120.0)) + return; + cbuf->deferred_oldtime = host.realtime; + + List_For_Each_Entry(current, &cbuf->deferred, cmd_input_t, list) { - SZ_Write (&cmd_text, (const unsigned char *)temp, templen); - Mem_Free (temp); + current->delay -= eat; + if(current->delay <= 0) + { + cbuf->size += current->length; + List_Move(¤t->list, &cbuf->start); + // We must return and come back next frame or the engine will freeze. Fragile... like glass :3 + return; + } } } @@ -132,60 +437,100 @@ void Cbuf_InsertText (const char *text) Cbuf_Execute ============ */ -void Cbuf_Execute (void) +static qbool Cmd_PreprocessString(cmd_state_t *cmd, const char *intext, char *outtext, unsigned maxoutlen, cmd_alias_t *alias ); +void Cbuf_Execute (cmd_buf_t *cbuf) { - int i; - char *text; - char line[1024]; - int quotes; + cmd_input_t *current; + char preprocessed[MAX_INPUTLINE]; + char *firstchar; - // LordHavoc: making sure the tokenizebuffer doesn't get filled up by repeated crashes - cmd_tokenizebufferpos = 0; + // LadyHavoc: making sure the tokenizebuffer doesn't get filled up by repeated crashes + cbuf->tokenizebufferpos = 0; - while (cmd_text.cursize) + while (!List_Is_Empty(&cbuf->start)) { -// find a \n or ; line break - text = (char *)cmd_text.data; - - quotes = 0; - for (i=0 ; i< cmd_text.cursize ; i++) + /* + * 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 + */ + current = List_Entry(cbuf->start.next, cmd_input_t, list); + + // Recycle memory so using WASD doesn't cause a malloc and free + List_Move_Tail(¤t->list, &cbuf->free); + + /* + * Assume we're rolling with the current command-line and + * always set this false because alias expansion or cbuf insertion + * without a newline may set this true, and cause weirdness. + */ + current->pending = false; + + cbuf->size -= current->length; + + firstchar = current->text; + 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 (text[i] == '"') - quotes ^= 1; - if ( !quotes && text[i] == ';') - break; // don't break if inside a quoted string - if (text[i] == '\r' || text[i] == '\n') - break; + if(Cmd_PreprocessString(current->source, current->text, preprocessed, sizeof(preprocessed), NULL )) + Cmd_ExecuteString(current->source, preprocessed, src_local, false); } - - 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 == cmd_text.cursize) - cmd_text.cursize = 0; else { - i++; - cmd_text.cursize -= i; - memcpy (cmd_text.data, text+i, cmd_text.cursize); + Cmd_ExecuteString (current->source, current->text, src_local, false); } -// execute the command line - Cmd_ExecuteString (line, src_command); + current = NULL; - if (cmd_wait) - { // skip out while text still remains in buffer, leaving it - // for next frame - cmd_wait = false; + if (cbuf->wait) + { + /* + * Skip out while text still remains in + * buffer, leaving it for next frame + */ + cbuf->wait = false; break; } } } +/* +=================== +Cbuf_Frame_Input + +Add them exactly as if they had been typed at the console +=================== +*/ +static void Cbuf_Frame_Input(void) +{ + char *line; + + while ((line = Sys_ConsoleInput())) + Cbuf_AddText(cmd_local, line); +} + +void Cbuf_Frame(cmd_buf_t *cbuf) +{ + // check for commands typed to the host + Cbuf_Frame_Input(); + +// R_TimeReport("preconsole"); + + // execute commands queued with the defer command + Cbuf_Execute_Deferred(cbuf); + if (cbuf->size) + { + SV_LockThreadMutex(); + Cbuf_Execute(cbuf); + SV_UnlockThreadMutex(); + } + +// R_TimeReport("console"); +} + /* ============================================================================== @@ -204,77 +549,342 @@ quake +prog jctest.qp +cmd amlev1 quake -nosound +cmd amlev1 =============== */ -qboolean host_stuffcmdsrun = false; -void Cmd_StuffCmds_f (void) +static void Cmd_StuffCmds_f (cmd_state_t *cmd) { int i, j, l; - // this is per command, and bounds checked (no buffer overflows) - char build[2048]; + // this is for all commandline options combined (and is bounds checked) + char build[MAX_INPUTLINE]; - if (Cmd_Argc () != 1) + if (Cmd_Argc (cmd) != 1) { Con_Print("stuffcmds : execute command line parameters\n"); return; } + // no reason to run the commandline arguments twice + if (host_stuffcmdsrun) + return; + host_stuffcmdsrun = true; - for (i = 0;i < com_argc;i++) + build[0] = 0; + l = 0; + for (i = 0;i < sys.argc;i++) { - if (com_argv[i] && com_argv[i][0] == '+' && (com_argv[i][1] < '0' || com_argv[i][1] > '9')) + if (sys.argv[i] && sys.argv[i][0] == '+' && (sys.argv[i][1] < '0' || sys.argv[i][1] > '9') && l + strlen(sys.argv[i]) - 1 <= sizeof(build) - 1) { - l = 0; j = 1; - while (com_argv[i][j]) - build[l++] = com_argv[i][j++]; + while (sys.argv[i][j]) + build[l++] = sys.argv[i][j++]; i++; - for (;i < com_argc;i++) + for (;i < sys.argc;i++) { - if (!com_argv[i]) + if (!sys.argv[i]) continue; - if ((com_argv[i][0] == '+' || com_argv[i][0] == '-') && (com_argv[i][1] < '0' || com_argv[i][1] > '9')) + if ((sys.argv[i][0] == '+' || sys.argv[i][0] == '-') && (sys.argv[i][1] < '0' || sys.argv[i][1] > '9')) break; - if (l + strlen(com_argv[i]) + 5 > sizeof(build)) + if (l + strlen(sys.argv[i]) + 4 > sizeof(build) - 1) break; build[l++] = ' '; - build[l++] = '\"'; - for (j = 0;com_argv[i][j];j++) - build[l++] = com_argv[i][j]; - build[l++] = '\"'; + if (strchr(sys.argv[i], ' ')) + build[l++] = '\"'; + for (j = 0;sys.argv[i][j];j++) + build[l++] = sys.argv[i][j]; + if (strchr(sys.argv[i], ' ')) + build[l++] = '\"'; } build[l++] = '\n'; - build[l++] = 0; - Cbuf_InsertText (build); i--; } } + // now terminate the combined string and prepend it to the command buffer + // we already reserved space for the terminator + build[l++] = 0; + Cbuf_InsertText (cmd, build); } +static void Cmd_Exec(cmd_state_t *cmd, const char *filename) +{ + char *f; + size_t filenameLen = strlen(filename); + qbool isdefaultcfg = + !strcmp(filename, "default.cfg") || + (filenameLen >= 12 && !strcmp(filename + filenameLen - 12, "/default.cfg")); + + if (!strcmp(filename, "config.cfg")) + { + filename = CONFIGFILENAME; + if (Sys_CheckParm("-noconfig")) + return; // don't execute config.cfg + } + + f = (char *)FS_LoadFile (filename, tempmempool, false, NULL); + if (!f) + { + Con_Printf("couldn't exec %s\n",filename); + return; + } + 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 (isdefaultcfg) + Cbuf_InsertText(cmd, "\ncvar_lockdefaults\n"); + + Cbuf_InsertText (cmd, 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(cmd, "\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" +"csqc_polygons_defaultmaterial_nocullface 0\n" + ); + break; + case GAME_NEHAHRA: + Cbuf_InsertText(cmd, "\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" +"csqc_polygons_defaultmaterial_nocullface 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(cmd, "\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" +"csqc_polygons_defaultmaterial_nocullface 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(cmd, "\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" +"csqc_polygons_defaultmaterial_nocullface 0\n" + ); + break; + case GAME_TENEBRAE: + Cbuf_InsertText(cmd, "\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" +"csqc_polygons_defaultmaterial_nocullface 0\n" + ); + break; + case GAME_NEXUIZ: + Cbuf_InsertText(cmd, "\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" +"csqc_polygons_defaultmaterial_nocullface 1\n" +"con_chatsound_team_mask 13\n" + ); + break; + case GAME_XONOTIC: + case GAME_VORETOURNAMENT: + // compatibility for versions prior to 2020-05-25, this can be overridden in newer versions to get the default behavior and be consistent with FTEQW engine + Cbuf_InsertText(cmd, "\n" +"csqc_polygons_defaultmaterial_nocullface 1\n" +"con_chatsound_team_mask 13\n" +"sv_gameplayfix_customstats 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(cmd, "\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" +"csqc_polygons_defaultmaterial_nocullface 1\n" + ); + break; + default: + Cbuf_InsertText(cmd, "\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" +"csqc_polygons_defaultmaterial_nocullface 0\n" + ); + break; + } + } +} /* =============== Cmd_Exec_f =============== */ -static void Cmd_Exec_f (void) +static void Cmd_Exec_f (cmd_state_t *cmd) { - char *f; + fssearch_t *s; + int i; - if (Cmd_Argc () != 2) + if (Cmd_Argc(cmd) != 2) { Con_Print("exec : execute a script file\n"); return; } - f = (char *)FS_LoadFile (Cmd_Argv(1), tempmempool, false, NULL); - if (!f) + s = FS_Search(Cmd_Argv(cmd, 1), true, true, NULL); + if(!s || !s->numfilenames) { - Con_Printf("couldn't exec %s\n",Cmd_Argv(1)); + Con_Printf("couldn't exec %s\n",Cmd_Argv(cmd, 1)); return; } - Con_DPrintf("execing %s\n",Cmd_Argv(1)); - Cbuf_InsertText (f); - Mem_Free(f); + for(i = 0; i < s->numfilenames; ++i) + Cmd_Exec(cmd, s->filenames[i]); + + FS_FreeSearch(s); } @@ -285,15 +895,95 @@ Cmd_Echo_f Just prints the rest of the line to the console =============== */ -static void Cmd_Echo_f (void) +static void Cmd_Echo_f (cmd_state_t *cmd) { int i; - for (i=1 ; i - toggles between 0 and 1\n toggle - toggles between 0 and \n toggle [string 1] [string 2]...[string n] - cycles through all strings\n"); + else + { // Correct Arguments Specified + // Acquire Potential CVar + cvar_t* cvCVar = Cvar_FindVar(cmd->cvars, Cmd_Argv(cmd, 1), cmd->cvars_flagsmask); + + if(cvCVar != NULL) + { // Valid CVar + if(nNumArgs == 2) + { // Default Usage + if(cvCVar->integer) + Cvar_SetValueQuick(cvCVar, 0); + else + Cvar_SetValueQuick(cvCVar, 1); + } + else + if(nNumArgs == 3) + { // 0 and Specified Usage + if(cvCVar->integer == atoi(Cmd_Argv(cmd, 2) ) ) + // CVar is Specified Value; // Reset to 0 + Cvar_SetValueQuick(cvCVar, 0); + else + if(cvCVar->integer == 0) + // CVar is 0; Specify Value + Cvar_SetQuick(cvCVar, Cmd_Argv(cmd, 2) ); + else + // CVar does not match; Reset to 0 + Cvar_SetValueQuick(cvCVar, 0); + } + else + { // Variable Values Specified + int nCnt; + int bFound = 0; + + for(nCnt = 2; nCnt < nNumArgs; nCnt++) + { // Cycle through Values + if( strcmp(cvCVar->string, Cmd_Argv(cmd, nCnt) ) == 0) + { // Current Value Located; Increment to Next + if( (nCnt + 1) == nNumArgs) + // Max Value Reached; Reset + Cvar_SetQuick(cvCVar, Cmd_Argv(cmd, 2) ); + else + // Next Value + Cvar_SetQuick(cvCVar, Cmd_Argv(cmd, nCnt + 1) ); + + // End Loop + nCnt = nNumArgs; + // Assign Found + bFound = 1; + } + } + if(!bFound) + // Value not Found; Reset to Original + Cvar_SetQuick(cvCVar, Cmd_Argv(cmd, 2) ); + } + + } + else + { // Invalid CVar + Con_Printf("ERROR : CVar '%s' not found\n", Cmd_Argv(cmd, 1) ); + } + } +} + /* =============== Cmd_Alias_f @@ -301,22 +991,23 @@ Cmd_Alias_f Creates a new command that executes a command string (possibly ; seperated) =============== */ -static void Cmd_Alias_f (void) +static void Cmd_Alias_f (cmd_state_t *cmd) { - cmdalias_t *a; - char cmd[1024]; + cmd_alias_t *a; + char line[MAX_INPUTLINE]; int i, c; const char *s; + size_t alloclen; - if (Cmd_Argc() == 1) + if (Cmd_Argc(cmd) == 1) { Con_Print("Current alias commands:\n"); - for (a = cmd_alias ; a ; a=a->next) - Con_Printf("%s : %s\n", a->name, a->value); + for (a = cmd->userdefined->alias ; a ; a=a->next) + Con_Printf("%s : %s", a->name, a->value); return; } - s = Cmd_Argv(1); + s = Cmd_Argv(cmd, 1); if (strlen(s) >= MAX_ALIAS_NAME) { Con_Print("Alias name is too long\n"); @@ -324,7 +1015,7 @@ static void Cmd_Alias_f (void) } // if the alias already exists, reuse it - for (a = cmd_alias ; a ; a=a->next) + for (a = cmd->userdefined->alias ; a ; a=a->next) { if (!strcmp(s, a->name)) { @@ -335,35 +1026,81 @@ static void Cmd_Alias_f (void) if (!a) { - cmdalias_t *prev, *current; + cmd_alias_t *prev, *current; - a = (cmdalias_t *)Z_Malloc (sizeof(cmdalias_t)); + a = (cmd_alias_t *)Z_Malloc (sizeof(cmd_alias_t)); strlcpy (a->name, s, sizeof (a->name)); // insert it at the right alphanumeric position - for( prev = NULL, current = cmd_alias ; current && strcmp( current->name, a->name ) < 0 ; prev = current, current = current->next ) + for( prev = NULL, current = cmd->userdefined->alias ; current && strcmp( current->name, a->name ) < 0 ; prev = current, current = current->next ) ; if( prev ) { prev->next = a; } else { - cmd_alias = a; + cmd->userdefined->alias = a; } a->next = current; } // 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++) + line[0] = 0; // start out with a null string + c = Cmd_Argc(cmd); + for (i=2 ; i < c ; i++) { - strlcat (cmd, Cmd_Argv(i), sizeof (cmd)); - if (i != c) - strlcat (cmd, " ", sizeof (cmd)); + if (i != 2) + strlcat (line, " ", sizeof (line)); + strlcat (line, Cmd_Argv(cmd, i), sizeof (line)); } - strlcat (cmd, "\n", sizeof (cmd)); + strlcat (line, "\n", sizeof (line)); - a->value = (char *)Z_Malloc (strlen (cmd) + 1); - strcpy (a->value, cmd); + alloclen = strlen (line) + 1; + if(alloclen >= 2) + line[alloclen - 2] = '\n'; // to make sure a newline is appended even if too long + a->value = (char *)Z_Malloc (alloclen); + memcpy (a->value, line, alloclen); +} + +/* +=============== +Cmd_UnAlias_f + +Remove existing aliases. +=============== +*/ +static void Cmd_UnAlias_f (cmd_state_t *cmd) +{ + cmd_alias_t *a, *p; + int i; + const char *s; + + if(Cmd_Argc(cmd) == 1) + { + Con_Print("unalias: Usage: unalias alias1 [alias2 ...]\n"); + return; + } + + for(i = 1; i < Cmd_Argc(cmd); ++i) + { + s = Cmd_Argv(cmd, i); + p = NULL; + for(a = cmd->userdefined->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->userdefined->alias) + cmd->userdefined->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); + } } /* @@ -374,114 +1111,368 @@ static void Cmd_Alias_f (void) ============================================================================= */ -typedef struct cmd_function_s +static const char *Cmd_GetDirectCvarValue(cmd_state_t *cmd, const char *varname, cmd_alias_t *alias, qbool *is_multiple) { - struct cmd_function_s *next; - const char *name; - xcommand_t function; -} cmd_function_t; + cvar_t *cvar; + long argno; + char *endptr; + static char vabuf[1024]; // cmd_mutex + if(is_multiple) + *is_multiple = false; -#define MAX_ARGS 80 + if(!varname || !*varname) + return NULL; -static int cmd_argc; -static const char *cmd_argv[MAX_ARGS]; -static const char *cmd_null_string = ""; -static const char *cmd_args = NULL; + if(alias) + { + if(!strcmp(varname, "*")) + { + if(is_multiple) + *is_multiple = true; + return Cmd_Args(cmd); + } + else if(!strcmp(varname, "#")) + { + return va(vabuf, sizeof(vabuf), "%d", Cmd_Argc(cmd)); + } + 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(cmd); + 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(cmd)) + return Cmd_Argv(cmd, argno); + } + } + } -cmd_source_t cmd_source; + if((cvar = Cvar_FindVar(cmd->cvars, varname, cmd->cvars_flagsmask)) && !(cvar->flags & CF_PRIVATE)) + return cvar->string; + return NULL; +} + +qbool Cmd_QuoteString(char *out, size_t outlen, const char *in, const char *quoteset, qbool putquotes) +{ + qbool quote_quot = !!strchr(quoteset, '"'); + qbool quote_backslash = !!strchr(quoteset, '\\'); + qbool 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(cmd_state_t *cmd, const char *var, size_t varlen, cmd_alias_t *alias) +{ + static char varname[MAX_INPUTLINE]; // cmd_mutex + static char varval[MAX_INPUTLINE]; // cmd_mutex + const char *varstr = NULL; + char *varfunc; + qbool required = false; + qbool 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(CON_WARN "Warning: Could not expand $ in alias %s\n", alias->name); + else + Con_Printf(CON_WARN "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, Cmd_GetDirectCvarValue(cmd, varname + 1, alias, NULL), alias, NULL); + else + { + qbool is_multiple = false; + // Exception: $* and $n- don't use the quoted form by default + varstr = Cmd_GetDirectCvarValue(cmd, varname, alias, &is_multiple); + if(is_multiple) + if(!varfunc) + varfunc = asis; + } + + if(!varstr) + { + if(required) + { + if(alias) + Con_Printf(CON_ERROR "Error: Could not expand $%s in alias %s\n", varname, alias->name); + else + Con_Printf(CON_ERROR "Error: Could not expand $%s\n", varname); + return NULL; + } + else if(optional) + { + return ""; + } + else + { + if(alias) + Con_Printf(CON_WARN "Warning: Could not expand $%s in alias %s\n", varname, alias->name); + else + Con_Printf(CON_WARN "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); -static cmd_function_t *cmd_functions; // possible commands to execute + 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 qbool Cmd_PreprocessString(cmd_state_t *cmd, const char *intext, char *outtext, unsigned maxoutlen, cmd_alias_t *alias ) { const char *in; - char *out; + size_t eat, varlen; unsigned outlen; - int inquote; + const char *val; - // HACK? + // don't crash if there's no room in the outtext buffer if( maxoutlen == 0 ) { - return; + return false; } maxoutlen--; // because of \0 in = intext; - out = outtext; outlen = 0; - inquote = 0; while( *in && outlen < maxoutlen ) { - if( *in == '$' && !inquote ) { - // read over the $ + if( *in == '$' ) { + // this is some kind of expansion, see what comes after the $ in++; - // $* is replaced with all formal parameters, $num is parsed as an argument (or as $num if there arent enough parameters), $bla becomes $bla and $$bla becomes $$bla - if( *in == '*' && alias ) { - const char *linein = Cmd_Args(); - // include all params - if (linein) { - while( *linein && outlen < maxoutlen ) { - *out++ = *linein++; - outlen++; - } - } - - 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 ) { - *out++ = *param++; - outlen++; - } - 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 ); - *out++ = '$'; - outlen++; + // The console does the following preprocessing: + // + // - $$ 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 == '$' ) { + val = "$"; + eat = 1; + } else if(*in == '{') { + varlen = strcspn(in + 1, "}"); + if(in[varlen + 1] == '}') + { + val = Cmd_GetCvarValue(cmd, in + 1, varlen, alias); + if(!val) + return false; + eat = varlen + 2; + } + else + { + // ran out of data? + val = NULL; + eat = varlen + 1; } } else { - cvar_t *cvar; - const char *tempin = in; - - COM_ParseTokenConsole( &tempin ); - cvar = Cvar_FindVar(&com_token[0]); - if (cvar) { - const char *cvarcontent = cvar->string; - while( *cvarcontent && outlen < maxoutlen ) { - *out++ = *cvarcontent++; - outlen++; - } - in = tempin; - } else if( com_token[0] == '$' ) { - // remove the first $ - char *pos = com_token; - while( *pos && outlen < maxoutlen ) { - *out++ = *pos++; - outlen++; - } - in = tempin; + varlen = strspn(in, "#*0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-"); + val = Cmd_GetCvarValue(cmd, in, varlen, alias); + if(!val) + return false; + eat = varlen; + } + 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; } } - } else { - if( *in == '"' ) { - inquote ^= 1; - } - *out++ = *in++; - outlen++; } + else + outtext[outlen++] = *in++; } - *out = 0; + outtext[outlen] = 0; + return true; } /* @@ -491,83 +1482,21 @@ Cmd_ExecuteAlias Called for aliases and fills in the alias into the cbuffer ============ */ -static void Cmd_ExecuteAlias (cmdalias_t *alias) +static void Cmd_ExecuteAlias (cmd_state_t *cmd, cmd_alias_t *alias) { - /* -#define ALIAS_BUFFER 1024 - static char buffer[ ALIAS_BUFFER + 2 ]; - const char *in; - char *out; - unsigned outlen; - int inquote; - - in = alias->value; - out = buffer; - outlen = 0; - inquote = 0; - - while( *in && outlen < ALIAS_BUFFER ) - { - if( *in == '"' ) - { - inquote ^= 1; - } - else if( *in == '$' && !inquote ) - { - // $* is replaced with all formal parameters, $num is parsed as an argument (or as $num if there arent enough parameters), $bla becomes $bla and $$bla becomes $$bla - // read over the $ - in++; - if( *in == '*' ) - { - const char *linein = Cmd_Args(); - // include all params - if (linein) { - while( *linein && outlen < ALIAS_BUFFER ) { - *out++ = *linein++; - outlen++; - } - } - - in++; - } else { - char *nexttoken; - int argnum; - - argnum = strtol( in, &nexttoken, 10 ); - - if( 0 < argnum && argnum < Cmd_Argc() ) - { - const char *param = Cmd_Argv( argnum ); - while( *param && outlen < ALIAS_BUFFER ) { - *out++ = *param++; - outlen++; - } - 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 ); - *out++ = '$'; - outlen++; - } - // not a number - else if( argnum == 0 ) - { - *out++ = '$'; - outlen++; - } - } - } else { - *out++ = *in++; - outlen++; - } - } - *out++ = '\n'; - *out++ = 0;*/ -#define ALIAS_BUFFER 1024 - static char buffer[ ALIAS_BUFFER + 2 ]; - Cmd_PreprocessString( alias->value, buffer, ALIAS_BUFFER, alias ); - Cbuf_AddText( buffer ); + static char buffer[ MAX_INPUTLINE ]; // cmd_mutex + static char buffer2[ MAX_INPUTLINE ]; // cmd_mutex + qbool ret = Cmd_PreprocessString( cmd, 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) + + // 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(cmd, buffer2); } /* @@ -579,37 +1508,139 @@ Cmd_List ======== */ -static void Cmd_List_f (void) +static void Cmd_List_f (cmd_state_t *cmd) { - cmd_function_t *cmd; + cmd_function_t *func; const char *partial; - int len, count; + size_t len; + int count; + qbool ispattern; - if (Cmd_Argc() > 1) + if (Cmd_Argc(cmd) > 1) { - partial = Cmd_Argv (1); - len = (int)strlen(partial); + partial = Cmd_Argv(cmd, 1); + len = strlen(partial); + ispattern = (strchr(partial, '*') || strchr(partial, '?')); } else { partial = NULL; len = 0; + ispattern = false; + } + + count = 0; + for (func = cmd->userdefined->qc_functions; func; func = func->next) + { + if (partial && (ispattern ? !matchpattern_with_separator(func->name, partial, false, "", false) : strncmp(partial, func->name, len))) + continue; + Con_Printf("%s : %s\n", func->name, func->description); + count++; + } + for (func = cmd->engine_functions; func; func = func->next) + { + if (partial && (ispattern ? !matchpattern_with_separator(func->name, partial, false, "", false) : strncmp(partial, func->name, len))) + continue; + Con_Printf("%s : %s\n", func->name, func->description); + count++; + } + + 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(cmd_state_t *cmd) +{ + cmd_function_t *func; + cvar_t *cvar; + cmd_alias_t *alias; + const char *partial; + int count; + qbool ispattern; + char vabuf[1024]; + + if (Cmd_Argc(cmd) > 1) + partial = Cmd_Args(cmd); + else + { + Con_Printf("usage: %s \n",Cmd_Argv(cmd, 0)); + return; } + ispattern = partial && (strchr(partial, '*') || strchr(partial, '?')); + if(!ispattern) + partial = va(vabuf, sizeof(vabuf), "*%s*", partial); + count = 0; - for (cmd = cmd_functions; cmd; cmd = cmd->next) + for (cvar = cmd->cvars->vars; cvar; cvar = cvar->next) + { + if (matchpattern_with_separator(cvar->name, partial, true, "", false) || + matchpattern_with_separator(cvar->description, partial, true, "", false)) + { + Con_Printf ("cvar "); + Cvar_PrintHelp(cvar, cvar->name, true); + count++; + } + for (char **cvar_alias = cvar->aliases; cvar_alias && *cvar_alias; cvar_alias++) + { + if (matchpattern_with_separator(*cvar_alias, partial, true, "", false)) + { + Con_Printf ("cvar "); + Cvar_PrintHelp(cvar, *cvar_alias, true); + count++; + } + } + } + for (func = cmd->userdefined->qc_functions; func; func = func->next) + { + if (!matchpattern_with_separator(func->name, partial, true, "", false)) + if (!matchpattern_with_separator(func->description, partial, true, "", false)) + continue; + Con_Printf("command ^2%s^7: %s\n", func->name, func->description); + count++; + } + for (func = cmd->engine_functions; func; func = func->next) + { + if (!matchpattern_with_separator(func->name, partial, true, "", false)) + if (!matchpattern_with_separator(func->description, partial, true, "", false)) + continue; + Con_Printf("command ^2%s^7: %s\n", func->name, func->description); + count++; + } + for (alias = cmd->userdefined->alias; alias; alias = alias->next) { - if (partial && strncmp(partial, cmd->name, len)) + // 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("%s\n", cmd->name); + 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" : ""); +} - Con_Printf("%i Command%s", count, (count > 1) ? "s" : ""); - if (partial) - Con_Printf(" beginning with \"%s\"", partial); +static cmd_state_t *Cmd_AddInterpreter(cmd_buf_t *cbuf, cvar_state_t *cvars, int cvars_flagsmask, int cmds_flagsmask, cmd_userdefined_t *userdefined) +{ + cmd_state_t *cmd = (cmd_state_t *)Mem_Alloc(tempmempool, sizeof(cmd_state_t)); + + cmd->mempool = Mem_AllocPool("commands", 0, NULL); + // space for commands and script files + cmd->cbuf = cbuf; + cmd->null_string = ""; - Con_Print("\n\n"); + cmd->cvars = cvars; + cmd->cvars_flagsmask = cvars_flagsmask; + cmd->cmd_flags = cmds_flagsmask; + cmd->userdefined = userdefined; + + return cmd; } /* @@ -617,31 +1648,73 @@ static void Cmd_List_f (void) Cmd_Init ============ */ -void Cmd_Init (void) -{ - cmd_mempool = Mem_AllocPool("commands", 0, NULL); - // space for commands and script files - cmd_text.data = cmd_text_buf; - cmd_text.maxsize = sizeof(cmd_text_buf); - cmd_text.cursize = 0; -} - -void Cmd_Init_Commands (void) +void Cmd_Init(void) { + cmd_buf_t *cbuf; + cbuf_mempool = Mem_AllocPool("Command buffer", 0, NULL); + cbuf = (cmd_buf_t *)Mem_Alloc(cbuf_mempool, sizeof(cmd_buf_t)); + cbuf->maxsize = 655360; + cbuf->lock = Thread_CreateMutex(); + cbuf->wait = false; + host.cbuf = cbuf; + + cbuf->start.prev = cbuf->start.next = &(cbuf->start); + cbuf->deferred.prev = cbuf->deferred.next = &(cbuf->deferred); + cbuf->free.prev = cbuf->free.next = &(cbuf->free); + + // FIXME: Get rid of cmd_iter_all eventually. This is just a hack to reduce the amount of work to make the interpreters dynamic. + cmd_iter_all = (cmd_iter_t *)Mem_Alloc(tempmempool, sizeof(cmd_iter_t) * 3); + + // local console + cmd_iter_all[0].cmd = cmd_local = Cmd_AddInterpreter(cbuf, &cvars_all, CF_CLIENT | CF_SERVER, CF_CLIENT | CF_CLIENT_FROM_SERVER | CF_SERVER_FROM_CLIENT, &cmd_userdefined_all); + cmd_local->Handle = Cmd_CL_Callback; + cmd_local->NotFound = NULL; + + // server commands received from clients have no reason to access cvars, cvar expansion seems perilous. + cmd_iter_all[1].cmd = cmd_serverfromclient = Cmd_AddInterpreter(cbuf, &cvars_null, 0, CF_SERVER_FROM_CLIENT | CF_USERINFO, &cmd_userdefined_null); + cmd_serverfromclient->Handle = Cmd_SV_Callback; + cmd_serverfromclient->NotFound = Cmd_SV_NotFound; + + cmd_iter_all[2].cmd = NULL; // // register our commands // - Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f); - Cmd_AddCommand ("exec",Cmd_Exec_f); - Cmd_AddCommand ("echo",Cmd_Echo_f); - Cmd_AddCommand ("alias",Cmd_Alias_f); - Cmd_AddCommand ("cmd", Cmd_ForwardToServer); - Cmd_AddCommand ("wait", Cmd_Wait_f); - Cmd_AddCommand ("cmdlist", Cmd_List_f); // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com - Cmd_AddCommand ("cvarlist", Cvar_List_f); // 2000-01-09 CmdList, CvarList commands - // By Matthias "Maddes" Buecher - Cmd_AddCommand ("set", Cvar_Set_f); - Cmd_AddCommand ("seta", Cvar_SetA_f); + // client-only commands + Cmd_AddCommand(CF_SHARED, "wait", Cmd_Wait_f, "make script execution wait for next rendered frame"); + Cmd_AddCommand(CF_CLIENT, "cprint", Cmd_Centerprint_f, "print something at the screen center"); + + // maintenance commands used for upkeep of cvars and saved configs + Cmd_AddCommand(CF_SHARED, "stuffcmds", Cmd_StuffCmds_f, "execute commandline parameters (must be present in quake.rc script)"); + Cmd_AddCommand(CF_SHARED, "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(CF_SHARED, "cvar_resettodefaults_all", Cvar_ResetToDefaults_All_f, "sets all cvars to their locked default values"); + Cmd_AddCommand(CF_SHARED, "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(CF_SHARED, "cvar_resettodefaults_saveonly", Cvar_ResetToDefaults_SaveOnly_f, "sets all saved cvars to their locked default values (variables that will be saved to config.cfg)"); + + // general console commands used in multiple environments + Cmd_AddCommand(CF_SHARED, "exec", Cmd_Exec_f, "execute a script file"); + Cmd_AddCommand(CF_SHARED, "echo",Cmd_Echo_f, "print a message to the console (useful in scripts)"); + Cmd_AddCommand(CF_SHARED, "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(CF_SHARED, "unalias",Cmd_UnAlias_f, "remove an alias"); + Cmd_AddCommand(CF_SHARED, "set", Cvar_Set_f, "create or change the value of a console variable"); + Cmd_AddCommand(CF_SHARED, "seta", Cvar_SetA_f, "create or change the value of a console variable that will be saved to config.cfg"); + Cmd_AddCommand(CF_SHARED, "unset", Cvar_Del_f, "delete a cvar (does not work for static ones like _cl_name, or read-only ones)"); + +#ifdef FILLALLCVARSWITHRUBBISH + Cmd_AddCommand(CF_SHARED, "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(CF_SHARED, "cmdlist", Cmd_List_f, "lists all console commands beginning with the specified prefix or matching the specified wildcard pattern"); + Cmd_AddCommand(CF_SHARED, "cvarlist", Cvar_List_f, "lists all console variables beginning with the specified prefix or matching the specified wildcard pattern"); + Cmd_AddCommand(CF_SHARED, "apropos", Cmd_Apropos_f, "lists all console variables/commands/aliases containing the specified string in the name or description"); + Cmd_AddCommand(CF_SHARED, "find", Cmd_Apropos_f, "lists all console variables/commands/aliases containing the specified string in the name or description"); + + Cmd_AddCommand(CF_SHARED, "defer", Cmd_Defer_f, "execute a command in the future"); + + // DRESK - 5/14/06 + // Support Doom3-style Toggle Command + Cmd_AddCommand(CF_SHARED | CF_CLIENT_FROM_SERVER, "toggle", Cmd_Toggle_f, "toggles a console variable's values (use for more info)"); } /* @@ -651,7 +1724,19 @@ Cmd_Shutdown */ void Cmd_Shutdown(void) { - Mem_FreePool(&cmd_mempool); + cmd_iter_t *cmd_iter; + for (cmd_iter = cmd_iter_all; cmd_iter->cmd; cmd_iter++) + { + cmd_state_t *cmd = cmd_iter->cmd; + + if (cmd->cbuf->lock) + { + // we usually have this locked when we get here from Host_Quit_f + Cbuf_Unlock(cmd->cbuf); + } + + Mem_FreePool(&cmd->mempool); + } } /* @@ -659,9 +1744,9 @@ void Cmd_Shutdown(void) Cmd_Argc ============ */ -int Cmd_Argc (void) +int Cmd_Argc (cmd_state_t *cmd) { - return cmd_argc; + return cmd->argc; } /* @@ -669,11 +1754,11 @@ int Cmd_Argc (void) Cmd_Argv ============ */ -const char *Cmd_Argv (int arg) +const char *Cmd_Argv(cmd_state_t *cmd, int arg) { - if (arg >= cmd_argc ) - return cmd_null_string; - return cmd_argv[arg]; + if (arg >= cmd->argc ) + return cmd->null_string; + return cmd->argv[arg]; } /* @@ -681,12 +1766,11 @@ const char *Cmd_Argv (int arg) Cmd_Args ============ */ -const char *Cmd_Args (void) +const char *Cmd_Args (cmd_state_t *cmd) { - return cmd_args; + return cmd->args; } - /* ============ Cmd_TokenizeString @@ -695,17 +1779,18 @@ Parses the given string into command line tokens. ============ */ // AK: This function should only be called from ExcuteString because the current design is a bit of an hack -static void Cmd_TokenizeString (const char *text) +static void Cmd_TokenizeString (cmd_state_t *cmd, const char *text) { int l; - cmd_argc = 0; - cmd_args = NULL; + cmd->argc = 0; + cmd->args = NULL; + cmd->cmdline = NULL; 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: @@ -724,24 +1809,26 @@ static void Cmd_TokenizeString (const char *text) if (!*text) return; - if (cmd_argc == 1) - cmd_args = text; + if(!cmd->argc) + cmd->cmdline = text; + if (cmd->argc == 1) + cmd->args = text; - if (!COM_ParseTokenConsole(&text)) + if (!COM_ParseToken_Console(&text)) return; - if (cmd_argc < MAX_ARGS) + if (cmd->argc < MAX_ARGS) { l = (int)strlen(com_token) + 1; - if (cmd_tokenizebufferpos + l > CMD_TOKENIZELENGTH) + if (cmd->cbuf->tokenizebufferpos + l > CMD_TOKENIZELENGTH) { - Con_Printf("Cmd_TokenizeString: ran out of %i character buffer space for command arguements\n", CMD_TOKENIZELENGTH); + Con_Printf("Cmd_TokenizeString: ran out of %i character buffer space for command arguments\n", CMD_TOKENIZELENGTH); break; } - strcpy (cmd_tokenizebuffer + cmd_tokenizebufferpos, com_token); - cmd_argv[cmd_argc] = cmd_tokenizebuffer + cmd_tokenizebufferpos; - cmd_tokenizebufferpos += l; - cmd_argc++; + memcpy (cmd->cbuf->tokenizebuffer + cmd->cbuf->tokenizebufferpos, com_token, l); + cmd->argv[cmd->argc] = cmd->cbuf->tokenizebuffer + cmd->cbuf->tokenizebufferpos; + cmd->cbuf->tokenizebufferpos += l; + cmd->argc++; } } } @@ -752,42 +1839,89 @@ static void Cmd_TokenizeString (const char *text) Cmd_AddCommand ============ */ -void Cmd_AddCommand (const char *cmd_name, xcommand_t function) +void Cmd_AddCommand(int flags, const char *cmd_name, xcommand_t function, const char *description) { - cmd_function_t *cmd; + cmd_function_t *func; cmd_function_t *prev, *current; + cmd_state_t *cmd; + int i; -// fail if the command is a variable name - if (Cvar_FindVar( cmd_name )) - { - Con_Printf("Cmd_AddCommand: %s already defined as a var\n", cmd_name); - return; - } - -// fail if the command already exists - for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + for (i = 0; i < 2; i++) { - if (!strcmp (cmd_name, cmd->name)) + cmd = cmd_iter_all[i].cmd; + if (flags & cmd->cmd_flags) { - Con_Printf("Cmd_AddCommand: %s already defined\n", cmd_name); - return; - } - } + // fail if the command is a variable name + if (Cvar_FindVar(cmd->cvars, cmd_name, ~0)) + { + Con_Printf("Cmd_AddCommand: %s already defined as a var\n", cmd_name); + return; + } + + if (function) + { + // fail if the command already exists in this interpreter + for (func = cmd->engine_functions; func; func = func->next) + { + if (!strcmp(cmd_name, func->name)) + { + Con_Printf("Cmd_AddCommand: %s already defined\n", cmd_name); + continue; + } + } + + func = (cmd_function_t *)Mem_Alloc(cmd->mempool, sizeof(cmd_function_t)); + func->flags = flags; + func->name = cmd_name; + func->function = function; + func->description = description; + func->next = cmd->engine_functions; + + // insert it at the right alphanumeric position + for (prev = NULL, current = cmd->engine_functions; current && strcmp(current->name, func->name) < 0; prev = current, current = current->next) + ; + if (prev) { + prev->next = func; + } + else { + cmd->engine_functions = func; + } + func->next = current; + } + else + { + // mark qcfunc if the function already exists in the qc_functions list + for (func = cmd->userdefined->qc_functions; func; func = func->next) + { + if (!strcmp(cmd_name, func->name)) + { + func->qcfunc = true; //[515]: csqc + continue; + } + } + - cmd = (cmd_function_t *)Mem_Alloc(cmd_mempool, sizeof(cmd_function_t)); - cmd->name = cmd_name; - cmd->function = function; - cmd->next = cmd_functions; + func = (cmd_function_t *)Mem_Alloc(cmd->mempool, sizeof(cmd_function_t)); + func->flags = flags; + func->name = cmd_name; + func->function = function; + func->description = description; + func->qcfunc = true; //[515]: csqc + func->next = cmd->userdefined->qc_functions; -// insert it at the right alphanumeric position - for( prev = NULL, current = cmd_functions ; current && strcmp( current->name, cmd->name ) < 0 ; prev = current, current = current->next ) - ; - if( prev ) { - prev->next = cmd; - } else { - cmd_functions = cmd; + // insert it at the right alphanumeric position + for (prev = NULL, current = cmd->userdefined->qc_functions; current && strcmp(current->name, func->name) < 0; prev = current, current = current->next) + ; + if (prev) { + prev->next = func; + } + else { + cmd->userdefined->qc_functions = func; + } + func->next = current; + } + } } - cmd->next = current; } /* @@ -795,12 +1929,16 @@ void Cmd_AddCommand (const char *cmd_name, xcommand_t function) Cmd_Exists ============ */ -qboolean Cmd_Exists (const char *cmd_name) +qbool Cmd_Exists (cmd_state_t *cmd, const char *cmd_name) { - cmd_function_t *cmd; + cmd_function_t *func; + + for (func = cmd->userdefined->qc_functions; func; func = func->next) + if (!strcmp(cmd_name, func->name)) + return true; - for (cmd=cmd_functions ; cmd ; cmd=cmd->next) - if (!strcmp (cmd_name,cmd->name)) + for (func=cmd->engine_functions ; func ; func=func->next) + if (!strcmp (cmd_name,func->name)) return true; return false; @@ -812,9 +1950,9 @@ qboolean Cmd_Exists (const char *cmd_name) Cmd_CompleteCommand ============ */ -const char *Cmd_CompleteCommand (const char *partial) +const char *Cmd_CompleteCommand (cmd_state_t *cmd, const char *partial) { - cmd_function_t *cmd; + cmd_function_t *func; size_t len; len = strlen(partial); @@ -823,9 +1961,13 @@ const char *Cmd_CompleteCommand (const char *partial) return NULL; // check functions - for (cmd = cmd_functions; cmd; cmd = cmd->next) - if (!strncmp(partial, cmd->name, len)) - return cmd->name; + for (func = cmd->userdefined->qc_functions; func; func = func->next) + if (!strncasecmp(partial, func->name, len)) + return func->name; + + for (func = cmd->engine_functions; func; func = func->next) + if (!strncasecmp(partial, func->name, len)) + return func->name; return NULL; } @@ -839,9 +1981,9 @@ const char *Cmd_CompleteCommand (const char *partial) Thanks to taniwha */ -int Cmd_CompleteCountPossible (const char *partial) +int Cmd_CompleteCountPossible (cmd_state_t *cmd, const char *partial) { - cmd_function_t *cmd; + cmd_function_t *func; size_t len; int h; @@ -852,8 +1994,12 @@ int Cmd_CompleteCountPossible (const char *partial) return 0; // Loop through the command list and count all partial matches - for (cmd = cmd_functions; cmd; cmd = cmd->next) - if (!strncasecmp(partial, cmd->name, len)) + for (func = cmd->userdefined->qc_functions; func; func = func->next) + if (!strncasecmp(partial, func->name, len)) + h++; + + for (func = cmd->engine_functions; func; func = func->next) + if (!strncasecmp(partial, func->name, len)) h++; return h; @@ -868,25 +2014,42 @@ int Cmd_CompleteCountPossible (const char *partial) Thanks to taniwha */ -const char **Cmd_CompleteBuildList (const char *partial) +const char **Cmd_CompleteBuildList (cmd_state_t *cmd, const char *partial) { - cmd_function_t *cmd; + cmd_function_t *func; size_t len = 0; size_t bpos = 0; - size_t sizeofbuf = (Cmd_CompleteCountPossible (partial) + 1) * sizeof (const char *); + size_t sizeofbuf = (Cmd_CompleteCountPossible (cmd, partial) + 1) * sizeof (const char *); const char **buf; len = strlen(partial); buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *)); - // Loop through the alias list and print all matches - for (cmd = cmd_functions; cmd; cmd = cmd->next) - if (!strncasecmp(partial, cmd->name, len)) - buf[bpos++] = cmd->name; + // Loop through the functions lists and print all matches + for (func = cmd->userdefined->qc_functions; func; func = func->next) + if (!strncasecmp(partial, func->name, len)) + buf[bpos++] = func->name; + for (func = cmd->engine_functions; func; func = func->next) + if (!strncasecmp(partial, func->name, len)) + buf[bpos++] = func->name; buf[bpos] = NULL; return buf; } +// written by LadyHavoc +void Cmd_CompleteCommandPrint (cmd_state_t *cmd, const char *partial) +{ + cmd_function_t *func; + size_t len = strlen(partial); + // Loop through the command list and print all matches + for (func = cmd->userdefined->qc_functions; func; func = func->next) + if (!strncasecmp(partial, func->name, len)) + Con_Printf("^2%s^7: %s\n", func->name, func->description); + for (func = cmd->engine_functions; func; func = func->next) + if (!strncasecmp(partial, func->name, len)) + Con_Printf("^2%s^7: %s\n", func->name, func->description); +} + /* Cmd_CompleteAlias @@ -896,9 +2059,9 @@ const char **Cmd_CompleteBuildList (const char *partial) Thanks to taniwha */ -const char *Cmd_CompleteAlias (const char *partial) +const char *Cmd_CompleteAlias (cmd_state_t *cmd, const char *partial) { - cmdalias_t *alias; + cmd_alias_t *alias; size_t len; len = strlen(partial); @@ -907,13 +2070,25 @@ const char *Cmd_CompleteAlias (const char *partial) return NULL; // Check functions - for (alias = cmd_alias; alias; alias = alias->next) + for (alias = cmd->userdefined->alias; alias; alias = alias->next) if (!strncasecmp(partial, alias->name, len)) return alias->name; return NULL; } +// written by LadyHavoc +void Cmd_CompleteAliasPrint (cmd_state_t *cmd, const char *partial) +{ + cmd_alias_t *alias; + size_t len = strlen(partial); + // Loop through the alias list and print all matches + for (alias = cmd->userdefined->alias; alias; alias = alias->next) + if (!strncasecmp(partial, alias->name, len)) + Con_Printf("^5%s^7: %s", alias->name, alias->value); +} + + /* Cmd_CompleteAliasCountPossible @@ -923,9 +2098,9 @@ const char *Cmd_CompleteAlias (const char *partial) Thanks to taniwha */ -int Cmd_CompleteAliasCountPossible (const char *partial) +int Cmd_CompleteAliasCountPossible (cmd_state_t *cmd, const char *partial) { - cmdalias_t *alias; + cmd_alias_t *alias; size_t len; int h; @@ -937,7 +2112,7 @@ int Cmd_CompleteAliasCountPossible (const char *partial) return 0; // Loop through the command list and count all partial matches - for (alias = cmd_alias; alias; alias = alias->next) + for (alias = cmd->userdefined->alias; alias; alias = alias->next) if (!strncasecmp(partial, alias->name, len)) h++; @@ -953,18 +2128,18 @@ int Cmd_CompleteAliasCountPossible (const char *partial) Thanks to taniwha */ -const char **Cmd_CompleteAliasBuildList (const char *partial) +const char **Cmd_CompleteAliasBuildList (cmd_state_t *cmd, const char *partial) { - cmdalias_t *alias; + cmd_alias_t *alias; size_t len = 0; size_t bpos = 0; - size_t sizeofbuf = (Cmd_CompleteAliasCountPossible (partial) + 1) * sizeof (const char *); + size_t sizeofbuf = (Cmd_CompleteAliasCountPossible (cmd, partial) + 1) * sizeof (const char *); const char **buf; len = strlen(partial); buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *)); // Loop through the alias list and print all matches - for (alias = cmd_alias; alias; alias = alias->next) + for (alias = cmd->userdefined->alias; alias; alias = alias->next) if (!strncasecmp(partial, alias->name, len)) buf[bpos++] = alias->name; @@ -972,6 +2147,92 @@ const char **Cmd_CompleteAliasBuildList (const char *partial) return buf; } +// TODO: Make this more generic? +void Cmd_ClearCSQCCommands (cmd_state_t *cmd) +{ + cmd_function_t *func; + cmd_function_t **next = &cmd->userdefined->qc_functions; + + while(*next) + { + func = *next; + *next = func->next; + Z_Free(func); + } +} + +extern cvar_t sv_cheats; + +/* + * Cloudwalk FIXME: This idea sounded great in my head but... + * How do we handle commands that can be received by the client, + * but which the server can also execute locally? + * + * If we create a callback where the engine will forward to server + * but try to execute the command locally if it's dedicated, + * we're back to intermixing client and server code which I'm + * trying to avoid. There's no other way I can think of to + * implement that behavior that doesn't involve an #ifdef, or + * making a mess of hooks. + */ +qbool Cmd_Callback(cmd_state_t *cmd, cmd_function_t *func, const char *text, cmd_source_t src) +{ + if (func->function) + func->function(cmd); + else + Con_Printf("Command \"%s\" can not be executed\n", Cmd_Argv(cmd, 0)); + return true; +} + +qbool Cmd_CL_Callback(cmd_state_t *cmd, cmd_function_t *func, const char *text, cmd_source_t src) +{ + // TODO: Assign these functions to QC commands directly? + if(func->qcfunc) + { + if(((func->flags & CF_CLIENT) && CL_VM_ConsoleCommand(text)) || + ((func->flags & CF_SERVER) && SV_VM_ConsoleCommand(text))) + return true; + } + if (func->flags & CF_SERVER_FROM_CLIENT) + { + if(host_isclient.integer) + { + CL_ForwardToServer_f(cmd); + return true; + } + else if(!(func->flags & CF_SERVER)) + { + Con_Printf("Cannot execute client commands from a dedicated server console.\n"); + return true; + } + } + return Cmd_Callback(cmd, func, text, src); +} + +qbool Cmd_SV_Callback(cmd_state_t *cmd, cmd_function_t *func, const char *text, cmd_source_t src) +{ + if(func->qcfunc && (func->flags & CF_SERVER)) + return SV_VM_ConsoleCommand(text); + else if (src == src_client) + { + if((func->flags & CF_CHEAT) && !sv_cheats.integer) + SV_ClientPrintf("No cheats allowed. The server must have sv_cheats set to 1\n"); + else + func->function(cmd); + return true; + } + return false; +} + +qbool Cmd_SV_NotFound(cmd_state_t *cmd, cmd_function_t *func, const char *text, cmd_source_t src) +{ + if (cmd->source == src_client) + { + Con_Printf("Client \"%s\" tried to execute \"%s\"\n", host_client->name, text); + return true; + } + return false; +} /* ============ Cmd_ExecuteString @@ -980,108 +2241,66 @@ 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 (cmd_state_t *cmd, const char *text, cmd_source_t src, qbool lockmutex) { -#define EXECUTESTRING_BUFFER 4096 - static char buffer[ EXECUTESTRING_BUFFER ]; int oldpos; - cmd_function_t *cmd; - cmdalias_t *a; + cmd_function_t *func; + cmd_alias_t *a; + if (lockmutex) + Cbuf_Lock(cmd->cbuf); + oldpos = cmd->cbuf->tokenizebufferpos; + cmd->source = src; - oldpos = cmd_tokenizebufferpos; - cmd_source = src; - - Cmd_PreprocessString( text, buffer, EXECUTESTRING_BUFFER, NULL ); - Cmd_TokenizeString (buffer); + Cmd_TokenizeString (cmd, text); // execute the command line - if (!Cmd_Argc()) - { - cmd_tokenizebufferpos = oldpos; - return; // no tokens - } + if (!Cmd_Argc(cmd)) + goto done; // no tokens // check functions - for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + for (func = cmd->userdefined->qc_functions; func; func = func->next) { - if (!strcasecmp (cmd_argv[0],cmd->name)) + if (!strcasecmp(cmd->argv[0], func->name)) { - cmd->function (); - cmd_tokenizebufferpos = oldpos; - return; + if(cmd->Handle(cmd, func, text, src)) + goto done; } } -// check alias - for (a=cmd_alias ; a ; a=a->next) + for (func = cmd->engine_functions; func; func=func->next) { - if (!strcasecmp (cmd_argv[0], a->name)) + if (!strcasecmp (cmd->argv[0], func->name)) { - Cmd_ExecuteAlias(a); - cmd_tokenizebufferpos = oldpos; - return; + if(cmd->Handle(cmd, func, text, src)) + goto done; } } -// check cvars - if (!Cvar_Command () && host_framecount > 0) - Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0)); - - cmd_tokenizebufferpos = oldpos; -} - - -/* -=================== -Cmd_ForwardStringToServer - -Sends an entire command string over to the server, unprocessed -=================== -*/ -void Cmd_ForwardStringToServer (const char *s) -{ - if (cls.state != ca_connected) + // if it's a client command and no command was found, say so. + if(cmd->NotFound) { - Con_Printf("Can't \"%s\", not connected\n", s); - return; + if(cmd->NotFound(cmd, func, text, src)) + goto done; } - if (cls.demoplayback) - return; // not really connected - - // LordHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my - // attention, it has been eradicated from here, its only (former) use in - // all of darkplaces. - MSG_WriteByte(&cls.message, clc_stringcmd); - SZ_Write(&cls.message, (const unsigned char *)s, (int)strlen(s) + 1); -} - -/* -=================== -Cmd_ForwardToServer - -Sends the entire command line over to the server -=================== -*/ -void Cmd_ForwardToServer (void) -{ - const char *s; - if (!strcasecmp(Cmd_Argv(0), "cmd")) - { - // we want to strip off "cmd", so just send the args - s = Cmd_Argc() > 1 ? Cmd_Args() : ""; - } - else +// check alias + for (a=cmd->userdefined->alias ; a ; a=a->next) { - // 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() : ""); + if (!strcasecmp (cmd->argv[0], a->name)) + { + Cmd_ExecuteAlias(cmd, a); + goto done; + } } - // don't send an empty forward message if the user tries "cmd" by itself - if (!s || !*s) - return; - Cmd_ForwardStringToServer(s); -} +// check cvars + if (!Cvar_Command(cmd) && host.framecount > 0) + Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(cmd, 0)); +done: + cmd->cbuf->tokenizebufferpos = oldpos; + if (lockmutex) + Cbuf_Unlock(cmd->cbuf); +} /* ================ @@ -1092,7 +2311,7 @@ where the given parameter apears, or 0 if not present ================ */ -int Cmd_CheckParm (const char *parm) +int Cmd_CheckParm (cmd_state_t *cmd, const char *parm) { int i; @@ -1102,10 +2321,96 @@ int Cmd_CheckParm (const char *parm) return 0; } - for (i = 1; i < Cmd_Argc (); i++) - if (!strcasecmp (parm, Cmd_Argv (i))) + for (i = 1; i < Cmd_Argc (cmd); i++) + if (!strcasecmp (parm, Cmd_Argv(cmd, i))) return i; return 0; } + + +void Cmd_SaveInitState(void) +{ + cmd_iter_t *cmd_iter; + for (cmd_iter = cmd_iter_all; cmd_iter->cmd; cmd_iter++) + { + cmd_state_t *cmd = cmd_iter->cmd; + cmd_function_t *f; + cmd_alias_t *a; + for (f = cmd->userdefined->qc_functions; f; f = f->next) + f->initstate = true; + for (f = cmd->engine_functions; f; f = f->next) + f->initstate = true; + for (a = cmd->userdefined->alias; a; a = a->next) + { + a->initstate = true; + a->initialvalue = Mem_strdup(zonemempool, a->value); + } + } + Cvar_SaveInitState(&cvars_all); +} + +void Cmd_RestoreInitState(void) +{ + cmd_iter_t *cmd_iter; + for (cmd_iter = cmd_iter_all; cmd_iter->cmd; cmd_iter++) + { + cmd_state_t *cmd = cmd_iter->cmd; + cmd_function_t *f, **fp; + cmd_alias_t *a, **ap; + for (fp = &cmd->userdefined->qc_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 (fp = &cmd->engine_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->userdefined->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(&cvars_all); +} + +void Cmd_NoOperation_f(cmd_state_t *cmd) +{ +}