Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

cmd.c (15413B)


      1 /*
      2 Copyright (C) 1997-2001 Id Software, Inc.
      3 
      4 This program is free software; you can redistribute it and/or
      5 modify it under the terms of the GNU General Public License
      6 as published by the Free Software Foundation; either version 2
      7 of the License, or (at your option) any later version.
      8 
      9 This program is distributed in the hope that it will be useful,
     10 but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
     12 
     13 See the GNU General Public License for more details.
     14 
     15 You should have received a copy of the GNU General Public License
     16 along with this program; if not, write to the Free Software
     17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     18 
     19 */
     20 // cmd.c -- Quake script command processing module
     21 
     22 #include "qcommon.h"
     23 
     24 void Cmd_ForwardToServer (void);
     25 
     26 #define	MAX_ALIAS_NAME	32
     27 
     28 typedef struct cmdalias_s
     29 {
     30 	struct cmdalias_s	*next;
     31 	char	name[MAX_ALIAS_NAME];
     32 	char	*value;
     33 } cmdalias_t;
     34 
     35 cmdalias_t	*cmd_alias;
     36 
     37 qboolean	cmd_wait;
     38 
     39 #define	ALIAS_LOOP_COUNT	16
     40 int		alias_count;		// for detecting runaway loops
     41 
     42 
     43 //=============================================================================
     44 
     45 /*
     46 ============
     47 Cmd_Wait_f
     48 
     49 Causes execution of the remainder of the command buffer to be delayed until
     50 next frame.  This allows commands like:
     51 bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
     52 ============
     53 */
     54 void Cmd_Wait_f (void)
     55 {
     56 	cmd_wait = true;
     57 }
     58 
     59 
     60 /*
     61 =============================================================================
     62 
     63 						COMMAND BUFFER
     64 
     65 =============================================================================
     66 */
     67 
     68 sizebuf_t	cmd_text;
     69 byte		cmd_text_buf[8192];
     70 
     71 byte		defer_text_buf[8192];
     72 
     73 /*
     74 ============
     75 Cbuf_Init
     76 ============
     77 */
     78 void Cbuf_Init (void)
     79 {
     80 	SZ_Init (&cmd_text, cmd_text_buf, sizeof(cmd_text_buf));
     81 }
     82 
     83 /*
     84 ============
     85 Cbuf_AddText
     86 
     87 Adds command text at the end of the buffer
     88 ============
     89 */
     90 void Cbuf_AddText (char *text)
     91 {
     92 	int		l;
     93 	
     94 	l = strlen (text);
     95 
     96 	if (cmd_text.cursize + l >= cmd_text.maxsize)
     97 	{
     98 		Com_Printf ("Cbuf_AddText: overflow\n");
     99 		return;
    100 	}
    101 	SZ_Write (&cmd_text, text, strlen (text));
    102 }
    103 
    104 
    105 /*
    106 ============
    107 Cbuf_InsertText
    108 
    109 Adds command text immediately after the current command
    110 Adds a \n to the text
    111 FIXME: actually change the command buffer to do less copying
    112 ============
    113 */
    114 void Cbuf_InsertText (char *text)
    115 {
    116 	char	*temp;
    117 	int		templen;
    118 
    119 // copy off any commands still remaining in the exec buffer
    120 	templen = cmd_text.cursize;
    121 	if (templen)
    122 	{
    123 		temp = Z_Malloc (templen);
    124 		memcpy (temp, cmd_text.data, templen);
    125 		SZ_Clear (&cmd_text);
    126 	}
    127 	else
    128 		temp = NULL;	// shut up compiler
    129 		
    130 // add the entire text of the file
    131 	Cbuf_AddText (text);
    132 	
    133 // add the copied off data
    134 	if (templen)
    135 	{
    136 		SZ_Write (&cmd_text, temp, templen);
    137 		Z_Free (temp);
    138 	}
    139 }
    140 
    141 
    142 /*
    143 ============
    144 Cbuf_CopyToDefer
    145 ============
    146 */
    147 void Cbuf_CopyToDefer (void)
    148 {
    149 	memcpy(defer_text_buf, cmd_text_buf, cmd_text.cursize);
    150 	defer_text_buf[cmd_text.cursize] = 0;
    151 	cmd_text.cursize = 0;
    152 }
    153 
    154 /*
    155 ============
    156 Cbuf_InsertFromDefer
    157 ============
    158 */
    159 void Cbuf_InsertFromDefer (void)
    160 {
    161 	Cbuf_InsertText (defer_text_buf);
    162 	defer_text_buf[0] = 0;
    163 }
    164 
    165 
    166 /*
    167 ============
    168 Cbuf_ExecuteText
    169 ============
    170 */
    171 void Cbuf_ExecuteText (int exec_when, char *text)
    172 {
    173 	switch (exec_when)
    174 	{
    175 	case EXEC_NOW:
    176 		Cmd_ExecuteString (text);
    177 		break;
    178 	case EXEC_INSERT:
    179 		Cbuf_InsertText (text);
    180 		break;
    181 	case EXEC_APPEND:
    182 		Cbuf_AddText (text);
    183 		break;
    184 	default:
    185 		Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when");
    186 	}
    187 }
    188 
    189 /*
    190 ============
    191 Cbuf_Execute
    192 ============
    193 */
    194 void Cbuf_Execute (void)
    195 {
    196 	int		i;
    197 	char	*text;
    198 	char	line[1024];
    199 	int		quotes;
    200 
    201 	alias_count = 0;		// don't allow infinite alias loops
    202 
    203 	while (cmd_text.cursize)
    204 	{
    205 // find a \n or ; line break
    206 		text = (char *)cmd_text.data;
    207 
    208 		quotes = 0;
    209 		for (i=0 ; i< cmd_text.cursize ; i++)
    210 		{
    211 			if (text[i] == '"')
    212 				quotes++;
    213 			if ( !(quotes&1) &&  text[i] == ';')
    214 				break;	// don't break if inside a quoted string
    215 			if (text[i] == '\n')
    216 				break;
    217 		}
    218 			
    219 				
    220 		memcpy (line, text, i);
    221 		line[i] = 0;
    222 		
    223 // delete the text from the command buffer and move remaining commands down
    224 // this is necessary because commands (exec, alias) can insert data at the
    225 // beginning of the text buffer
    226 
    227 		if (i == cmd_text.cursize)
    228 			cmd_text.cursize = 0;
    229 		else
    230 		{
    231 			i++;
    232 			cmd_text.cursize -= i;
    233 			memmove (text, text+i, cmd_text.cursize);
    234 		}
    235 
    236 // execute the command line
    237 		Cmd_ExecuteString (line);
    238 		
    239 		if (cmd_wait)
    240 		{
    241 			// skip out while text still remains in buffer, leaving it
    242 			// for next frame
    243 			cmd_wait = false;
    244 			break;
    245 		}
    246 	}
    247 }
    248 
    249 
    250 /*
    251 ===============
    252 Cbuf_AddEarlyCommands
    253 
    254 Adds command line parameters as script statements
    255 Commands lead with a +, and continue until another +
    256 
    257 Set commands are added early, so they are guaranteed to be set before
    258 the client and server initialize for the first time.
    259 
    260 Other commands are added late, after all initialization is complete.
    261 ===============
    262 */
    263 void Cbuf_AddEarlyCommands (qboolean clear)
    264 {
    265 	int		i;
    266 	char	*s;
    267 
    268 	for (i=0 ; i<COM_Argc() ; i++)
    269 	{
    270 		s = COM_Argv(i);
    271 		if (strcmp (s, "+set"))
    272 			continue;
    273 		Cbuf_AddText (va("set %s %s\n", COM_Argv(i+1), COM_Argv(i+2)));
    274 		if (clear)
    275 		{
    276 			COM_ClearArgv(i);
    277 			COM_ClearArgv(i+1);
    278 			COM_ClearArgv(i+2);
    279 		}
    280 		i+=2;
    281 	}
    282 }
    283 
    284 /*
    285 =================
    286 Cbuf_AddLateCommands
    287 
    288 Adds command line parameters as script statements
    289 Commands lead with a + and continue until another + or -
    290 quake +vid_ref gl +map amlev1
    291 
    292 Returns true if any late commands were added, which
    293 will keep the demoloop from immediately starting
    294 =================
    295 */
    296 qboolean Cbuf_AddLateCommands (void)
    297 {
    298 	int		i, j;
    299 	int		s;
    300 	char	*text, *build, c;
    301 	int		argc;
    302 	qboolean	ret;
    303 
    304 // build the combined string to parse from
    305 	s = 0;
    306 	argc = COM_Argc();
    307 	for (i=1 ; i<argc ; i++)
    308 	{
    309 		s += strlen (COM_Argv(i)) + 1;
    310 	}
    311 	if (!s)
    312 		return false;
    313 		
    314 	text = Z_Malloc (s+1);
    315 	text[0] = 0;
    316 	for (i=1 ; i<argc ; i++)
    317 	{
    318 		strcat (text,COM_Argv(i));
    319 		if (i != argc-1)
    320 			strcat (text, " ");
    321 	}
    322 	
    323 // pull out the commands
    324 	build = Z_Malloc (s+1);
    325 	build[0] = 0;
    326 	
    327 	for (i=0 ; i<s-1 ; i++)
    328 	{
    329 		if (text[i] == '+')
    330 		{
    331 			i++;
    332 
    333 			for (j=i ; (text[j] != '+') && (text[j] != '-') && (text[j] != 0) ; j++)
    334 				;
    335 
    336 			c = text[j];
    337 			text[j] = 0;
    338 			
    339 			strcat (build, text+i);
    340 			strcat (build, "\n");
    341 			text[j] = c;
    342 			i = j-1;
    343 		}
    344 	}
    345 
    346 	ret = (build[0] != 0);
    347 	if (ret)
    348 		Cbuf_AddText (build);
    349 	
    350 	Z_Free (text);
    351 	Z_Free (build);
    352 
    353 	return ret;
    354 }
    355 
    356 
    357 /*
    358 ==============================================================================
    359 
    360 						SCRIPT COMMANDS
    361 
    362 ==============================================================================
    363 */
    364 
    365 
    366 /*
    367 ===============
    368 Cmd_Exec_f
    369 ===============
    370 */
    371 void Cmd_Exec_f (void)
    372 {
    373 	char	*f, *f2;
    374 	int		len;
    375 
    376 	if (Cmd_Argc () != 2)
    377 	{
    378 		Com_Printf ("exec <filename> : execute a script file\n");
    379 		return;
    380 	}
    381 
    382 	len = FS_LoadFile (Cmd_Argv(1), (void **)&f);
    383 	if (!f)
    384 	{
    385 		Com_Printf ("couldn't exec %s\n",Cmd_Argv(1));
    386 		return;
    387 	}
    388 	Com_Printf ("execing %s\n",Cmd_Argv(1));
    389 	
    390 	// the file doesn't have a trailing 0, so we need to copy it off
    391 	f2 = Z_Malloc(len+1);
    392 	memcpy (f2, f, len);
    393 	f2[len] = 0;
    394 
    395 	Cbuf_InsertText (f2);
    396 
    397 	Z_Free (f2);
    398 	FS_FreeFile (f);
    399 }
    400 
    401 
    402 /*
    403 ===============
    404 Cmd_Echo_f
    405 
    406 Just prints the rest of the line to the console
    407 ===============
    408 */
    409 void Cmd_Echo_f (void)
    410 {
    411 	int		i;
    412 	
    413 	for (i=1 ; i<Cmd_Argc() ; i++)
    414 		Com_Printf ("%s ",Cmd_Argv(i));
    415 	Com_Printf ("\n");
    416 }
    417 
    418 /*
    419 ===============
    420 Cmd_Alias_f
    421 
    422 Creates a new command that executes a command string (possibly ; seperated)
    423 ===============
    424 */
    425 void Cmd_Alias_f (void)
    426 {
    427 	cmdalias_t	*a;
    428 	char		cmd[1024];
    429 	int			i, c;
    430 	char		*s;
    431 
    432 	if (Cmd_Argc() == 1)
    433 	{
    434 		Com_Printf ("Current alias commands:\n");
    435 		for (a = cmd_alias ; a ; a=a->next)
    436 			Com_Printf ("%s : %s\n", a->name, a->value);
    437 		return;
    438 	}
    439 
    440 	s = Cmd_Argv(1);
    441 	if (strlen(s) >= MAX_ALIAS_NAME)
    442 	{
    443 		Com_Printf ("Alias name is too long\n");
    444 		return;
    445 	}
    446 
    447 	// if the alias already exists, reuse it
    448 	for (a = cmd_alias ; a ; a=a->next)
    449 	{
    450 		if (!strcmp(s, a->name))
    451 		{
    452 			Z_Free (a->value);
    453 			break;
    454 		}
    455 	}
    456 
    457 	if (!a)
    458 	{
    459 		a = Z_Malloc (sizeof(cmdalias_t));
    460 		a->next = cmd_alias;
    461 		cmd_alias = a;
    462 	}
    463 	strcpy (a->name, s);	
    464 
    465 // copy the rest of the command line
    466 	cmd[0] = 0;		// start out with a null string
    467 	c = Cmd_Argc();
    468 	for (i=2 ; i< c ; i++)
    469 	{
    470 		strcat (cmd, Cmd_Argv(i));
    471 		if (i != (c - 1))
    472 			strcat (cmd, " ");
    473 	}
    474 	strcat (cmd, "\n");
    475 	
    476 	a->value = CopyString (cmd);
    477 }
    478 
    479 /*
    480 =============================================================================
    481 
    482 					COMMAND EXECUTION
    483 
    484 =============================================================================
    485 */
    486 
    487 typedef struct cmd_function_s
    488 {
    489 	struct cmd_function_s	*next;
    490 	char					*name;
    491 	xcommand_t				function;
    492 } cmd_function_t;
    493 
    494 
    495 static	int			cmd_argc;
    496 static	char		*cmd_argv[MAX_STRING_TOKENS];
    497 static	char		*cmd_null_string = "";
    498 static	char		cmd_args[MAX_STRING_CHARS];
    499 
    500 static	cmd_function_t	*cmd_functions;		// possible commands to execute
    501 
    502 /*
    503 ============
    504 Cmd_Argc
    505 ============
    506 */
    507 int		Cmd_Argc (void)
    508 {
    509 	return cmd_argc;
    510 }
    511 
    512 /*
    513 ============
    514 Cmd_Argv
    515 ============
    516 */
    517 char	*Cmd_Argv (int arg)
    518 {
    519 	if ( (unsigned)arg >= cmd_argc )
    520 		return cmd_null_string;
    521 	return cmd_argv[arg];	
    522 }
    523 
    524 /*
    525 ============
    526 Cmd_Args
    527 
    528 Returns a single string containing argv(1) to argv(argc()-1)
    529 ============
    530 */
    531 char		*Cmd_Args (void)
    532 {
    533 	return cmd_args;
    534 }
    535 
    536 
    537 /*
    538 ======================
    539 Cmd_MacroExpandString
    540 ======================
    541 */
    542 char *Cmd_MacroExpandString (char *text)
    543 {
    544 	int		i, j, count, len;
    545 	qboolean	inquote;
    546 	char	*scan;
    547 	static	char	expanded[MAX_STRING_CHARS];
    548 	char	temporary[MAX_STRING_CHARS];
    549 	char	*token, *start;
    550 
    551 	inquote = false;
    552 	scan = text;
    553 
    554 	len = strlen (scan);
    555 	if (len >= MAX_STRING_CHARS)
    556 	{
    557 		Com_Printf ("Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS);
    558 		return NULL;
    559 	}
    560 
    561 	count = 0;
    562 
    563 	for (i=0 ; i<len ; i++)
    564 	{
    565 		if (scan[i] == '"')
    566 			inquote ^= 1;
    567 		if (inquote)
    568 			continue;	// don't expand inside quotes
    569 		if (scan[i] != '$')
    570 			continue;
    571 		// scan out the complete macro
    572 		start = scan+i+1;
    573 		token = COM_Parse (&start);
    574 		if (!start)
    575 			continue;
    576 	
    577 		token = Cvar_VariableString (token);
    578 
    579 		j = strlen(token);
    580 		len += j;
    581 		if (len >= MAX_STRING_CHARS)
    582 		{
    583 			Com_Printf ("Expanded line exceeded %i chars, discarded.\n", MAX_STRING_CHARS);
    584 			return NULL;
    585 		}
    586 
    587 		strncpy (temporary, scan, i);
    588 		strcpy (temporary+i, token);
    589 		strcpy (temporary+i+j, start);
    590 
    591 		strcpy (expanded, temporary);
    592 		scan = expanded;
    593 		i--;
    594 
    595 		if (++count == 100)
    596 		{
    597 			Com_Printf ("Macro expansion loop, discarded.\n");
    598 			return NULL;
    599 		}
    600 	}
    601 
    602 	if (inquote)
    603 	{
    604 		Com_Printf ("Line has unmatched quote, discarded.\n");
    605 		return NULL;
    606 	}
    607 
    608 	return scan;
    609 }
    610 
    611 
    612 /*
    613 ============
    614 Cmd_TokenizeString
    615 
    616 Parses the given string into command line tokens.
    617 $Cvars will be expanded unless they are in a quoted token
    618 ============
    619 */
    620 void Cmd_TokenizeString (char *text, qboolean macroExpand)
    621 {
    622 	int		i;
    623 	char	*com_token;
    624 
    625 // clear the args from the last string
    626 	for (i=0 ; i<cmd_argc ; i++)
    627 		Z_Free (cmd_argv[i]);
    628 		
    629 	cmd_argc = 0;
    630 	cmd_args[0] = 0;
    631 	
    632 	// macro expand the text
    633 	if (macroExpand)
    634 		text = Cmd_MacroExpandString (text);
    635 	if (!text)
    636 		return;
    637 
    638 	while (1)
    639 	{
    640 // skip whitespace up to a /n
    641 		while (*text && *text <= ' ' && *text != '\n')
    642 		{
    643 			text++;
    644 		}
    645 		
    646 		if (*text == '\n')
    647 		{	// a newline seperates commands in the buffer
    648 			text++;
    649 			break;
    650 		}
    651 
    652 		if (!*text)
    653 			return;
    654 
    655 		// set cmd_args to everything after the first arg
    656 		if (cmd_argc == 1)
    657 		{
    658 			int		l;
    659 
    660 			strcpy (cmd_args, text);
    661 
    662 			// strip off any trailing whitespace
    663 			l = strlen(cmd_args) - 1;
    664 			for ( ; l >= 0 ; l--)
    665 				if (cmd_args[l] <= ' ')
    666 					cmd_args[l] = 0;
    667 				else
    668 					break;
    669 		}
    670 			
    671 		com_token = COM_Parse (&text);
    672 		if (!text)
    673 			return;
    674 
    675 		if (cmd_argc < MAX_STRING_TOKENS)
    676 		{
    677 			cmd_argv[cmd_argc] = Z_Malloc (strlen(com_token)+1);
    678 			strcpy (cmd_argv[cmd_argc], com_token);
    679 			cmd_argc++;
    680 		}
    681 	}
    682 	
    683 }
    684 
    685 
    686 /*
    687 ============
    688 Cmd_AddCommand
    689 ============
    690 */
    691 void	Cmd_AddCommand (char *cmd_name, xcommand_t function)
    692 {
    693 	cmd_function_t	*cmd;
    694 	
    695 // fail if the command is a variable name
    696 	if (Cvar_VariableString(cmd_name)[0])
    697 	{
    698 		Com_Printf ("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
    699 		return;
    700 	}
    701 	
    702 // fail if the command already exists
    703 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
    704 	{
    705 		if (!strcmp (cmd_name, cmd->name))
    706 		{
    707 			Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
    708 			return;
    709 		}
    710 	}
    711 
    712 	cmd = Z_Malloc (sizeof(cmd_function_t));
    713 	cmd->name = cmd_name;
    714 	cmd->function = function;
    715 	cmd->next = cmd_functions;
    716 	cmd_functions = cmd;
    717 }
    718 
    719 /*
    720 ============
    721 Cmd_RemoveCommand
    722 ============
    723 */
    724 void	Cmd_RemoveCommand (char *cmd_name)
    725 {
    726 	cmd_function_t	*cmd, **back;
    727 
    728 	back = &cmd_functions;
    729 	while (1)
    730 	{
    731 		cmd = *back;
    732 		if (!cmd)
    733 		{
    734 			Com_Printf ("Cmd_RemoveCommand: %s not added\n", cmd_name);
    735 			return;
    736 		}
    737 		if (!strcmp (cmd_name, cmd->name))
    738 		{
    739 			*back = cmd->next;
    740 			Z_Free (cmd);
    741 			return;
    742 		}
    743 		back = &cmd->next;
    744 	}
    745 }
    746 
    747 /*
    748 ============
    749 Cmd_Exists
    750 ============
    751 */
    752 qboolean	Cmd_Exists (char *cmd_name)
    753 {
    754 	cmd_function_t	*cmd;
    755 
    756 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
    757 	{
    758 		if (!strcmp (cmd_name,cmd->name))
    759 			return true;
    760 	}
    761 
    762 	return false;
    763 }
    764 
    765 
    766 
    767 /*
    768 ============
    769 Cmd_CompleteCommand
    770 ============
    771 */
    772 char *Cmd_CompleteCommand (char *partial)
    773 {
    774 	cmd_function_t	*cmd;
    775 	int				len;
    776 	cmdalias_t		*a;
    777 	
    778 	len = strlen(partial);
    779 	
    780 	if (!len)
    781 		return NULL;
    782 		
    783 // check for exact match
    784 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
    785 		if (!strcmp (partial,cmd->name))
    786 			return cmd->name;
    787 	for (a=cmd_alias ; a ; a=a->next)
    788 		if (!strcmp (partial, a->name))
    789 			return a->name;
    790 
    791 // check for partial match
    792 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
    793 		if (!strncmp (partial,cmd->name, len))
    794 			return cmd->name;
    795 	for (a=cmd_alias ; a ; a=a->next)
    796 		if (!strncmp (partial, a->name, len))
    797 			return a->name;
    798 
    799 	return NULL;
    800 }
    801 
    802 
    803 /*
    804 ============
    805 Cmd_ExecuteString
    806 
    807 A complete command line has been parsed, so try to execute it
    808 FIXME: lookupnoadd the token to speed search?
    809 ============
    810 */
    811 void	Cmd_ExecuteString (char *text)
    812 {	
    813 	cmd_function_t	*cmd;
    814 	cmdalias_t		*a;
    815 
    816 	Cmd_TokenizeString (text, true);
    817 			
    818 	// execute the command line
    819 	if (!Cmd_Argc())
    820 		return;		// no tokens
    821 
    822 	// check functions
    823 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
    824 	{
    825 		if (!Q_strcasecmp (cmd_argv[0],cmd->name))
    826 		{
    827 			if (!cmd->function)
    828 			{	// forward to server command
    829 				Cmd_ExecuteString (va("cmd %s", text));
    830 			}
    831 			else
    832 				cmd->function ();
    833 			return;
    834 		}
    835 	}
    836 
    837 	// check alias
    838 	for (a=cmd_alias ; a ; a=a->next)
    839 	{
    840 		if (!Q_strcasecmp (cmd_argv[0], a->name))
    841 		{
    842 			if (++alias_count == ALIAS_LOOP_COUNT)
    843 			{
    844 				Com_Printf ("ALIAS_LOOP_COUNT\n");
    845 				return;
    846 			}
    847 			Cbuf_InsertText (a->value);
    848 			return;
    849 		}
    850 	}
    851 	
    852 	// check cvars
    853 	if (Cvar_Command ())
    854 		return;
    855 
    856 	// send it as a server command if we are connected
    857 	Cmd_ForwardToServer ();
    858 }
    859 
    860 /*
    861 ============
    862 Cmd_List_f
    863 ============
    864 */
    865 void Cmd_List_f (void)
    866 {
    867 	cmd_function_t	*cmd;
    868 	int				i;
    869 
    870 	i = 0;
    871 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next, i++)
    872 		Com_Printf ("%s\n", cmd->name);
    873 	Com_Printf ("%i commands\n", i);
    874 }
    875 
    876 /*
    877 ============
    878 Cmd_Init
    879 ============
    880 */
    881 void Cmd_Init (void)
    882 {
    883 //
    884 // register our commands
    885 //
    886 	Cmd_AddCommand ("cmdlist",Cmd_List_f);
    887 	Cmd_AddCommand ("exec",Cmd_Exec_f);
    888 	Cmd_AddCommand ("echo",Cmd_Echo_f);
    889 	Cmd_AddCommand ("alias",Cmd_Alias_f);
    890 	Cmd_AddCommand ("wait", Cmd_Wait_f);
    891 }
    892