/* 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" // joe: ReadDir()'s stuff #ifndef _WIN32 #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; 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 = 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_strcpy (name, Cmd_Argv(1)); 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++; // joe: check for legacy commands also 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 ; i + space(s)" included param = strrchr (stay, ' ') + 1; if (!*param) // no parameter was written in, so quit return; compl_len = strlen (param); strcat (param, attachment); // map requires maps/ dir if (strstr(attachment, "*.bsp")) { sprintf (subdir, "maps/%s", param); param = subdir; RDFlags |= (FOR_COMPLETION | NoPrint); ReadDir (param); RDFlags |= FOR_ID1; } // sky requires env/ dir // FIXME: what about crosshair pics? else if (strstr(attachment, "*.tga")) { sprintf (subdir, "env/%s", param); param = subdir; RDFlags |= (FOR_COMPLETION | NoPrint | FOR_SKYBOX); ReadDir (param); RDFlags |= (FOR_ID1 | FOR_SKYBOX); } RDFlags |= (FOR_COMPLETION | NoPrint); // to avoid printing .EXT and "No such file" ReadDir (param); FindFilesInPak (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; } if (*s == '*') Q_strcpy (compl_common, s); else 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_strcpy (myarg, "*.*"); Q_strcpy (filetype, "file"); } else { Q_strcpy (myarg, Cmd_Argv(1)); // first two are exceptional cases if (strstr (myarg, "*.*")) Q_strcpy (filetype, "file"); else if (strstr (myarg, "*.dem")) Q_strcpy (filetype, "demo"); else { if (strchr (myarg, '.')) { Q_strcpy (filetype, COM_FileExtension (myarg)); filetype[strlen(filetype)] = 0x60; // right-shadowed apostrophe } else { strcat (myarg, "*.*"); Q_strcpy (filetype, "file"); } } } ReadDir (myarg); if (!filelist) return; Con_Printf ("\x02" "%ss in current folder are:\n", filetype); PrintEntries (); } #if 0 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++; } } #endif 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 = 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 = strdup (fname); filelist[num_files].type = ftype; filelist[num_files].size = fsize; num_files++; } static void EraseDirEntries (void) { if (filelist) { free (filelist); filelist = NULL; num_files = 0; } } static qboolean CheckEntryName (char *ename) { int i; for (i=0 ; id_name), &fileinfo); else stat (va("%s%s/%s", com_basedir, demodir, dstruct->d_name), &fileinfo); if (S_ISDIR(fileinfo.st_mode)) { if (!(RDFlags & FOR_MENU_DEMOS) || !strcmp(dstruct->d_name, ".") || !strcmp(dstruct->d_name, "..")) continue; fdtype = 1; fdsize = 0; Q_strcpy (filename, dstruct->d_name); } else { char ext[MAX_FILELENGTH]; if (!(RDFlags & (FOR_MENU_DEMOS | FOR_MENU_MAPS))) { if (Q_strcasecmp(dstruct->d_name + strlen(dstruct->d_name)-4, p + strlen(p)-4) || !(*p == '*' || !Q_strncasecmp(dstruct->d_name, p, compl_len))) continue; } Q_strcpy (ext, COM_FileExtension(dstruct->d_name)); if (RDFlags & FOR_MENU_DEMOS && Q_strcasecmp(ext, "dem") && Q_strcasecmp(ext, "dz")) continue; else if (RDFlags & FOR_MENU_MAPS && Q_strcasecmp(ext, "bsp")) continue; fdtype = 0; fdsize = fileinfo.st_size; if (Q_strcasecmp(ext, "dz") && RDFlags & (FOR_COMPLETION | FOR_DEMDIR | FOR_MENU_DEMOS | FOR_MENU_MAPS)) { COM_StripExtension (dstruct->d_name, filename); if (RDFlags & FOR_SKYBOX) filename[strlen(filename)-2] = 0; // cut off skybox_ext if (RDFlags & (FOR_ID1 | FOR_SKYBOX) && CheckEntryName(filename)) continue; // file already on list } else { Q_strcpy (filename, dstruct->d_name); } } AddNewEntry (filename, fdtype, fdsize); } while ((dstruct = readdir(d))); closedir (d); if (!num_files) { if (RDFlags & FOR_MENU_DEMOS) { AddNewEntry ("[ no files ]", 3, 0); num_files = 1; } else if (!(RDFlags & NoPrint)) { Con_Printf ("No such file\n"); } } end: RDFlags &= 0; } #endif static qboolean CheckRealBSP (char *bspname) { if (!strcmp(bspname, "b_batt0.bsp") || !strcmp(bspname, "b_batt1.bsp") || !strcmp(bspname, "b_bh10.bsp") || !strcmp(bspname, "b_bh100.bsp") || !strcmp(bspname, "b_bh25.bsp") || !strcmp(bspname, "b_explob.bsp") || !strcmp(bspname, "b_nail0.bsp") || !strcmp(bspname, "b_nail1.bsp") || !strcmp(bspname, "b_rock0.bsp") || !strcmp(bspname, "b_rock1.bsp") || !strcmp(bspname, "b_shell0.bsp") || !strcmp(bspname, "b_shell1.bsp") || !strcmp(bspname, "b_exbox2.bsp")) return false; return true; } /* ================= FindFilesInPak Search for files inside a PAK file -- by joe ================= */ #define SLASHJMP(x, y) \ if (!(x = strrchr(y, '/'))) \ x = y; \ else \ *++x int pak_files = 0; void FindFilesInPak (char *the_arg) { int i; searchpath_t *search; pack_t *pak; char *myarg; extern searchpath_t *com_searchpaths; pak_files = 0; SLASHJMP(myarg, the_arg); for (search = com_searchpaths ; search ; search = search->next) { if (search->pack) { char *s, ext[8], filename[MAX_FILELENGTH]; int csiba; // look through all the pak file elements pak = search->pack; for (i=0 ; inumfiles ; i++) { SLASHJMP(s, pak->files[i].name); Q_strcpy (ext, COM_FileExtension(s)); if (!Q_strcasecmp(ext, COM_FileExtension(myarg))) { if (!Q_strcasecmp(ext, "bsp") && !CheckRealBSP(s)) continue; // FIXME: what if *something*.* csiba = (myarg[compl_len-1] == '*') ? compl_len - 1 : compl_len; if (!Q_strncasecmp(s, myarg, csiba) || *myarg == '*') { COM_StripExtension (s, 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 = (num_files + pak_files) ? (num_files - pak_files) : 0; for (i=0 ; i 1) { if (c) { cmd_function_t *cmd; legacycmd_t *lcmd; Con_Printf ("\n"); Con_Printf ("\x02" "commands:\n"); // check commands for (cmd = cmd_functions ; cmd ; cmd = cmd->next) if (!Q_strncasecmp(partial, 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(partial, lcmd->oldname, compl_len)) { PaddedPrint (lcmd->oldname); FindCommonSubString (lcmd->oldname); } } if (v) { cvar_t *cvar; Con_Printf ("\n"); Con_Printf ("\x02" "variables:\n"); // check variables for (cvar = cvar_vars ; cvar ; cvar = cvar->next) if (!Q_strncasecmp(partial, cvar->name, compl_len)) { PaddedPrint (cvar->name); FindCommonSubString (cvar->name); } } } if (con_x) Con_Printf ("\n"); if (c + v == 1) { if (!(cmd = Cmd_CompleteCommand(partial))) cmd = Cvar_CompleteVariable (partial); } else if (compl_clen) { compl_common[compl_clen] = 0; cmd = compl_common; } Q_strcpy (key_lines[edit_line]+1, cmd); key_linepos = strlen (cmd) + 1; if (c + v == 1) { key_lines[edit_line][key_linepos] = ' '; 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_strcpy (myarg, "*.dem"); } else { Q_strcpy (myarg, Cmd_Argv(1)); 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_strcpy (filetype, "demo"); RDFlags |= FOR_DEMDIR; ReadDir (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++) { if (*s == '\t') { strcpy ((char *)tmp, (char *)s + 1); while (i < 8) { *s++ = ' '; i++; } *s = '\0'; strcat (buf, (char *)tmp); s--; } else if (*s == 0xb4 || *s == 0x27) { *s = 0x60; } 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]; FILE *f; char buf[256]; // int lines = 0; if (cmd_source != src_command) return; if (Cmd_Argc() != 2) { Con_Printf ("printtxt : prints a text file\n"); return; } Q_strcpy (name, Cmd_Argv(1)); COM_DefaultExtension (name, ".txt"); Q_strcpy (buf, va("%s%s/%s", com_basedir, demodir, name)); if (!(f = fopen (buf, "rt"))) { Con_Printf ("ERROR: couldn't open %s\n", name); return; } Con_Printf ("\n"); while (!feof(f)) { fgets (buf, 256, f); AddTabs (buf); Con_Printf ("%s", buf); buf[0] = '\0'; // lines++; // if (lines == ((int)scr_conlines-16)>>3) {} } 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); }