/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cmd.c -- Quake script command processing module #include "quakedef.h" #ifdef _WIN32 #include "winquake.h" #endif // joe: ReadDir()'s stuff #ifndef _WIN32 #include #include #include #endif #define MAX_ALIAS_NAME 32 typedef struct cmdalias_s { struct cmdalias_s *next; char name[MAX_ALIAS_NAME]; char *value; } cmdalias_t; cmdalias_t *cmd_alias; int trashtest, *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 = strlen (text); if (cmd_text.cursize + l >= cmd_text.maxsize) { Con_Printf ("Cbuf_AddText: overflow\n"); return; } SZ_Write (&cmd_text, text, 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 if ((templen = cmd_text.cursize)) { temp = Z_Malloc (templen); 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, quotes; char *text, line[1024]; while (cmd_text.cursize) { // find a \n or ; line break text = (char *)cmd_text.data; quotes = 0; for (i=0 ; i : execute a script file\n"); return; } Q_strncpyz (name, Cmd_Argv(1), sizeof(name)); mark = Hunk_LowMark (); if (!(f = (char *)COM_LoadHunkFile(name))) { char *p; p = COM_SkipPath (name); if (!strchr(p, '.')) { // no extension, so try the default (.cfg) strcat (name, ".cfg"); f = (char *)COM_LoadHunkFile (name); } if (!f) { Con_Printf ("couldn't exec %s\n", name); return; } } Con_Printf ("execing %s\n",name); 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 ; inext) Con_Printf ("%s : %s\n", a->name, a->value); return; } s = Cmd_Argv (1); if (strlen(s) >= MAX_ALIAS_NAME) { Con_Printf ("Alias name is too long\n"); return; } // if the alias already exists, reuse it for (a = cmd_alias ; a ; a = a->next) { if (!strcmp(s, a->name)) { Z_Free (a->value); break; } } if (!a) { a = Z_Malloc (sizeof(cmdalias_t)); a->next = cmd_alias; cmd_alias = a; } strcpy (a->name, s); // copy the rest of the command line cmd[0] = 0; // start out with a null string c = Cmd_Argc (); for (i=2 ; ivalue = CopyString (cmd); } // joe: legacy commands, from FuhQuake typedef struct legacycmd_s { char *oldname, *newname; struct legacycmd_s *next; } legacycmd_t; static legacycmd_t *legacycmds = NULL; void Cmd_AddLegacyCommand (char *oldname, char *newname) { legacycmd_t *cmd; cmd = (legacycmd_t *)Q_malloc (sizeof(legacycmd_t)); cmd->next = legacycmds; legacycmds = cmd; cmd->oldname = CopyString (oldname); cmd->newname = CopyString (newname); } static qboolean Cmd_LegacyCommand (void) { qboolean recursive = false; legacycmd_t *cmd; char text[1024]; for (cmd = legacycmds ; cmd ; cmd = cmd->next) if (!Q_strcasecmp(cmd->oldname, Cmd_Argv(0))) break; if (!cmd) return false; if (!cmd->newname[0]) return true; // just ignore this command // build new command string Q_strncpyz (text, cmd->newname, sizeof(text) - 1); strcat (text, " "); strncat (text, Cmd_Args(), sizeof(text) - strlen(text) - 1); assert (!recursive); recursive = true; Cmd_ExecuteString (text, src_command); recursive = false; return true; } /* ============================================================================= COMMAND EXECUTION ============================================================================= */ typedef struct cmd_function_s { struct cmd_function_s *next; char *name; xcommand_t function; } cmd_function_t; static cmd_function_t *cmd_functions; // possible commands to execute #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; /* ============ 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) { if (!cmd_args) return ""; return cmd_args; } /* ============ Cmd_TokenizeString Parses the given string into command line tokens. ============ */ void Cmd_TokenizeString (char *text) { int idx; static char argv_buf[1024]; idx = 0; cmd_argc = 0; cmd_args = NULL; while (1) { // skip whitespace up to a /n while (*text == ' ' || *text == '\t' || *text == '\r') text++; if (*text == '\n') { // a newline seperates commands in the buffer text++; break; } if (!*text) return; if (cmd_argc == 1) cmd_args = text; if (!(text = COM_Parse(text))) return; if (cmd_argc < MAX_ARGS) { cmd_argv[cmd_argc] = argv_buf + idx; strcpy (cmd_argv[cmd_argc], com_token); idx += strlen(com_token) + 1; 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 if (Cmd_Exists(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 (!strcmp(cmd_name, cmd->name)) return true; } return false; } /* ============ Cmd_CompleteCommand ============ */ char *Cmd_CompleteCommand (char *partial) { cmd_function_t *cmd; legacycmd_t *lcmd; int len; if (!(len = strlen(partial))) return NULL; // check functions for (cmd = cmd_functions ; cmd ; cmd = cmd->next) if (!Q_strncasecmp(partial, cmd->name, len)) return cmd->name; for (lcmd = legacycmds ; lcmd ; lcmd = lcmd->next) if (!Q_strncasecmp(partial, lcmd->oldname, len)) return lcmd->oldname; return NULL; } /* ============ Cmd_CompleteCountPossible ============ */ int Cmd_CompleteCountPossible (char *partial) { cmd_function_t *cmd; legacycmd_t *lcmd; int len, c = 0; if (!(len = strlen(partial))) return 0; for (cmd = cmd_functions ; cmd ; cmd = cmd->next) if (!Q_strncasecmp(partial, cmd->name, len)) c++; for (lcmd = legacycmds ; lcmd ; lcmd = lcmd->next) if (!Q_strncasecmp(partial, lcmd->oldname, len)) c++; return c; } //=================================================================== int RDFlags = 0; direntry_t *filelist = NULL; static char filetype[8] = "file"; static char compl_common[MAX_FILELENGTH]; static int compl_len; static int compl_clen; static void FindCommonSubString (char *s) { if (!compl_clen) { Q_strncpyz (compl_common, s, sizeof(compl_common)); compl_clen = strlen (compl_common); } else { while (compl_clen > compl_len && Q_strncasecmp(s, compl_common, compl_clen)) compl_clen--; } } static void CompareParams (void) { int i; compl_clen = 0; for (i=0 ; inext) \ { \ if (!search->pack) \ { \ RDFlags |= (RD_STRIPEXT | RD_NOERASE); \ if (skybox) \ RDFlags |= RD_SKYBOX; \ ReadDir (va("%s/%s", search->filename, subdir), p); \ } \ } /* ============ Cmd_CompleteParameter -- by joe parameter completion for various commands ============ */ void Cmd_CompleteParameter (char *partial, char *attachment) { char *s, *param, stay[MAX_QPATH], subdir[MAX_QPATH] = "", param2[MAX_QPATH]; qboolean skybox = false; Q_strncpyz (stay, partial, sizeof(stay)); // we don't need " + space(s)" included param = strrchr (stay, ' ') + 1; if (!*param) // no parameter was written in, so quit return; compl_len = strlen (param); strcat (param, attachment); if (!strcmp(attachment, "*.bsp")) { Q_strncpyz (subdir, "maps/", sizeof(subdir)); } else if (!strcmp(attachment, "*.tga")) { if (strstr(stay, "loadsky ") == stay || strstr(stay, "r_skybox ") == stay) { Q_strncpyz (subdir, "env/", sizeof(subdir)); skybox = true; } else if (strstr(stay, "loadcharset ") == stay || strstr(stay, "gl_consolefont ") == stay) { Q_strncpyz (subdir, "textures/charsets/", sizeof(subdir)); } else if (strstr(stay, "crosshairimage ") == stay) { Q_strncpyz (subdir, "crosshairs/", sizeof(subdir)); } } if (strstr(stay, "gamedir ") == stay) { RDFlags |= RD_GAMEDIR; ReadDir (com_basedir, param); pak_files = 0; // so that previous pack searches are cleared } else if (strstr(stay, "load ") == stay || strstr(stay, "printtxt ") == stay) { RDFlags |= RD_STRIPEXT; ReadDir (com_gamedir, param); pak_files = 0; // same here } else { searchpath_t *search; EraseDirEntries (); pak_files = 0; READDIR_ALL_PATH(param); if (!strcmp(param + strlen(param)-3, "tga")) { Q_strncpyz (param2, param, strlen(param)-3); strcat (param2, "png"); READDIR_ALL_PATH(param2); FindFilesInPak (va("%s%s", subdir, param2)); } else if (!strcmp(param + strlen(param)-3, "dem")) { Q_strncpyz (param2, param, strlen(param)-3); strcat (param2, "dz"); READDIR_ALL_PATH(param2); FindFilesInPak (va("%s%s", subdir, param2)); } FindFilesInPak (va("%s%s", subdir, param)); } if (!filelist) return; s = strchr (partial, ' ') + 1; // just made this to avoid printing the filename twice when there's only one match if (num_files == 1) { *s = '\0'; strcat (partial, filelist[0].name); key_linepos = strlen(partial) + 1; key_lines[edit_line][key_linepos] = 0; return; } CompareParams (); Con_Printf ("]%s\n", partial); PrintEntries (); *s = '\0'; strcat (partial, compl_common); key_linepos = strlen(partial) + 1; key_lines[edit_line][key_linepos] = 0; } /* ============ 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()) return; // joe: check legacy commands if (Cmd_LegacyCommand()) return; 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); if (Q_strcasecmp(Cmd_Argv(0), "cmd")) { SZ_Print (&cls.message, Cmd_Argv(0)); SZ_Print (&cls.message, " "); } if (Cmd_Argc() > 1) SZ_Print (&cls.message, Cmd_Args()); else SZ_Print (&cls.message, "\n"); } /* ================ 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 ; inext, counter++) Con_Printf ("%s\n", cmd->name); Con_Printf ("------------\n%d commands\n", counter); } /* ==================== Cmd_CvarList_f List all console variables ==================== */ void Cmd_CvarList_f (void) { cvar_t *var; int counter; if (cmd_source != src_command) return; for (counter = 0, var = cvar_vars ; var ; var = var->next, counter++) Con_Printf ("%s\n", var->name); Con_Printf ("------------\n%d variables\n", counter); } /* ==================== Cmd_Dir_f List all files in the mod's directory -- by joe ==================== */ void Cmd_Dir_f (void) { char myarg[MAX_FILELENGTH]; if (cmd_source != src_command) return; if (!strcmp(Cmd_Argv(1), cmd_null_string)) { Q_strncpyz (myarg, "*", sizeof(myarg)); Q_strncpyz (filetype, "file", sizeof(filetype)); } else { Q_strncpyz (myarg, Cmd_Argv(1), sizeof(myarg)); // first two are exceptional cases if (strstr(myarg, "*")) Q_strncpyz (filetype, "file", sizeof(filetype)); else if (strstr(myarg, "*.dem")) Q_strncpyz (filetype, "demo", sizeof(filetype)); else { if (strchr(myarg, '.')) { Q_strncpyz (filetype, COM_FileExtension(myarg), sizeof(filetype)); filetype[strlen(filetype)] = 0x60; // right-shadowed apostrophe } else { strcat (myarg, "*"); Q_strncpyz (filetype, "file", sizeof(filetype)); } } } RDFlags |= RD_COMPLAIN; ReadDir (com_gamedir, myarg); if (!filelist) return; Con_Printf ("\x02" "%ss in current folder are:\n", filetype); PrintEntries (); } static void toLower (char* str) // for strings { char *s; int i; i = 0; s = str; while (*s) { if (*s >= 'A' && *s <= 'Z') *(str + i) = *s + 32; i++; s++; } } static void AddNewEntry (char *fname, int ftype, long fsize) { int i, pos; filelist = Q_realloc (filelist, (num_files + 1) * sizeof(direntry_t)); #ifdef _WIN32 toLower (fname); // else don't convert, linux is case sensitive #endif // inclusion sort for (i=0 ; i filelist[i].type) break; if (strcmp(fname, filelist[i].name) < 0) break; } pos = i; for (i=num_files ; i>pos ; i--) filelist[i] = filelist[i-1]; filelist[i].name = Q_strdup (fname); filelist[i].type = ftype; filelist[i].size = fsize; num_files++; } static void AddNewEntry_unsorted (char *fname, int ftype, long fsize) { filelist = Q_realloc (filelist, (num_files + 1) * sizeof(direntry_t)); #ifdef _WIN32 toLower (fname); // else don't convert, linux is case sensitive #endif filelist[num_files].name = Q_strdup (fname); filelist[num_files].type = ftype; filelist[num_files].size = fsize; num_files++; } void EraseDirEntries (void) { if (filelist) { free (filelist); filelist = NULL; num_files = 0; } } static qboolean CheckEntryName (char *ename) { int i; for (i=0 ; inext) { if (search->pack) { char *s, *p, ext[8], filename[MAX_FILELENGTH]; // look through all the pak file elements pak = search->pack; for (i=0 ; inumfiles ; i++) { s = pak->files[i].name; Q_strncpyz (ext, COM_FileExtension(s), sizeof(ext)); if (!Q_strcasecmp(ext, COM_FileExtension(myarg))) { SLASHJMP(p, s); if (!Q_strcasecmp(ext, "bsp") && !CheckRealBSP(p)) continue; if (!Q_strncasecmp(s, the_arg, strlen(the_arg)-5) || (*myarg == '*' && !Q_strncasecmp(s, the_arg, strlen(the_arg)-5-compl_len))) { COM_StripExtension (p, filename); if (CheckEntryName(filename)) continue; AddNewEntry_unsorted (filename, 0, pak->files[i].filelen); pak_files++; } } } } } } /* ================== PaddedPrint ================== */ #define COLUMNWIDTH 20 #define MINCOLUMNWIDTH 18 // the last column may be slightly smaller extern int con_x; static void PaddedPrint (char *s) { extern int con_linewidth; int nextcolx = 0; if (con_x) nextcolx = (int)((con_x + COLUMNWIDTH) / COLUMNWIDTH) * COLUMNWIDTH; if (nextcolx > con_linewidth - MINCOLUMNWIDTH || (con_x && nextcolx + strlen(s) >= con_linewidth)) Con_Printf ("\n"); if (con_x) Con_Printf (" "); while (con_x % COLUMNWIDTH) Con_Printf (" "); Con_Printf ("%s", s); } static void PrintEntries (void) { int i, filectr; filectr = pak_files ? (num_files - pak_files) : 0; for (i=0 ; i 1) { Con_Printf ("\n"); if (c) { cmd_function_t *cmd; legacycmd_t *lcmd; Con_Printf ("\x02" "commands:\n"); // check commands for (cmd = cmd_functions ; cmd ; cmd = cmd->next) { if (!Q_strncasecmp(s, cmd->name, compl_len)) { PaddedPrint (cmd->name); FindCommonSubString (cmd->name); } } // joe: check for legacy commands also for (lcmd = legacycmds ; lcmd ; lcmd = lcmd->next) { if (!Q_strncasecmp(s, lcmd->oldname, compl_len)) { PaddedPrint (lcmd->oldname); FindCommonSubString (lcmd->oldname); } } if (con_x) Con_Printf ("\n"); } if (v) { cvar_t *var; Con_Printf ("\x02" "variables:\n"); // check variables for (var = cvar_vars ; var ; var = var->next) { if (!Q_strncasecmp(s, var->name, compl_len)) { PaddedPrint (var->name); FindCommonSubString (var->name); } } if (con_x) Con_Printf ("\n"); } } if (c + v == 1) { if (!(cmd = Cmd_CompleteCommand(s))) cmd = Cvar_CompleteVariable (s); } else if (compl_clen) { compl_common[compl_clen] = 0; cmd = compl_common; } else return; strcpy (key_lines[edit_line]+1, cmd); key_linepos = strlen(cmd) + 1; if (c + v == 1) key_lines[edit_line][key_linepos++] = ' '; key_lines[edit_line][key_linepos] = 0; } /* ==================== Cmd_DemDir_f List all demo files -- by joe ==================== */ void Cmd_DemDir_f (void) { char myarg[MAX_FILELENGTH]; if (cmd_source != src_command) return; if (!strcmp(Cmd_Argv(1), cmd_null_string)) { Q_strncpyz (myarg, "*.dem", sizeof(myarg)); } else { Q_strncpyz (myarg, Cmd_Argv(1), sizeof(myarg)); if (strchr(myarg, '.')) { Con_Printf ("You needn`t use dots in demdir parameters\n"); if (strcmp(COM_FileExtension(myarg), "dem")) { Con_Printf ("demdir is for demo files only\n"); return; } } else { strcat (myarg, "*.dem"); } } Q_strncpyz (filetype, "demo", sizeof(filetype)); RDFlags |= (RD_STRIPEXT | RD_COMPLAIN); ReadDir (com_gamedir, myarg); if (!filelist) return; Con_Printf ("\x02" "%ss in current folder are:\n", filetype); PrintEntries (); } /* ==================== AddTabs Replaces nasty tab character with spaces -- by joe ==================== */ static void AddTabs (char *buf) { unsigned char *s, tmp[256]; int i; for (s = (unsigned char *)buf, i = 0 ; *s ; s++, i++) { switch (*s) { case 0xb4: case 0x27: *s = 0x60; break; case '\t': strcpy ((char *)tmp, (char *)s + 1); while (i++ < 8) *s++ = ' '; *s-- = '\0'; strcat (buf, (char *)tmp); break; } if (i >= 7) i = -1; } } /* ==================== Cmd_PrintTxt_f Prints a text file into the console -- by joe ==================== */ void Cmd_PrintTxt_f (void) { char name[MAX_FILELENGTH], buf[256] = {0}; FILE *f; if (cmd_source != src_command) return; if (Cmd_Argc() != 2) { Con_Printf ("printtxt : prints a text file\n"); return; } Q_strncpyz (name, Cmd_Argv(1), sizeof(name)); COM_DefaultExtension (name, ".txt"); Q_strncpyz (buf, va("%s/%s", com_gamedir, name), sizeof(buf)); if (!(f = fopen(buf, "rt"))) { Con_Printf ("ERROR: couldn't open %s\n", name); return; } Con_Printf ("\n"); while (fgets(buf, 256, f)) { AddTabs (buf); Con_Printf ("%s", buf); memset (buf, 0, sizeof(buf)); } Con_Printf ("\n\n"); fclose (f); } /* ============ 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); // by joe Cmd_AddCommand ("cmdlist", Cmd_CmdList_f); Cmd_AddCommand ("cvarlist", Cmd_CvarList_f); Cmd_AddCommand ("dir", Cmd_Dir_f); Cmd_AddCommand ("demdir", Cmd_DemDir_f); Cmd_AddCommand ("printtxt", Cmd_PrintTxt_f); }