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