patch from Blub adding cprint (centerprint) console command, similar to
[divverent/darkplaces.git] / cmd.c
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 #define MAX_ALIAS_NAME  32
25 // this is the largest script file that can be executed in one step
26 // LordHavoc: inreased this from 8192 to 32768
27 // div0: increased this from 32k to 128k
28 #define CMDBUFSIZE 131072
29 // maximum number of parameters to a command
30 #define MAX_ARGS 80
31 // maximum tokenizable commandline length (counting NUL terminations)
32 #define CMD_TOKENIZELENGTH (MAX_INPUTLINE + MAX_ARGS)
33
34 typedef struct cmdalias_s
35 {
36         struct cmdalias_s *next;
37         char name[MAX_ALIAS_NAME];
38         char *value;
39 } cmdalias_t;
40
41 static cmdalias_t *cmd_alias;
42
43 static qboolean cmd_wait;
44
45 static mempool_t *cmd_mempool;
46
47 static char cmd_tokenizebuffer[CMD_TOKENIZELENGTH];
48 static int cmd_tokenizebufferpos = 0;
49
50 //=============================================================================
51
52 /*
53 ============
54 Cmd_Wait_f
55
56 Causes execution of the remainder of the command buffer to be delayed until
57 next frame.  This allows commands like:
58 bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
59 ============
60 */
61 static void Cmd_Wait_f (void)
62 {
63         cmd_wait = true;
64 }
65
66 /*
67 ============
68 Cmd_Centerprint_f
69
70 Print something to the center of the screen using SCR_Centerprint
71 ============
72 */
73 static void Cmd_Centerprint_f (void)
74 {
75         char msg[MAX_INPUTLINE];
76         unsigned int i, c, p;
77         c = Cmd_Argc();
78         if(c >= 2)
79         {
80                 strlcpy(msg, Cmd_Argv(1), sizeof(msg));
81                 for(i = 2; i < c; ++i)
82                 {
83                         strlcat(msg, " ", sizeof(msg));
84                         strlcat(msg, Cmd_Argv(i), sizeof(msg));
85                 }
86                 c = strlen(msg);
87                 for(p = 0, i = 0; i < c; ++i)
88                 {
89                         if(msg[i] == '\\')
90                         {
91                                 if(msg[i+1] == 'n')
92                                         msg[p++] = '\n';
93                                 else if(msg[i+1] == '\\')
94                                         msg[p++] = '\\';
95                                 else {
96                                         msg[p++] = '\\';
97                                         msg[p++] = msg[i+1];
98                                 }
99                                 ++i;
100                         } else {
101                                 msg[p++] = msg[i];
102                         }
103                 }
104                 msg[p] = '\0';
105                 SCR_CenterPrint(msg);
106         }
107 }
108
109 /*
110 =============================================================================
111
112                                                 COMMAND BUFFER
113
114 =============================================================================
115 */
116
117 static sizebuf_t        cmd_text;
118 static unsigned char            cmd_text_buf[CMDBUFSIZE];
119
120 /*
121 ============
122 Cbuf_AddText
123
124 Adds command text at the end of the buffer
125 ============
126 */
127 void Cbuf_AddText (const char *text)
128 {
129         int             l;
130
131         l = (int)strlen (text);
132
133         if (cmd_text.cursize + l >= cmd_text.maxsize)
134         {
135                 Con_Print("Cbuf_AddText: overflow\n");
136                 return;
137         }
138
139         SZ_Write (&cmd_text, (const unsigned char *)text, (int)strlen (text));
140 }
141
142
143 /*
144 ============
145 Cbuf_InsertText
146
147 Adds command text immediately after the current command
148 Adds a \n to the text
149 FIXME: actually change the command buffer to do less copying
150 ============
151 */
152 void Cbuf_InsertText (const char *text)
153 {
154         char    *temp;
155         int             templen;
156
157         // copy off any commands still remaining in the exec buffer
158         templen = cmd_text.cursize;
159         if (templen)
160         {
161                 temp = (char *)Mem_Alloc (tempmempool, templen);
162                 memcpy (temp, cmd_text.data, templen);
163                 SZ_Clear (&cmd_text);
164         }
165         else
166                 temp = NULL;
167
168         // add the entire text of the file
169         Cbuf_AddText (text);
170
171         // add the copied off data
172         if (temp != NULL)
173         {
174                 SZ_Write (&cmd_text, (const unsigned char *)temp, templen);
175                 Mem_Free (temp);
176         }
177 }
178
179 /*
180 ============
181 Cbuf_Execute
182 ============
183 */
184 static void Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias );
185 void Cbuf_Execute (void)
186 {
187         int i;
188         char *text;
189         char line[MAX_INPUTLINE];
190         char preprocessed[MAX_INPUTLINE];
191         char *firstchar;
192         int quotes;
193
194         // LordHavoc: making sure the tokenizebuffer doesn't get filled up by repeated crashes
195         cmd_tokenizebufferpos = 0;
196
197         while (cmd_text.cursize)
198         {
199 // find a \n or ; line break
200                 text = (char *)cmd_text.data;
201
202                 quotes = 0;
203                 for (i=0 ; i< cmd_text.cursize ; i++)
204                 {
205                         if (text[i] == '"')
206                                 quotes ^= 1;
207                         // make sure i doesn't get > cursize which causes a negative
208                         // size in memmove, which is fatal --blub
209                         if (i < (cmd_text.cursize-1) && (text[i] == '\\' && (text[i+1] == '"' || text[i+1] == '\\')))
210                                 i++;
211                         if ( !quotes &&  text[i] == ';')
212                                 break;  // don't break if inside a quoted string
213                         if (text[i] == '\r' || text[i] == '\n')
214                                 break;
215                 }
216
217                 // better than CRASHING on overlong input lines that may SOMEHOW enter the buffer
218                 if(i >= MAX_INPUTLINE)
219                 {
220                         Con_Printf("Warning: console input buffer had an overlong line. Ignored.\n");
221                         line[0] = 0;
222                 }
223                 else
224                 {
225                         memcpy (line, text, i);
226                         line[i] = 0;
227                 }
228
229 // delete the text from the command buffer and move remaining commands down
230 // this is necessary because commands (exec, alias) can insert data at the
231 // beginning of the text buffer
232
233                 if (i == cmd_text.cursize)
234                         cmd_text.cursize = 0;
235                 else
236                 {
237                         i++;
238                         cmd_text.cursize -= i;
239                         memmove (cmd_text.data, text+i, cmd_text.cursize);
240                 }
241
242 // execute the command line
243                 firstchar = line + strspn(line, " \t");
244                 if(
245                         (strncmp(firstchar, "alias", 5) || (firstchar[5] != ' ' && firstchar[5] != '\t'))
246                         &&
247                         (strncmp(firstchar, "bind", 4) || (firstchar[4] != ' ' && firstchar[4] != '\t'))
248                         &&
249                         (strncmp(firstchar, "in_bind", 7) || (firstchar[7] != ' ' && firstchar[7] != '\t'))
250                 )
251                 {
252                         Cmd_PreprocessString( line, preprocessed, sizeof(preprocessed), NULL );
253                         Cmd_ExecuteString (preprocessed, src_command);
254                 }
255                 else
256                 {
257                         Cmd_ExecuteString (line, src_command);
258                 }
259
260                 if (cmd_wait)
261                 {       // skip out while text still remains in buffer, leaving it
262                         // for next frame
263                         cmd_wait = false;
264                         break;
265                 }
266         }
267 }
268
269 /*
270 ==============================================================================
271
272                                                 SCRIPT COMMANDS
273
274 ==============================================================================
275 */
276
277 /*
278 ===============
279 Cmd_StuffCmds_f
280
281 Adds command line parameters as script statements
282 Commands lead with a +, and continue until a - or another +
283 quake +prog jctest.qp +cmd amlev1
284 quake -nosound +cmd amlev1
285 ===============
286 */
287 qboolean host_stuffcmdsrun = false;
288 void Cmd_StuffCmds_f (void)
289 {
290         int             i, j, l;
291         // this is for all commandline options combined (and is bounds checked)
292         char    build[MAX_INPUTLINE];
293
294         if (Cmd_Argc () != 1)
295         {
296                 Con_Print("stuffcmds : execute command line parameters\n");
297                 return;
298         }
299
300         // no reason to run the commandline arguments twice
301         if (host_stuffcmdsrun)
302                 return;
303
304         host_stuffcmdsrun = true;
305         build[0] = 0;
306         l = 0;
307         for (i = 0;i < com_argc;i++)
308         {
309                 if (com_argv[i] && com_argv[i][0] == '+' && (com_argv[i][1] < '0' || com_argv[i][1] > '9') && l + strlen(com_argv[i]) - 1 <= sizeof(build) - 1)
310                 {
311                         j = 1;
312                         while (com_argv[i][j])
313                                 build[l++] = com_argv[i][j++];
314                         i++;
315                         for (;i < com_argc;i++)
316                         {
317                                 if (!com_argv[i])
318                                         continue;
319                                 if ((com_argv[i][0] == '+' || com_argv[i][0] == '-') && (com_argv[i][1] < '0' || com_argv[i][1] > '9'))
320                                         break;
321                                 if (l + strlen(com_argv[i]) + 4 > sizeof(build) - 1)
322                                         break;
323                                 build[l++] = ' ';
324                                 if (strchr(com_argv[i], ' '))
325                                         build[l++] = '\"';
326                                 for (j = 0;com_argv[i][j];j++)
327                                         build[l++] = com_argv[i][j];
328                                 if (strchr(com_argv[i], ' '))
329                                         build[l++] = '\"';
330                         }
331                         build[l++] = '\n';
332                         i--;
333                 }
334         }
335         // now terminate the combined string and prepend it to the command buffer
336         // we already reserved space for the terminator
337         build[l++] = 0;
338         Cbuf_InsertText (build);
339 }
340
341
342 /*
343 ===============
344 Cmd_Exec_f
345 ===============
346 */
347 static void Cmd_Exec_f (void)
348 {
349         char *f;
350
351         if (Cmd_Argc () != 2)
352         {
353                 Con_Print("exec <filename> : execute a script file\n");
354                 return;
355         }
356
357         f = (char *)FS_LoadFile (Cmd_Argv(1), tempmempool, false, NULL);
358         if (!f)
359         {
360                 Con_Printf("couldn't exec %s\n",Cmd_Argv(1));
361                 return;
362         }
363         Con_Printf("execing %s\n",Cmd_Argv(1));
364
365         // if executing default.cfg for the first time, lock the cvar defaults
366         // it may seem backwards to insert this text BEFORE the default.cfg
367         // but Cbuf_InsertText inserts before, so this actually ends up after it.
368         if (!strcmp(Cmd_Argv(1), "default.cfg"))
369                 Cbuf_InsertText("\ncvar_lockdefaults\n");
370
371         // insert newline after the text to make sure the last line is terminated (some text editors omit the trailing newline)
372         // (note: insertion order here is backwards from execution order, so this adds it after the text, by calling it before...)
373         Cbuf_InsertText ("\n");
374         Cbuf_InsertText (f);
375         Mem_Free(f);
376 }
377
378
379 /*
380 ===============
381 Cmd_Echo_f
382
383 Just prints the rest of the line to the console
384 ===============
385 */
386 static void Cmd_Echo_f (void)
387 {
388         int             i;
389
390         for (i=1 ; i<Cmd_Argc() ; i++)
391                 Con_Printf("%s ",Cmd_Argv(i));
392         Con_Print("\n");
393 }
394
395 // DRESK - 5/14/06
396 // Support Doom3-style Toggle Console Command
397 /*
398 ===============
399 Cmd_Toggle_f
400
401 Toggles a specified console variable amongst the values specified (default is 0 and 1)
402 ===============
403 */
404 static void Cmd_Toggle_f(void)
405 {
406         // Acquire Number of Arguments
407         int nNumArgs = Cmd_Argc();
408
409         if(nNumArgs == 1)
410                 // No Arguments Specified; Print Usage
411                 Con_Print("Toggle Console Variable - Usage\n  toggle <variable> - toggles between 0 and 1\n  toggle <variable> <value> - toggles between 0 and <value>\n  toggle <variable> [string 1] [string 2]...[string n] - cycles through all strings\n");
412         else
413         { // Correct Arguments Specified
414                 // Acquire Potential CVar
415                 cvar_t* cvCVar = Cvar_FindVar( Cmd_Argv(1) );
416
417                 if(cvCVar != NULL)
418                 { // Valid CVar
419                         if(nNumArgs == 2)
420                         { // Default Usage
421                                 if(cvCVar->integer)
422                                         Cvar_SetValueQuick(cvCVar, 0);
423                                 else
424                                         Cvar_SetValueQuick(cvCVar, 1);
425                         }
426                         else
427                         if(nNumArgs == 3)
428                         { // 0 and Specified Usage
429                                 if(cvCVar->integer == atoi(Cmd_Argv(2) ) )
430                                         // CVar is Specified Value; // Reset to 0
431                                         Cvar_SetValueQuick(cvCVar, 0);
432                                 else
433                                 if(cvCVar->integer == 0)
434                                         // CVar is 0; Specify Value
435                                         Cvar_SetQuick(cvCVar, Cmd_Argv(2) );
436                                 else
437                                         // CVar does not match; Reset to 0
438                                         Cvar_SetValueQuick(cvCVar, 0);
439                         }
440                         else
441                         { // Variable Values Specified
442                                 int nCnt;
443                                 int bFound = 0;
444
445                                 for(nCnt = 2; nCnt < nNumArgs; nCnt++)
446                                 { // Cycle through Values
447                                         if( strcmp(cvCVar->string, Cmd_Argv(nCnt) ) == 0)
448                                         { // Current Value Located; Increment to Next
449                                                 if( (nCnt + 1) == nNumArgs)
450                                                         // Max Value Reached; Reset
451                                                         Cvar_SetQuick(cvCVar, Cmd_Argv(2) );
452                                                 else
453                                                         // Next Value
454                                                         Cvar_SetQuick(cvCVar, Cmd_Argv(nCnt + 1) );
455
456                                                 // End Loop
457                                                 nCnt = nNumArgs;
458                                                 // Assign Found
459                                                 bFound = 1;
460                                         }
461                                 }
462                                 if(!bFound)
463                                         // Value not Found; Reset to Original
464                                         Cvar_SetQuick(cvCVar, Cmd_Argv(2) );
465                         }
466
467                 }
468                 else
469                 { // Invalid CVar
470                         Con_Printf("ERROR : CVar '%s' not found\n", Cmd_Argv(1) );
471                 }
472         }
473 }
474
475 /*
476 ===============
477 Cmd_Alias_f
478
479 Creates a new command that executes a command string (possibly ; seperated)
480 ===============
481 */
482 static void Cmd_Alias_f (void)
483 {
484         cmdalias_t      *a;
485         char            cmd[MAX_INPUTLINE];
486         int                     i, c;
487         const char              *s;
488         size_t          alloclen;
489
490         if (Cmd_Argc() == 1)
491         {
492                 Con_Print("Current alias commands:\n");
493                 for (a = cmd_alias ; a ; a=a->next)
494                         Con_Printf("%s : %s\n", a->name, a->value);
495                 return;
496         }
497
498         s = Cmd_Argv(1);
499         if (strlen(s) >= MAX_ALIAS_NAME)
500         {
501                 Con_Print("Alias name is too long\n");
502                 return;
503         }
504
505         // if the alias already exists, reuse it
506         for (a = cmd_alias ; a ; a=a->next)
507         {
508                 if (!strcmp(s, a->name))
509                 {
510                         Z_Free (a->value);
511                         break;
512                 }
513         }
514
515         if (!a)
516         {
517                 cmdalias_t *prev, *current;
518
519                 a = (cmdalias_t *)Z_Malloc (sizeof(cmdalias_t));
520                 strlcpy (a->name, s, sizeof (a->name));
521                 // insert it at the right alphanumeric position
522                 for( prev = NULL, current = cmd_alias ; current && strcmp( current->name, a->name ) < 0 ; prev = current, current = current->next )
523                         ;
524                 if( prev ) {
525                         prev->next = a;
526                 } else {
527                         cmd_alias = a;
528                 }
529                 a->next = current;
530         }
531
532
533 // copy the rest of the command line
534         cmd[0] = 0;             // start out with a null string
535         c = Cmd_Argc();
536         for (i=2 ; i< c ; i++)
537         {
538                 strlcat (cmd, Cmd_Argv(i), sizeof (cmd));
539                 if (i != c)
540                         strlcat (cmd, " ", sizeof (cmd));
541         }
542         strlcat (cmd, "\n", sizeof (cmd));
543
544         alloclen = strlen (cmd) + 1;
545         a->value = (char *)Z_Malloc (alloclen);
546         memcpy (a->value, cmd, alloclen);
547 }
548
549 /*
550 =============================================================================
551
552                                         COMMAND EXECUTION
553
554 =============================================================================
555 */
556
557 typedef struct cmd_function_s
558 {
559         struct cmd_function_s *next;
560         const char *name;
561         const char *description;
562         xcommand_t consolefunction;
563         xcommand_t clientfunction;
564         qboolean csqcfunc;
565 } cmd_function_t;
566
567 static int cmd_argc;
568 static const char *cmd_argv[MAX_ARGS];
569 static const char *cmd_null_string = "";
570 static const char *cmd_args;
571 cmd_source_t cmd_source;
572
573
574 static cmd_function_t *cmd_functions;           // possible commands to execute
575
576 static const char *Cmd_GetDirectCvarValue(const char *varname, cmdalias_t *alias, qboolean *is_multiple)
577 {
578         cvar_t *cvar;
579         long argno;
580         char *endptr;
581
582         if(is_multiple)
583                 *is_multiple = false;
584
585         if(!varname || !*varname)
586                 return NULL;
587
588         if(alias)
589         {
590                 if(!strcmp(varname, "*"))
591                 {
592                         if(is_multiple)
593                                 *is_multiple = true;
594                         return Cmd_Args();
595                 }
596                 else if(varname[strlen(varname) - 1] == '-')
597                 {
598                         argno = strtol(varname, &endptr, 10);
599                         if(endptr == varname + strlen(varname) - 1)
600                         {
601                                 // whole string is a number, apart from the -
602                                 const char *p = Cmd_Args();
603                                 for(; argno > 1; --argno)
604                                         if(!COM_ParseToken_Console(&p))
605                                                 break;
606                                 if(p)
607                                 {
608                                         if(is_multiple)
609                                                 *is_multiple = true;
610
611                                         // kill pre-argument whitespace
612                                         for (;*p && *p <= ' ';p++)
613                                                 ;
614
615                                         return p;
616                                 }
617                         }
618                 }
619                 else
620                 {
621                         argno = strtol(varname, &endptr, 10);
622                         if(*endptr == 0)
623                         {
624                                 // whole string is a number
625                                 // NOTE: we already made sure we don't have an empty cvar name!
626                                 if(argno >= 0 && argno < Cmd_Argc())
627                                         return Cmd_Argv(argno);
628                         }
629                 }
630         }
631
632         if((cvar = Cvar_FindVar(varname)) && !(cvar->flags & CVAR_PRIVATE))
633                 return cvar->string;
634
635         return NULL;
636 }
637
638 qboolean Cmd_QuoteString(char *out, size_t outlen, const char *in, const char *quoteset)
639 {
640         qboolean quote_quot = !!strchr(quoteset, '"');
641         qboolean quote_backslash = !!strchr(quoteset, '\\');
642         qboolean quote_dollar = !!strchr(quoteset, '$');
643
644         while(*in)
645         {
646                 if(*in == '"' && quote_quot)
647                 {
648                         if(outlen <= 2)
649                         {
650                                 *out++ = 0;
651                                 return false;
652                         }
653                         *out++ = '\\'; --outlen;
654                         *out++ = '"'; --outlen;
655                 }
656                 else if(*in == '\\' && quote_backslash)
657                 {
658                         if(outlen <= 2)
659                         {
660                                 *out++ = 0;
661                                 return false;
662                         }
663                         *out++ = '\\'; --outlen;
664                         *out++ = '\\'; --outlen;
665                 }
666                 else if(*in == '$' && quote_dollar)
667                 {
668                         if(outlen <= 2)
669                         {
670                                 *out++ = 0;
671                                 return false;
672                         }
673                         *out++ = '$'; --outlen;
674                         *out++ = '$'; --outlen;
675                 }
676                 else
677                 {
678                         if(outlen <= 1)
679                         {
680                                 *out++ = 0;
681                                 return false;
682                         }
683                         *out++ = *in; --outlen;
684                 }
685                 ++in;
686         }
687         *out++ = 0;
688         return true;
689 }
690
691 static const char *Cmd_GetCvarValue(const char *var, size_t varlen, cmdalias_t *alias)
692 {
693         static char varname[MAX_INPUTLINE];
694         static char varval[MAX_INPUTLINE];
695         const char *varstr;
696         char *varfunc;
697
698         if(varlen >= MAX_INPUTLINE)
699                 varlen = MAX_INPUTLINE - 1;
700         memcpy(varname, var, varlen);
701         varname[varlen] = 0;
702         varfunc = strchr(varname, ' ');
703
704         if(varfunc)
705         {
706                 *varfunc = 0;
707                 ++varfunc;
708         }
709
710         if(*var == 0)
711         {
712                 // empty cvar name?
713                 return NULL;
714         }
715
716         varstr = NULL;
717
718         if(varname[0] == '$')
719                 varstr = Cmd_GetDirectCvarValue(Cmd_GetDirectCvarValue(varname + 1, alias, NULL), alias, NULL);
720         else
721         {
722                 qboolean is_multiple = false;
723                 // Exception: $* and $n- don't use the quoted form by default
724                 varstr = Cmd_GetDirectCvarValue(varname, alias, &is_multiple);
725                 if(is_multiple)
726                         varfunc = "asis";
727         }
728
729         if(!varstr)
730         {
731                 if(alias)
732                         Con_Printf("Warning: Could not expand $%s in alias %s\n", varname, alias->name);
733                 else
734                         Con_Printf("Warning: Could not expand $%s\n", varname);
735                 return NULL;
736         }
737
738         if(!varfunc || !strcmp(varfunc, "q")) // note: quoted form is default, use "asis" to override!
739         {
740                 // quote it so it can be used inside double quotes
741                 // we just need to replace " by \", and of course, double backslashes
742                 Cmd_QuoteString(varval, sizeof(varval), varstr, "\"\\");
743                 return varval;
744         }
745         else if(!strcmp(varfunc, "asis"))
746         {
747                 return varstr;
748         }
749         else
750                 Con_Printf("Unknown variable function %s\n", varfunc);
751
752         return varstr;
753 }
754
755 /*
756 Cmd_PreprocessString
757
758 Preprocesses strings and replaces $*, $param#, $cvar accordingly
759 */
760 static void Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ) {
761         const char *in;
762         size_t eat, varlen;
763         unsigned outlen;
764         const char *val;
765
766         // don't crash if there's no room in the outtext buffer
767         if( maxoutlen == 0 ) {
768                 return;
769         }
770         maxoutlen--; // because of \0
771
772         in = intext;
773         outlen = 0;
774
775         while( *in && outlen < maxoutlen ) {
776                 if( *in == '$' ) {
777                         // this is some kind of expansion, see what comes after the $
778                         in++;
779
780                         // The console does the following preprocessing:
781                         //
782                         // - $$ is transformed to a single dollar sign.
783                         // - $var or ${var} are expanded to the contents of the named cvar,
784                         //   with quotation marks and backslashes quoted so it can safely
785                         //   be used inside quotation marks (and it should always be used
786                         //   that way)
787                         // - ${var asis} inserts the cvar value as is, without doing this
788                         //   quoting
789                         // - prefix the cvar name with a dollar sign to do indirection;
790                         //   for example, if $x has the value timelimit, ${$x} will return
791                         //   the value of $timelimit
792                         // - when expanding an alias, the special variable name $* refers
793                         //   to all alias parameters, and a number refers to that numbered
794                         //   alias parameter, where the name of the alias is $0, the first
795                         //   parameter is $1 and so on; as a special case, $* inserts all
796                         //   parameters, without extra quoting, so one can use $* to just
797                         //   pass all parameters around. All parameters starting from $n
798                         //   can be referred to as $n- (so $* is equivalent to $1-).
799                         //
800                         // Note: when expanding an alias, cvar expansion is done in the SAME step
801                         // as alias expansion so that alias parameters or cvar values containing
802                         // dollar signs have no unwanted bad side effects. However, this needs to
803                         // be accounted for when writing complex aliases. For example,
804                         //   alias foo "set x NEW; echo $x"
805                         // actually expands to
806                         //   "set x NEW; echo OLD"
807                         // and will print OLD! To work around this, use a second alias:
808                         //   alias foo "set x NEW; foo2"
809                         //   alias foo2 "echo $x"
810                         //
811                         // Also note: lines starting with alias are exempt from cvar expansion.
812                         // If you want cvar expansion, write "alias" instead:
813                         //
814                         //   set x 1
815                         //   alias foo "echo $x"
816                         //   "alias" bar "echo $x"
817                         //   set x 2
818                         //
819                         // foo will print 2, because the variable $x will be expanded when the alias
820                         // gets expanded. bar will print 1, because the variable $x was expanded
821                         // at definition time. foo can be equivalently defined as
822                         //
823                         //   "alias" foo "echo $$x"
824                         //
825                         // because at definition time, $$ will get replaced to a single $.
826
827                         if( *in == '$' ) {
828                                 val = "$";
829                                 eat = 1;
830                         } else if(*in == '{') {
831                                 varlen = strcspn(in + 1, "}");
832                                 if(in[varlen + 1] == '}')
833                                 {
834                                         val = Cmd_GetCvarValue(in + 1, varlen, alias);
835                                         eat = varlen + 2;
836                                 }
837                                 else
838                                 {
839                                         // ran out of data?
840                                         val = NULL;
841                                         eat = varlen + 1;
842                                 }
843                         } else {
844                                 varlen = strspn(in, "*0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-");
845                                 val = Cmd_GetCvarValue(in, varlen, alias);
846                                 eat = varlen;
847                         }
848                         if(val)
849                         {
850                                 // insert the cvar value
851                                 while(*val && outlen < maxoutlen)
852                                         outtext[outlen++] = *val++;
853                                 in += eat;
854                         }
855                         else
856                         {
857                                 // copy the unexpanded text
858                                 outtext[outlen++] = '$';
859                                 while(eat && outlen < maxoutlen)
860                                 {
861                                         outtext[outlen++] = *in++;
862                                         --eat;
863                                 }
864                         }
865                 } else {
866                         outtext[outlen++] = *in++;
867                 }
868         }
869         outtext[outlen] = 0;
870 }
871
872 /*
873 ============
874 Cmd_ExecuteAlias
875
876 Called for aliases and fills in the alias into the cbuffer
877 ============
878 */
879 static void Cmd_ExecuteAlias (cmdalias_t *alias)
880 {
881         static char buffer[ MAX_INPUTLINE ];
882         static char buffer2[ MAX_INPUTLINE ];
883         Cmd_PreprocessString( alias->value, buffer, sizeof(buffer) - 2, alias );
884         // insert at start of command buffer, so that aliases execute in order
885         // (fixes bug introduced by Black on 20050705)
886
887         // Note: Cbuf_PreprocessString will be called on this string AGAIN! So we
888         // have to make sure that no second variable expansion takes place, otherwise
889         // alias parameters containing dollar signs can have bad effects.
890         Cmd_QuoteString(buffer2, sizeof(buffer2), buffer, "$");
891         Cbuf_InsertText( buffer2 );
892 }
893
894 /*
895 ========
896 Cmd_List
897
898         CmdList Added by EvilTypeGuy eviltypeguy@qeradiant.com
899         Thanks to Matthias "Maddes" Buecher, http://www.inside3d.com/qip/
900
901 ========
902 */
903 static void Cmd_List_f (void)
904 {
905         cmd_function_t *cmd;
906         const char *partial;
907         int len, count;
908
909         if (Cmd_Argc() > 1)
910         {
911                 partial = Cmd_Argv (1);
912                 len = (int)strlen(partial);
913         }
914         else
915         {
916                 partial = NULL;
917                 len = 0;
918         }
919
920         count = 0;
921         for (cmd = cmd_functions; cmd; cmd = cmd->next)
922         {
923                 if (partial && strncmp(partial, cmd->name, len))
924                         continue;
925                 Con_Printf("%s : %s\n", cmd->name, cmd->description);
926                 count++;
927         }
928
929         if (partial)
930                 Con_Printf("%i Command%s beginning with \"%s\"\n\n", count, (count > 1) ? "s" : "", partial);
931         else
932                 Con_Printf("%i Command%s\n\n", count, (count > 1) ? "s" : "");
933 }
934
935 /*
936 ============
937 Cmd_Init
938 ============
939 */
940 void Cmd_Init (void)
941 {
942         cmd_mempool = Mem_AllocPool("commands", 0, NULL);
943         // space for commands and script files
944         cmd_text.data = cmd_text_buf;
945         cmd_text.maxsize = sizeof(cmd_text_buf);
946         cmd_text.cursize = 0;
947 }
948
949 void Cmd_Init_Commands (void)
950 {
951 //
952 // register our commands
953 //
954         Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f, "execute commandline parameters (must be present in quake.rc script)");
955         Cmd_AddCommand ("exec",Cmd_Exec_f, "execute a script file");
956         Cmd_AddCommand ("echo",Cmd_Echo_f, "print a message to the console (useful in scripts)");
957         Cmd_AddCommand ("alias",Cmd_Alias_f, "create a script function (parameters are passed in as $1 through $9, and $* for all parameters)");
958         Cmd_AddCommand ("cmd", Cmd_ForwardToServer, "send a console commandline to the server (used by some mods)");
959         Cmd_AddCommand ("wait", Cmd_Wait_f, "make script execution wait for next rendered frame");
960         Cmd_AddCommand ("set", Cvar_Set_f, "create or change the value of a console variable");
961         Cmd_AddCommand ("seta", Cvar_SetA_f, "create or change the value of a console variable that will be saved to config.cfg");
962
963         // 2000-01-09 CmdList, CvarList commands By Matthias "Maddes" Buecher
964         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
965         Cmd_AddCommand ("cmdlist", Cmd_List_f, "lists all console commands beginning with the specified prefix");
966         Cmd_AddCommand ("cvarlist", Cvar_List_f, "lists all console variables beginning with the specified prefix");
967
968         Cmd_AddCommand ("cvar_lockdefaults", Cvar_LockDefaults_f, "stores the current values of all cvars into their default values, only used once during startup after parsing default.cfg");
969         Cmd_AddCommand ("cvar_resettodefaults_all", Cvar_ResetToDefaults_All_f, "sets all cvars to their locked default values");
970         Cmd_AddCommand ("cvar_resettodefaults_nosaveonly", Cvar_ResetToDefaults_NoSaveOnly_f, "sets all non-saved cvars to their locked default values (variables that will not be saved to config.cfg)");
971         Cmd_AddCommand ("cvar_resettodefaults_saveonly", Cvar_ResetToDefaults_SaveOnly_f, "sets all saved cvars to their locked default values (variables that will be saved to config.cfg)");
972
973         Cmd_AddCommand ("cprint", Cmd_Centerprint_f, "print something at the screen center");
974
975         // DRESK - 5/14/06
976         // Support Doom3-style Toggle Command
977         Cmd_AddCommand( "toggle", Cmd_Toggle_f, "toggles a console variable's values (use for more info)");
978 }
979
980 /*
981 ============
982 Cmd_Shutdown
983 ============
984 */
985 void Cmd_Shutdown(void)
986 {
987         Mem_FreePool(&cmd_mempool);
988 }
989
990 /*
991 ============
992 Cmd_Argc
993 ============
994 */
995 int             Cmd_Argc (void)
996 {
997         return cmd_argc;
998 }
999
1000 /*
1001 ============
1002 Cmd_Argv
1003 ============
1004 */
1005 const char *Cmd_Argv (int arg)
1006 {
1007         if (arg >= cmd_argc )
1008                 return cmd_null_string;
1009         return cmd_argv[arg];
1010 }
1011
1012 /*
1013 ============
1014 Cmd_Args
1015 ============
1016 */
1017 const char *Cmd_Args (void)
1018 {
1019         return cmd_args;
1020 }
1021
1022
1023 /*
1024 ============
1025 Cmd_TokenizeString
1026
1027 Parses the given string into command line tokens.
1028 ============
1029 */
1030 // AK: This function should only be called from ExcuteString because the current design is a bit of an hack
1031 static void Cmd_TokenizeString (const char *text)
1032 {
1033         int l;
1034
1035         cmd_argc = 0;
1036         cmd_args = NULL;
1037
1038         while (1)
1039         {
1040                 // skip whitespace up to a /n
1041                 while (*text && *text <= ' ' && *text != '\r' && *text != '\n')
1042                         text++;
1043
1044                 // line endings:
1045                 // UNIX: \n
1046                 // Mac: \r
1047                 // Windows: \r\n
1048                 if (*text == '\n' || *text == '\r')
1049                 {
1050                         // a newline separates commands in the buffer
1051                         if (*text == '\r' && text[1] == '\n')
1052                                 text++;
1053                         text++;
1054                         break;
1055                 }
1056
1057                 if (!*text)
1058                         return;
1059
1060                 if (cmd_argc == 1)
1061                         cmd_args = text;
1062
1063                 if (!COM_ParseToken_Console(&text))
1064                         return;
1065
1066                 if (cmd_argc < MAX_ARGS)
1067                 {
1068                         l = (int)strlen(com_token) + 1;
1069                         if (cmd_tokenizebufferpos + l > CMD_TOKENIZELENGTH)
1070                         {
1071                                 Con_Printf("Cmd_TokenizeString: ran out of %i character buffer space for command arguements\n", CMD_TOKENIZELENGTH);
1072                                 break;
1073                         }
1074                         memcpy (cmd_tokenizebuffer + cmd_tokenizebufferpos, com_token, l);
1075                         cmd_argv[cmd_argc] = cmd_tokenizebuffer + cmd_tokenizebufferpos;
1076                         cmd_tokenizebufferpos += l;
1077                         cmd_argc++;
1078                 }
1079         }
1080 }
1081
1082
1083 /*
1084 ============
1085 Cmd_AddCommand
1086 ============
1087 */
1088 void Cmd_AddCommand_WithClientCommand (const char *cmd_name, xcommand_t consolefunction, xcommand_t clientfunction, const char *description)
1089 {
1090         cmd_function_t *cmd;
1091         cmd_function_t *prev, *current;
1092
1093 // fail if the command is a variable name
1094         if (Cvar_FindVar( cmd_name ))
1095         {
1096                 Con_Printf("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
1097                 return;
1098         }
1099
1100 // fail if the command already exists
1101         for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
1102         {
1103                 if (!strcmp (cmd_name, cmd->name))
1104                 {
1105                         if (consolefunction || clientfunction)
1106                         {
1107                                 Con_Printf("Cmd_AddCommand: %s already defined\n", cmd_name);
1108                                 return;
1109                         }
1110                         else    //[515]: csqc
1111                         {
1112                                 cmd->csqcfunc = true;
1113                                 return;
1114                         }
1115                 }
1116         }
1117
1118         cmd = (cmd_function_t *)Mem_Alloc(cmd_mempool, sizeof(cmd_function_t));
1119         cmd->name = cmd_name;
1120         cmd->consolefunction = consolefunction;
1121         cmd->clientfunction = clientfunction;
1122         cmd->description = description;
1123         if(!consolefunction && !clientfunction)                 //[515]: csqc
1124                 cmd->csqcfunc = true;
1125         cmd->next = cmd_functions;
1126
1127 // insert it at the right alphanumeric position
1128         for( prev = NULL, current = cmd_functions ; current && strcmp( current->name, cmd->name ) < 0 ; prev = current, current = current->next )
1129                 ;
1130         if( prev ) {
1131                 prev->next = cmd;
1132         } else {
1133                 cmd_functions = cmd;
1134         }
1135         cmd->next = current;
1136 }
1137
1138 void Cmd_AddCommand (const char *cmd_name, xcommand_t function, const char *description)
1139 {
1140         Cmd_AddCommand_WithClientCommand (cmd_name, function, NULL, description);
1141 }
1142
1143 /*
1144 ============
1145 Cmd_Exists
1146 ============
1147 */
1148 qboolean Cmd_Exists (const char *cmd_name)
1149 {
1150         cmd_function_t  *cmd;
1151
1152         for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
1153                 if (!strcmp (cmd_name,cmd->name))
1154                         return true;
1155
1156         return false;
1157 }
1158
1159
1160 /*
1161 ============
1162 Cmd_CompleteCommand
1163 ============
1164 */
1165 const char *Cmd_CompleteCommand (const char *partial)
1166 {
1167         cmd_function_t *cmd;
1168         size_t len;
1169
1170         len = strlen(partial);
1171
1172         if (!len)
1173                 return NULL;
1174
1175 // check functions
1176         for (cmd = cmd_functions; cmd; cmd = cmd->next)
1177                 if (!strncasecmp(partial, cmd->name, len))
1178                         return cmd->name;
1179
1180         return NULL;
1181 }
1182
1183 /*
1184         Cmd_CompleteCountPossible
1185
1186         New function for tab-completion system
1187         Added by EvilTypeGuy
1188         Thanks to Fett erich@heintz.com
1189         Thanks to taniwha
1190
1191 */
1192 int Cmd_CompleteCountPossible (const char *partial)
1193 {
1194         cmd_function_t *cmd;
1195         size_t len;
1196         int h;
1197
1198         h = 0;
1199         len = strlen(partial);
1200
1201         if (!len)
1202                 return 0;
1203
1204         // Loop through the command list and count all partial matches
1205         for (cmd = cmd_functions; cmd; cmd = cmd->next)
1206                 if (!strncasecmp(partial, cmd->name, len))
1207                         h++;
1208
1209         return h;
1210 }
1211
1212 /*
1213         Cmd_CompleteBuildList
1214
1215         New function for tab-completion system
1216         Added by EvilTypeGuy
1217         Thanks to Fett erich@heintz.com
1218         Thanks to taniwha
1219
1220 */
1221 const char **Cmd_CompleteBuildList (const char *partial)
1222 {
1223         cmd_function_t *cmd;
1224         size_t len = 0;
1225         size_t bpos = 0;
1226         size_t sizeofbuf = (Cmd_CompleteCountPossible (partial) + 1) * sizeof (const char *);
1227         const char **buf;
1228
1229         len = strlen(partial);
1230         buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *));
1231         // Loop through the alias list and print all matches
1232         for (cmd = cmd_functions; cmd; cmd = cmd->next)
1233                 if (!strncasecmp(partial, cmd->name, len))
1234                         buf[bpos++] = cmd->name;
1235
1236         buf[bpos] = NULL;
1237         return buf;
1238 }
1239
1240 // written by LordHavoc
1241 void Cmd_CompleteCommandPrint (const char *partial)
1242 {
1243         cmd_function_t *cmd;
1244         size_t len = strlen(partial);
1245         // Loop through the command list and print all matches
1246         for (cmd = cmd_functions; cmd; cmd = cmd->next)
1247                 if (!strncasecmp(partial, cmd->name, len))
1248                         Con_Printf("%s : %s\n", cmd->name, cmd->description);
1249 }
1250
1251 /*
1252         Cmd_CompleteAlias
1253
1254         New function for tab-completion system
1255         Added by EvilTypeGuy
1256         Thanks to Fett erich@heintz.com
1257         Thanks to taniwha
1258
1259 */
1260 const char *Cmd_CompleteAlias (const char *partial)
1261 {
1262         cmdalias_t *alias;
1263         size_t len;
1264
1265         len = strlen(partial);
1266
1267         if (!len)
1268                 return NULL;
1269
1270         // Check functions
1271         for (alias = cmd_alias; alias; alias = alias->next)
1272                 if (!strncasecmp(partial, alias->name, len))
1273                         return alias->name;
1274
1275         return NULL;
1276 }
1277
1278 // written by LordHavoc
1279 void Cmd_CompleteAliasPrint (const char *partial)
1280 {
1281         cmdalias_t *alias;
1282         size_t len = strlen(partial);
1283         // Loop through the alias list and print all matches
1284         for (alias = cmd_alias; alias; alias = alias->next)
1285                 if (!strncasecmp(partial, alias->name, len))
1286                         Con_Printf("%s : %s\n", alias->name, alias->value);
1287 }
1288
1289
1290 /*
1291         Cmd_CompleteAliasCountPossible
1292
1293         New function for tab-completion system
1294         Added by EvilTypeGuy
1295         Thanks to Fett erich@heintz.com
1296         Thanks to taniwha
1297
1298 */
1299 int Cmd_CompleteAliasCountPossible (const char *partial)
1300 {
1301         cmdalias_t      *alias;
1302         size_t          len;
1303         int                     h;
1304
1305         h = 0;
1306
1307         len = strlen(partial);
1308
1309         if (!len)
1310                 return 0;
1311
1312         // Loop through the command list and count all partial matches
1313         for (alias = cmd_alias; alias; alias = alias->next)
1314                 if (!strncasecmp(partial, alias->name, len))
1315                         h++;
1316
1317         return h;
1318 }
1319
1320 /*
1321         Cmd_CompleteAliasBuildList
1322
1323         New function for tab-completion system
1324         Added by EvilTypeGuy
1325         Thanks to Fett erich@heintz.com
1326         Thanks to taniwha
1327
1328 */
1329 const char **Cmd_CompleteAliasBuildList (const char *partial)
1330 {
1331         cmdalias_t *alias;
1332         size_t len = 0;
1333         size_t bpos = 0;
1334         size_t sizeofbuf = (Cmd_CompleteAliasCountPossible (partial) + 1) * sizeof (const char *);
1335         const char **buf;
1336
1337         len = strlen(partial);
1338         buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *));
1339         // Loop through the alias list and print all matches
1340         for (alias = cmd_alias; alias; alias = alias->next)
1341                 if (!strncasecmp(partial, alias->name, len))
1342                         buf[bpos++] = alias->name;
1343
1344         buf[bpos] = NULL;
1345         return buf;
1346 }
1347
1348 void Cmd_ClearCsqcFuncs (void)
1349 {
1350         cmd_function_t *cmd;
1351         for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
1352                 cmd->csqcfunc = false;
1353 }
1354
1355 qboolean CL_VM_ConsoleCommand (const char *cmd);
1356 /*
1357 ============
1358 Cmd_ExecuteString
1359
1360 A complete command line has been parsed, so try to execute it
1361 FIXME: lookupnoadd the token to speed search?
1362 ============
1363 */
1364 void Cmd_ExecuteString (const char *text, cmd_source_t src)
1365 {
1366         int oldpos;
1367         cmd_function_t *cmd;
1368         cmdalias_t *a;
1369
1370         oldpos = cmd_tokenizebufferpos;
1371         cmd_source = src;
1372
1373         Cmd_TokenizeString (text);
1374
1375 // execute the command line
1376         if (!Cmd_Argc())
1377         {
1378                 cmd_tokenizebufferpos = oldpos;
1379                 return;         // no tokens
1380         }
1381
1382 // check functions
1383         for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
1384         {
1385                 if (!strcasecmp (cmd_argv[0],cmd->name))
1386                 {
1387                         if (cmd->csqcfunc && CL_VM_ConsoleCommand (text))       //[515]: csqc
1388                                 return;
1389                         switch (src)
1390                         {
1391                         case src_command:
1392                                 if (cmd->consolefunction)
1393                                         cmd->consolefunction ();
1394                                 else if (cmd->clientfunction)
1395                                 {
1396                                         if (cls.state == ca_connected)
1397                                         {
1398                                                 // forward remote commands to the server for execution
1399                                                 Cmd_ForwardToServer();
1400                                         }
1401                                         else
1402                                                 Con_Printf("Can not send command \"%s\", not connected.\n", Cmd_Argv(0));
1403                                 }
1404                                 else
1405                                         Con_Printf("Command \"%s\" can not be executed\n", Cmd_Argv(0));
1406                                 cmd_tokenizebufferpos = oldpos;
1407                                 return;
1408                         case src_client:
1409                                 if (cmd->clientfunction)
1410                                 {
1411                                         cmd->clientfunction ();
1412                                         cmd_tokenizebufferpos = oldpos;
1413                                         return;
1414                                 }
1415                                 break;
1416                         }
1417                         break;
1418                 }
1419         }
1420
1421         // if it's a client command and no command was found, say so.
1422         if (cmd_source == src_client)
1423         {
1424                 Con_Printf("player \"%s\" tried to %s\n", host_client->name, text);
1425                 return;
1426         }
1427
1428 // check alias
1429         for (a=cmd_alias ; a ; a=a->next)
1430         {
1431                 if (!strcasecmp (cmd_argv[0], a->name))
1432                 {
1433                         Cmd_ExecuteAlias(a);
1434                         cmd_tokenizebufferpos = oldpos;
1435                         return;
1436                 }
1437         }
1438
1439 // check cvars
1440         if (!Cvar_Command () && host_framecount > 0)
1441                 Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0));
1442
1443         cmd_tokenizebufferpos = oldpos;
1444 }
1445
1446
1447 /*
1448 ===================
1449 Cmd_ForwardStringToServer
1450
1451 Sends an entire command string over to the server, unprocessed
1452 ===================
1453 */
1454 void Cmd_ForwardStringToServer (const char *s)
1455 {
1456         char temp[128];
1457         if (cls.state != ca_connected)
1458         {
1459                 Con_Printf("Can't \"%s\", not connected\n", s);
1460                 return;
1461         }
1462
1463         if (!cls.netcon)
1464                 return;
1465
1466         // LordHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my
1467         // attention, it has been eradicated from here, its only (former) use in
1468         // all of darkplaces.
1469         if (cls.protocol == PROTOCOL_QUAKEWORLD)
1470                 MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
1471         else
1472                 MSG_WriteByte(&cls.netcon->message, clc_stringcmd);
1473         if ((!strncmp(s, "say ", 4) || !strncmp(s, "say_team ", 9)) && cl_locs_enable.integer)
1474         {
1475                 // say/say_team commands can replace % character codes with status info
1476                 while (*s)
1477                 {
1478                         if (*s == '%' && s[1])
1479                         {
1480                                 // handle proquake message macros
1481                                 temp[0] = 0;
1482                                 switch (s[1])
1483                                 {
1484                                 case 'l': // current location
1485                                         CL_Locs_FindLocationName(temp, sizeof(temp), cl.movement_origin);
1486                                         break;
1487                                 case 'h': // current health
1488                                         dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_HEALTH]);
1489                                         break;
1490                                 case 'a': // current armor
1491                                         dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_ARMOR]);
1492                                         break;
1493                                 case 'x': // current rockets
1494                                         dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_ROCKETS]);
1495                                         break;
1496                                 case 'c': // current cells
1497                                         dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_CELLS]);
1498                                         break;
1499                                 // silly proquake macros
1500                                 case 'd': // loc at last death
1501                                         CL_Locs_FindLocationName(temp, sizeof(temp), cl.lastdeathorigin);
1502                                         break;
1503                                 case 't': // current time
1504                                         dpsnprintf(temp, sizeof(temp), "%.0f:%.0f", floor(cl.time / 60), cl.time - floor(cl.time / 60) * 60);
1505                                         break;
1506                                 case 'r': // rocket launcher status ("I have RL", "I need rockets", "I need RL")
1507                                         if (!(cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER))
1508                                                 dpsnprintf(temp, sizeof(temp), "I need RL");
1509                                         else if (!cl.stats[STAT_ROCKETS])
1510                                                 dpsnprintf(temp, sizeof(temp), "I need rockets");
1511                                         else
1512                                                 dpsnprintf(temp, sizeof(temp), "I have RL");
1513                                         break;
1514                                 case 'p': // powerup status (outputs "quad" "pent" and "eyes" according to status)
1515                                         if (cl.stats[STAT_ITEMS] & IT_QUAD)
1516                                         {
1517                                                 if (temp[0])
1518                                                         strlcat(temp, " ", sizeof(temp));
1519                                                 strlcat(temp, "quad", sizeof(temp));
1520                                         }
1521                                         if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY)
1522                                         {
1523                                                 if (temp[0])
1524                                                         strlcat(temp, " ", sizeof(temp));
1525                                                 strlcat(temp, "pent", sizeof(temp));
1526                                         }
1527                                         if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY)
1528                                         {
1529                                                 if (temp[0])
1530                                                         strlcat(temp, " ", sizeof(temp));
1531                                                 strlcat(temp, "eyes", sizeof(temp));
1532                                         }
1533                                         break;
1534                                 case 'w': // weapon status (outputs "SSG:NG:SNG:GL:RL:LG" with the text between : characters omitted if you lack the weapon)
1535                                         if (cl.stats[STAT_ITEMS] & IT_SUPER_SHOTGUN)
1536                                                 strlcat(temp, "SSG", sizeof(temp));
1537                                         strlcat(temp, ":", sizeof(temp));
1538                                         if (cl.stats[STAT_ITEMS] & IT_NAILGUN)
1539                                                 strlcat(temp, "NG", sizeof(temp));
1540                                         strlcat(temp, ":", sizeof(temp));
1541                                         if (cl.stats[STAT_ITEMS] & IT_SUPER_NAILGUN)
1542                                                 strlcat(temp, "SNG", sizeof(temp));
1543                                         strlcat(temp, ":", sizeof(temp));
1544                                         if (cl.stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER)
1545                                                 strlcat(temp, "GL", sizeof(temp));
1546                                         strlcat(temp, ":", sizeof(temp));
1547                                         if (cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER)
1548                                                 strlcat(temp, "RL", sizeof(temp));
1549                                         strlcat(temp, ":", sizeof(temp));
1550                                         if (cl.stats[STAT_ITEMS] & IT_LIGHTNING)
1551                                                 strlcat(temp, "LG", sizeof(temp));
1552                                         break;
1553                                 default:
1554                                         // not a recognized macro, print it as-is...
1555                                         temp[0] = s[0];
1556                                         temp[1] = s[1];
1557                                         temp[2] = 0;
1558                                         break;
1559                                 }
1560                                 // write the resulting text
1561                                 SZ_Write(&cls.netcon->message, (unsigned char *)temp, strlen(temp));
1562                                 s += 2;
1563                                 continue;
1564                         }
1565                         MSG_WriteByte(&cls.netcon->message, *s);
1566                         s++;
1567                 }
1568                 MSG_WriteByte(&cls.netcon->message, 0);
1569         }
1570         else // any other command is passed on as-is
1571                 SZ_Write(&cls.netcon->message, (const unsigned char *)s, (int)strlen(s) + 1);
1572 }
1573
1574 /*
1575 ===================
1576 Cmd_ForwardToServer
1577
1578 Sends the entire command line over to the server
1579 ===================
1580 */
1581 void Cmd_ForwardToServer (void)
1582 {
1583         const char *s;
1584         if (!strcasecmp(Cmd_Argv(0), "cmd"))
1585         {
1586                 // we want to strip off "cmd", so just send the args
1587                 s = Cmd_Argc() > 1 ? Cmd_Args() : "";
1588         }
1589         else
1590         {
1591                 // we need to keep the command name, so send Cmd_Argv(0), a space and then Cmd_Args()
1592                 s = va("%s %s", Cmd_Argv(0), Cmd_Argc() > 1 ? Cmd_Args() : "");
1593         }
1594         // don't send an empty forward message if the user tries "cmd" by itself
1595         if (!s || !*s)
1596                 return;
1597         Cmd_ForwardStringToServer(s);
1598 }
1599
1600
1601 /*
1602 ================
1603 Cmd_CheckParm
1604
1605 Returns the position (1 to argc-1) in the command's argument list
1606 where the given parameter apears, or 0 if not present
1607 ================
1608 */
1609
1610 int Cmd_CheckParm (const char *parm)
1611 {
1612         int i;
1613
1614         if (!parm)
1615         {
1616                 Con_Printf ("Cmd_CheckParm: NULL");
1617                 return 0;
1618         }
1619
1620         for (i = 1; i < Cmd_Argc (); i++)
1621                 if (!strcasecmp (parm, Cmd_Argv (i)))
1622                         return i;
1623
1624         return 0;
1625 }
1626