Home | History | Annotate | Download | only in client
      1 /*
      2 Copyright (C) 1996-1997 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 "quakedef.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 cvar_t cl_warncmd = CVAR2("cl_warncmd", "0");
     40 
     41 //=============================================================================
     42 
     43 /*
     44 ============
     45 Cmd_Wait_f
     46 
     47 Causes execution of the remainder of the command buffer to be delayed until
     48 next frame.  This allows commands like:
     49 bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
     50 ============
     51 */
     52 void Cmd_Wait_f (void)
     53 {
     54 	cmd_wait = true;
     55 }
     56 
     57 /*
     58 =============================================================================
     59 
     60 						COMMAND BUFFER
     61 
     62 =============================================================================
     63 */
     64 
     65 sizebuf_t	cmd_text;
     66 byte		cmd_text_buf[8192];
     67 
     68 /*
     69 ============
     70 Cbuf_Init
     71 ============
     72 */
     73 void Cbuf_Init (void)
     74 {
     75 	cmd_text.data = cmd_text_buf;
     76 	cmd_text.maxsize = sizeof(cmd_text_buf);
     77 }
     78 
     79 /*
     80 ============
     81 Cbuf_AddText
     82 
     83 Adds command text at the end of the buffer
     84 ============
     85 */
     86 void Cbuf_AddText (char *text)
     87 {
     88 	int		l;
     89 
     90 	l = Q_strlen (text);
     91 
     92 	if (cmd_text.cursize + l >= cmd_text.maxsize)
     93 	{
     94 		Con_Printf ("Cbuf_AddText: overflow\n");
     95 		return;
     96 	}
     97 	SZ_Write (&cmd_text, text, Q_strlen (text));
     98 }
     99 
    100 
    101 /*
    102 ============
    103 Cbuf_InsertText
    104 
    105 Adds command text immediately after the current command
    106 Adds a \n to the text
    107 FIXME: actually change the command buffer to do less copying
    108 ============
    109 */
    110 void Cbuf_InsertText (char *text)
    111 {
    112 	char	*temp;
    113 	int		templen;
    114 
    115 // copy off any commands still remaining in the exec buffer
    116 	templen = cmd_text.cursize;
    117 	if (templen)
    118 	{
    119 		temp = Z_Malloc (templen);
    120 		Q_memcpy (temp, cmd_text.data, templen);
    121 		SZ_Clear (&cmd_text);
    122 	}
    123 	else
    124 		temp = NULL;	// shut up compiler
    125 
    126 // add the entire text of the file
    127 	Cbuf_AddText (text);
    128 	SZ_Write (&cmd_text, "\n", 1);
    129 // add the copied off data
    130 	if (templen)
    131 	{
    132 		SZ_Write (&cmd_text, temp, templen);
    133 		Z_Free (temp);
    134 	}
    135 }
    136 
    137 /*
    138 ============
    139 Cbuf_Execute
    140 ============
    141 */
    142 void Cbuf_Execute (void)
    143 {
    144 	int		i;
    145 	char	*text;
    146 	char	line[1024];
    147 	int		quotes;
    148 
    149 	while (cmd_text.cursize)
    150 	{
    151 // find a \n or ; line break
    152 		text = (char *)cmd_text.data;
    153 
    154 		quotes = 0;
    155 		for (i=0 ; i< cmd_text.cursize ; i++)
    156 		{
    157 			if (text[i] == '"')
    158 				quotes++;
    159 			if ( !(quotes&1) &&  text[i] == ';')
    160 				break;	// don't break if inside a quoted string
    161 			if (text[i] == '\n' || text[i] == '\r')
    162 				break;
    163 		}
    164 
    165 
    166 		memcpy (line, text, i);
    167 		line[i] = 0;
    168 
    169 // delete the text from the command buffer and move remaining commands down
    170 // this is necessary because commands (exec, alias) can insert data at the
    171 // beginning of the text buffer
    172 
    173 		if (i == cmd_text.cursize)
    174 			cmd_text.cursize = 0;
    175 		else
    176 		{
    177 			i++;
    178 			cmd_text.cursize -= i;
    179 			Q_memcpy (text, text+i, cmd_text.cursize);
    180 		}
    181 
    182 // execute the command line
    183 		Cmd_ExecuteString (line);
    184 
    185 		if (cmd_wait)
    186 		{	// skip out while text still remains in buffer, leaving it
    187 			// for next frame
    188 			cmd_wait = false;
    189 			break;
    190 		}
    191 	}
    192 }
    193 
    194 /*
    195 ==============================================================================
    196 
    197 						SCRIPT COMMANDS
    198 
    199 ==============================================================================
    200 */
    201 
    202 /*
    203 ===============
    204 Cmd_StuffCmds_f
    205 
    206 Adds command line parameters as script statements
    207 Commands lead with a +, and continue until a - or another +
    208 quake +prog jctest.qp +cmd amlev1
    209 quake -nosound +cmd amlev1
    210 ===============
    211 */
    212 void Cmd_StuffCmds_f (void)
    213 {
    214 	int		i, j;
    215 	int		s;
    216 	char	*text, *build, c;
    217 
    218 // build the combined string to parse from
    219 	s = 0;
    220 	for (i=1 ; i<com_argc ; i++)
    221 	{
    222 		if (!com_argv[i])
    223 			continue;		// NEXTSTEP nulls out -NXHost
    224 		s += Q_strlen (com_argv[i]) + 1;
    225 	}
    226 	if (!s)
    227 		return;
    228 
    229 	text = Z_Malloc (s+1);
    230 	text[0] = 0;
    231 	for (i=1 ; i<com_argc ; i++)
    232 	{
    233 		if (!com_argv[i])
    234 			continue;		// NEXTSTEP nulls out -NXHost
    235 		Q_strcat (text,com_argv[i]);
    236 		if (i != com_argc-1)
    237 			Q_strcat (text, " ");
    238 	}
    239 
    240 // pull out the commands
    241 	build = Z_Malloc (s+1);
    242 	build[0] = 0;
    243 
    244 	for (i=0 ; i<s-1 ; i++)
    245 	{
    246 		if (text[i] == '+')
    247 		{
    248 			i++;
    249 
    250 			for (j=i ; (text[j] != '+') && (text[j] != '-') && (text[j] != 0) ; j++)
    251 				;
    252 
    253 			c = text[j];
    254 			text[j] = 0;
    255 
    256 			Q_strcat (build, text+i);
    257 			Q_strcat (build, "\n");
    258 			text[j] = c;
    259 			i = j-1;
    260 		}
    261 	}
    262 
    263 	if (build[0])
    264 		Cbuf_InsertText (build);
    265 
    266 	Z_Free (text);
    267 	Z_Free (build);
    268 }
    269 
    270 
    271 /*
    272 ===============
    273 Cmd_Exec_f
    274 ===============
    275 */
    276 void Cmd_Exec_f (void)
    277 {
    278 	char	*f;
    279 	int		mark;
    280 
    281 	if (Cmd_Argc () != 2)
    282 	{
    283 		Con_Printf ("exec <filename> : execute a script file\n");
    284 		return;
    285 	}
    286 
    287 	// FIXME: is this safe freeing the hunk here???
    288 	mark = Hunk_LowMark ();
    289 	f = (char *)COM_LoadHunkFile (Cmd_Argv(1));
    290 	if (!f)
    291 	{
    292 		Con_Printf ("couldn't exec %s\n",Cmd_Argv(1));
    293 		return;
    294 	}
    295 	if (!Cvar_Command () && (cl_warncmd.value || developer.value))
    296 		Con_Printf ("execing %s\n",Cmd_Argv(1));
    297 
    298 	Cbuf_InsertText (f);
    299 	Hunk_FreeToLowMark (mark);
    300 }
    301 
    302 
    303 /*
    304 ===============
    305 Cmd_Echo_f
    306 
    307 Just prints the rest of the line to the console
    308 ===============
    309 */
    310 void Cmd_Echo_f (void)
    311 {
    312 	int		i;
    313 
    314 	for (i=1 ; i<Cmd_Argc() ; i++)
    315 		Con_Printf ("%s ",Cmd_Argv(i));
    316 	Con_Printf ("\n");
    317 }
    318 
    319 /*
    320 ===============
    321 Cmd_Alias_f
    322 
    323 Creates a new command that executes a command string (possibly ; seperated)
    324 ===============
    325 */
    326 
    327 char *CopyString (char *in)
    328 {
    329 	char	*out;
    330 
    331 	out = Z_Malloc (strlen(in)+1);
    332 	strcpy (out, in);
    333 	return out;
    334 }
    335 
    336 void Cmd_Alias_f (void)
    337 {
    338 	cmdalias_t	*a;
    339 	char		cmd[1024];
    340 	int			i, c;
    341 	char		*s;
    342 
    343 	if (Cmd_Argc() == 1)
    344 	{
    345 		Con_Printf ("Current alias commands:\n");
    346 		for (a = cmd_alias ; a ; a=a->next)
    347 			Con_Printf ("%s : %s\n", a->name, a->value);
    348 		return;
    349 	}
    350 
    351 	s = Cmd_Argv(1);
    352 	if (strlen(s) >= MAX_ALIAS_NAME)
    353 	{
    354 		Con_Printf ("Alias name is too long\n");
    355 		return;
    356 	}
    357 
    358 	// if the alias allready exists, reuse it
    359 	for (a = cmd_alias ; a ; a=a->next)
    360 	{
    361 		if (!strcmp(s, a->name))
    362 		{
    363 			Z_Free (a->value);
    364 			break;
    365 		}
    366 	}
    367 
    368 	if (!a)
    369 	{
    370 		a = Z_Malloc (sizeof(cmdalias_t));
    371 		a->next = cmd_alias;
    372 		cmd_alias = a;
    373 	}
    374 	strcpy (a->name, s);
    375 
    376 // copy the rest of the command line
    377 	cmd[0] = 0;		// start out with a null string
    378 	c = Cmd_Argc();
    379 	for (i=2 ; i< c ; i++)
    380 	{
    381 		strcat (cmd, Cmd_Argv(i));
    382 		if (i != c)
    383 			strcat (cmd, " ");
    384 	}
    385 	strcat (cmd, "\n");
    386 
    387 	a->value = CopyString (cmd);
    388 }
    389 
    390 /*
    391 =============================================================================
    392 
    393 					COMMAND EXECUTION
    394 
    395 =============================================================================
    396 */
    397 
    398 typedef struct cmd_function_s
    399 {
    400 	struct cmd_function_s	*next;
    401 	char					*name;
    402 	xcommand_t				function;
    403 } cmd_function_t;
    404 
    405 
    406 #define	MAX_ARGS		80
    407 
    408 static	int			cmd_argc;
    409 static	char		*cmd_argv[MAX_ARGS];
    410 static	char		*cmd_null_string = "";
    411 static	char		*cmd_args = NULL;
    412 
    413 
    414 
    415 static	cmd_function_t	*cmd_functions;		// possible commands to execute
    416 
    417 /*
    418 ============
    419 Cmd_Argc
    420 ============
    421 */
    422 int		Cmd_Argc (void)
    423 {
    424 	return cmd_argc;
    425 }
    426 
    427 /*
    428 ============
    429 Cmd_Argv
    430 ============
    431 */
    432 char	*Cmd_Argv (int arg)
    433 {
    434 	if ( arg >= cmd_argc )
    435 		return cmd_null_string;
    436 	return cmd_argv[arg];
    437 }
    438 
    439 /*
    440 ============
    441 Cmd_Args
    442 
    443 Returns a single string containing argv(1) to argv(argc()-1)
    444 ============
    445 */
    446 char		*Cmd_Args (void)
    447 {
    448 	if (!cmd_args)
    449 		return "";
    450 	return cmd_args;
    451 }
    452 
    453 
    454 /*
    455 ============
    456 Cmd_TokenizeString
    457 
    458 Parses the given string into command line tokens.
    459 ============
    460 */
    461 void Cmd_TokenizeString (char *text)
    462 {
    463 	int		i;
    464 
    465 // clear the args from the last string
    466 	for (i=0 ; i<cmd_argc ; i++)
    467 		Z_Free (cmd_argv[i]);
    468 
    469 	cmd_argc = 0;
    470 	cmd_args = NULL;
    471 
    472 	while (1)
    473 	{
    474 // skip whitespace up to a /n
    475 		while (*text && *text <= ' ' && *text != '\n')
    476 		{
    477 			text++;
    478 		}
    479 
    480 		if (*text == '\n')
    481 		{	// a newline seperates commands in the buffer
    482 			text++;
    483 			break;
    484 		}
    485 
    486 		if (!*text)
    487 			return;
    488 
    489 		if (cmd_argc == 1)
    490 			 cmd_args = text;
    491 
    492 		text = COM_Parse (text);
    493 		if (!text)
    494 			return;
    495 
    496 		if (cmd_argc < MAX_ARGS)
    497 		{
    498 			cmd_argv[cmd_argc] = Z_Malloc (Q_strlen(com_token)+1);
    499 			Q_strcpy (cmd_argv[cmd_argc], com_token);
    500 			cmd_argc++;
    501 		}
    502 	}
    503 
    504 }
    505 
    506 
    507 /*
    508 ============
    509 Cmd_AddCommand
    510 ============
    511 */
    512 void	Cmd_AddCommand (char *cmd_name, xcommand_t function)
    513 {
    514 	cmd_function_t	*cmd;
    515 
    516 	if (host_initialized)	// because hunk allocation would get stomped
    517 		Sys_Error ("Cmd_AddCommand after host_initialized");
    518 
    519 // fail if the command is a variable name
    520 	if (Cvar_VariableString(cmd_name)[0])
    521 	{
    522 		Con_Printf ("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
    523 		return;
    524 	}
    525 
    526 // fail if the command already exists
    527 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
    528 	{
    529 		if (!Q_strcmp (cmd_name, cmd->name))
    530 		{
    531 			Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
    532 			return;
    533 		}
    534 	}
    535 
    536 	cmd = Hunk_Alloc (sizeof(cmd_function_t));
    537 	cmd->name = cmd_name;
    538 	cmd->function = function;
    539 	cmd->next = cmd_functions;
    540 	cmd_functions = cmd;
    541 }
    542 
    543 /*
    544 ============
    545 Cmd_Exists
    546 ============
    547 */
    548 qboolean	Cmd_Exists (char *cmd_name)
    549 {
    550 	cmd_function_t	*cmd;
    551 
    552 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
    553 	{
    554 		if (!Q_strcmp (cmd_name,cmd->name))
    555 			return true;
    556 	}
    557 
    558 	return false;
    559 }
    560 
    561 
    562 
    563 /*
    564 ============
    565 Cmd_CompleteCommand
    566 ============
    567 */
    568 char *Cmd_CompleteCommand (char *partial)
    569 {
    570 	cmd_function_t	*cmd;
    571 	int				len;
    572 	cmdalias_t		*a;
    573 
    574 	len = Q_strlen(partial);
    575 
    576 	if (!len)
    577 		return NULL;
    578 
    579 // check for exact match
    580 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
    581 		if (!strcmp (partial,cmd->name))
    582 			return cmd->name;
    583 	for (a=cmd_alias ; a ; a=a->next)
    584 		if (!strcmp (partial, a->name))
    585 			return a->name;
    586 
    587 // check for partial match
    588 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
    589 		if (!strncmp (partial,cmd->name, len))
    590 			return cmd->name;
    591 	for (a=cmd_alias ; a ; a=a->next)
    592 		if (!strncmp (partial, a->name, len))
    593 			return a->name;
    594 
    595 	return NULL;
    596 }
    597 
    598 #ifndef SERVERONLY		// FIXME
    599 /*
    600 ===================
    601 Cmd_ForwardToServer
    602 
    603 adds the current command line as a clc_stringcmd to the client message.
    604 things like godmode, noclip, etc, are commands directed to the server,
    605 so when they are typed in at the console, they will need to be forwarded.
    606 ===================
    607 */
    608 void Cmd_ForwardToServer (void)
    609 {
    610 	if (cls.state == ca_disconnected)
    611 	{
    612 		Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0));
    613 		return;
    614 	}
    615 
    616 	if (cls.demoplayback)
    617 		return;		// not really connected
    618 
    619 	MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
    620 	SZ_Print (&cls.netchan.message, Cmd_Argv(0));
    621 	if (Cmd_Argc() > 1)
    622 	{
    623 		SZ_Print (&cls.netchan.message, " ");
    624 		SZ_Print (&cls.netchan.message, Cmd_Args());
    625 	}
    626 }
    627 
    628 // don't forward the first argument
    629 void Cmd_ForwardToServer_f (void)
    630 {
    631 	if (cls.state == ca_disconnected)
    632 	{
    633 		Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0));
    634 		return;
    635 	}
    636 
    637 	if (Q_strcasecmp(Cmd_Argv(1), "snap") == 0) {
    638 		Cbuf_InsertText ("snap\n");
    639 		return;
    640 	}
    641 
    642 	if (cls.demoplayback)
    643 		return;		// not really connected
    644 
    645 	if (Cmd_Argc() > 1)
    646 	{
    647 		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
    648 		SZ_Print (&cls.netchan.message, Cmd_Args());
    649 	}
    650 }
    651 #else
    652 void Cmd_ForwardToServer (void)
    653 {
    654 }
    655 #endif
    656 
    657 /*
    658 ============
    659 Cmd_ExecuteString
    660 
    661 A complete command line has been parsed, so try to execute it
    662 FIXME: lookupnoadd the token to speed search?
    663 ============
    664 */
    665 void	Cmd_ExecuteString (char *text)
    666 {
    667 	cmd_function_t	*cmd;
    668 	cmdalias_t		*a;
    669 
    670 	Cmd_TokenizeString (text);
    671 
    672 // execute the command line
    673 	if (!Cmd_Argc())
    674 		return;		// no tokens
    675 
    676 // check functions
    677 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
    678 	{
    679 		if (!Q_strcasecmp (cmd_argv[0],cmd->name))
    680 		{
    681 			if (!cmd->function)
    682 				Cmd_ForwardToServer ();
    683 			else
    684 				cmd->function ();
    685 			return;
    686 		}
    687 	}
    688 
    689 // check alias
    690 	for (a=cmd_alias ; a ; a=a->next)
    691 	{
    692 		if (!Q_strcasecmp (cmd_argv[0], a->name))
    693 		{
    694 			Cbuf_InsertText (a->value);
    695 			return;
    696 		}
    697 	}
    698 
    699 // check cvars
    700 	if (!Cvar_Command () && (cl_warncmd.value || developer.value))
    701 		Con_Printf ("Unknown command \"%s\"\n", Cmd_Argv(0));
    702 
    703 }
    704 
    705 
    706 
    707 /*
    708 ================
    709 Cmd_CheckParm
    710 
    711 Returns the position (1 to argc-1) in the command's argument list
    712 where the given parameter apears, or 0 if not present
    713 ================
    714 */
    715 int Cmd_CheckParm (char *parm)
    716 {
    717 	int i;
    718 
    719 	if (!parm)
    720 		Sys_Error ("Cmd_CheckParm: NULL");
    721 
    722 	for (i = 1; i < Cmd_Argc (); i++)
    723 		if (! Q_strcasecmp (parm, Cmd_Argv (i)))
    724 			return i;
    725 
    726 	return 0;
    727 }
    728 
    729 /*
    730 ============
    731 Cmd_Init
    732 ============
    733 */
    734 void Cmd_Init (void)
    735 {
    736 //
    737 // register our commands
    738 //
    739 	Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f);
    740 	Cmd_AddCommand ("exec",Cmd_Exec_f);
    741 	Cmd_AddCommand ("echo",Cmd_Echo_f);
    742 	Cmd_AddCommand ("alias",Cmd_Alias_f);
    743 	Cmd_AddCommand ("wait", Cmd_Wait_f);
    744 #ifndef SERVERONLY
    745 	Cmd_AddCommand ("cmd", Cmd_ForwardToServer_f);
    746 #endif
    747 }
    748 
    749