// cmd.c -- Quake script command processing module #include "quakedef.h" void Cmd_ForwardToServer(void); typedef struct cmdalias_s { struct cmdalias_s *next; char *name; char *value; } cmdalias_t; cmdalias_t *cmd_alias; int trashtest; int *trashspot; qboolean cmd_wait; //============================================================================= /* ============ Cmd_Wait_f Causes execution of the remainder of the command buffer to be delayed until next frame. This allows commands like: bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2" ============ */ void Cmd_Wait_f(void) { cmd_wait = true; } /* ============================================================================= COMMAND BUFFER ============================================================================= */ sizebuf_t cmd_text; /* ============ Cbuf_Init ============ */ void Cbuf_Init(void) { SZ_Alloc(&cmd_text, 8192); // space for commands and script files } /* ============ Cbuf_AddText Adds command text at the end of the buffer ============ */ void Cbuf_AddText(char *text) { int l; l = Q_strlen(text); if (cmd_text.cursize + l >= cmd_text.maxsize) { Con_Printf("Cbuf_AddText: overflow\n"); return; } SZ_Write(&cmd_text, text, Q_strlen(text)); } /* ============ 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(char *text) { char *temp; int templen; // copy off any commands still remaining in the exec buffer templen = cmd_text.cursize; if (templen) { temp = Z_Malloc(templen); Q_memcpy(temp, cmd_text.data, templen); SZ_Clear(&cmd_text); } else temp = NULL; // shut up compiler // add the entire text of the file Cbuf_AddText(text); // add the copied off data if (templen) { SZ_Write(&cmd_text, temp, templen); Z_Free(temp); } } /* ============ Cbuf_Execute ============ */ void Cbuf_Execute(void) { int i; char *text; char line[1024]; int quotes; while (cmd_text.cursize) { // find a \n or ; line break text = (char *) cmd_text.data; quotes = 0; for (i = 0; i < cmd_text.cursize; i++) { if (text[i] == '"') quotes++; if (!(quotes & 1) && text[i] == ';') break; // don't break if inside a quoted string if (text[i] == '\n') break; } 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; Q_memcpy(text, text + i, cmd_text.cursize); } // execute the command line Cmd_ExecuteString(line, src_command); if (cmd_wait) { // skip out while text still remains in buffer, leaving it // for next frame cmd_wait = false; break; } } } /* ============================================================================== SCRIPT COMMANDS ============================================================================== */ /* =============== Cmd_StuffCmds_f Adds command line parameters as script statements Commands lead with a +, and continue until a - or another + quake +prog jctest.qp +cmd amlev1 quake -nosound +cmd amlev1 =============== */ void Cmd_StuffCmds_f(void) { int i, j; int s; char *text, *build, c; if (Cmd_Argc() != 1) { Con_Printf("stuffcmds : execute command line parameters\n"); return; } // build the combined string to parse from s = 0; for (i = 1; i < com_argc; i++) { if (!com_argv[i]) continue; // NEXTSTEP nulls out -NXHost s += Q_strlen(com_argv[i]) + 1; } if (!s) return; text = Z_Malloc(s + 1); text[0] = 0; for (i = 1; i < com_argc; i++) { if (!com_argv[i]) continue; // NEXTSTEP nulls out -NXHost Q_strcat(text, com_argv[i]); if (i != com_argc - 1) Q_strcat(text, " "); } // pull out the commands build = Z_Malloc(s + 1); build[0] = 0; for (i = 0; i < s - 1; i++) { if (text[i] == '+') { i++; for (j = i; (text[j] != '+') && (text[j] != '-') && (text[j] != 0); j++); c = text[j]; text[j] = 0; Q_strcat(build, text + i); Q_strcat(build, "\n"); text[j] = c; i = j - 1; } } if (build[0]) Cbuf_InsertText(build); Z_Free(text); Z_Free(build); } /* =============== Cmd_Exec_f =============== */ void Cmd_Exec_f(void) { char *f; int mark; if (Cmd_Argc() != 2) { Con_Printf("exec : execute a script file\n"); return; } mark = Hunk_LowMark(); f = (char *) COM_LoadHunkFile(Cmd_Argv(1)); if (!f) { Con_Printf("couldn't exec %s\n", Cmd_Argv(1)); return; } Con_Printf("execing %s\n", Cmd_Argv(1)); Cbuf_InsertText(f); Hunk_FreeToLowMark(mark); } /* =============== Cmd_Echo_f Just prints the rest of the line to the console =============== */ void Cmd_Echo_f(void) { int i; for (i = 1; i < Cmd_Argc(); i++) Con_Printf("%s ", Cmd_Argv(i)); Con_Printf("\n"); } /* =============== Cmd_Alias_f Creates a new command that executes a command string (possibly ; seperated) =============== */ char *CopyString(char *in) { char *out; out = Z_Malloc(strlen(in) + 1); strcpy(out, in); return out; } void Cmd_Alias_f(void) { cmdalias_t *a; char cmd[1024]; int i, c; a = Z_Malloc(sizeof(cmdalias_t)); a->next = cmd_alias; cmd_alias = a; a->name = CopyString(Cmd_Argv(1)); // 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++) { strcat(cmd, Cmd_Argv(i)); if (i != c) strcat(cmd, " "); } strcat(cmd, "\n"); a->value = CopyString(cmd); } /* ============================================================================= COMMAND EXECUTION ============================================================================= */ typedef struct cmd_function_s { struct cmd_function_s *next; char *name; xcommand_t function; } cmd_function_t; #define MAX_ARGS 80 static int cmd_argc; static char *cmd_argv[MAX_ARGS]; static char *cmd_null_string = ""; static char *cmd_args = NULL; cmd_source_t cmd_source; static cmd_function_t *cmd_functions; // possible commands to execute /* ============ Cmd_Init ============ */ void Cmd_Init(void) { // // 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_Argc ============ */ int Cmd_Argc(void) { return cmd_argc; } /* ============ Cmd_Argv ============ */ char *Cmd_Argv(int arg) { if ((unsigned) arg >= cmd_argc) return cmd_null_string; return cmd_argv[arg]; } /* ============ Cmd_Args ============ */ char *Cmd_Args(void) { return cmd_args; } /* ============ Cmd_TokenizeString Parses the given string into command line tokens. ============ */ void Cmd_TokenizeString(char *text) { int i; // clear the args from the last string for (i = 0; i < cmd_argc; i++) Z_Free(cmd_argv[i]); cmd_argc = 0; cmd_args = NULL; while (1) { // skip whitespace up to a /n while (*text && *text <= ' ' && *text != '\n') { text++; } if (*text == '\n') { // a newline seperates commands in the buffer text++; break; } if (!*text) return; if (cmd_argc == 1) cmd_args = text; text = COM_Parse(text); if (!text) return; if (cmd_argc < MAX_ARGS) { cmd_argv[cmd_argc] = Z_Malloc(Q_strlen(com_token) + 1); Q_strcpy(cmd_argv[cmd_argc], com_token); cmd_argc++; } } } /* ============ Cmd_AddCommand ============ */ void Cmd_AddCommand(char *cmd_name, xcommand_t function) { cmd_function_t *cmd; if (host_initialized) // because hunk allocation would get stomped Sys_Error("Cmd_AddCommand after host_initialized"); // fail if the command is a variable name if (Cvar_VariableString(cmd_name)[0]) { 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) { if (!Q_strcmp(cmd_name, cmd->name)) { Con_Printf("Cmd_AddCommand: %s already defined\n", cmd_name); return; } } cmd = Hunk_Alloc(sizeof(cmd_function_t)); cmd->name = cmd_name; cmd->function = function; cmd->next = cmd_functions; cmd_functions = cmd; } /* ============ Cmd_Exists ============ */ qboolean Cmd_Exists(char *cmd_name) { cmd_function_t *cmd; for (cmd = cmd_functions; cmd; cmd = cmd->next) { if (!Q_strcmp(cmd_name, cmd->name)) return true; } return false; } /* ============ Cmd_CompleteCommand ============ */ char *Cmd_CompleteCommand(char *partial) { cmd_function_t *cmd; int len; len = Q_strlen(partial); if (!len) return NULL; // check functions for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!Q_strncmp(partial, cmd->name, len)) return cmd->name; return NULL; } /* ============ Cmd_ExecuteString A complete command line has been parsed, so try to execute it FIXME: lookupnoadd the token to speed search? ============ */ void Cmd_ExecuteString(char *text, cmd_source_t src) { cmd_function_t *cmd; cmdalias_t *a; cmd_source = src; Cmd_TokenizeString(text); // execute the command line if (!Cmd_Argc()) return; // no tokens // check functions for (cmd = cmd_functions; cmd; cmd = cmd->next) { if (!Q_strcasecmp(cmd_argv[0], cmd->name)) { cmd->function(); return; } } // check alias for (a = cmd_alias; a; a = a->next) { if (!Q_strcasecmp(cmd_argv[0], a->name)) { Cbuf_InsertText(a->value); return; } } // check cvars if (!Cvar_Command()) Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0)); } /* =================== Cmd_ForwardToServer Sends the entire command line over to the server =================== */ void Cmd_ForwardToServer(void) { if (cls.state != ca_connected) { Con_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0)); return; } if (cls.demoplayback) return; // not really connected MSG_WriteByte(&cls.message, clc_stringcmd); SZ_Print(&cls.message, Cmd_Argv(0)); if (Cmd_Argc() > 1) { SZ_Print(&cls.message, " "); SZ_Print(&cls.message, Cmd_Args()); } } /* ================ Cmd_CheckParm Returns the position (1 to argc-1) in the command's argument list where the given parameter apears, or 0 if not present ================ */ int Cmd_CheckParm(char *parm) { int i; if (!parm) Sys_Error("Cmd_CheckParm: NULL"); for (i = 1; i < Cmd_Argc(); i++) if (!Q_strcasecmp(parm, Cmd_Argv(i))) return i; return 0; }