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 */
Cmd_Wait_f(void)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 */
Cbuf_Init(void)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 */
Cbuf_AddText(char * text)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 */
Cbuf_InsertText(char * text)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 */
Cbuf_Execute(void)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 */
Cmd_StuffCmds_f(void)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 */
Cmd_Exec_f(void)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 */
Cmd_Echo_f(void)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
CopyString(char * in)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
Cmd_Alias_f(void)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 */
Cmd_Argc(void)422 int Cmd_Argc (void)
423 {
424 return cmd_argc;
425 }
426
427 /*
428 ============
429 Cmd_Argv
430 ============
431 */
Cmd_Argv(int arg)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 */
Cmd_Args(void)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 */
Cmd_TokenizeString(char * text)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 */
Cmd_AddCommand(char * cmd_name,xcommand_t function)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 */
Cmd_Exists(char * cmd_name)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 */
Cmd_CompleteCommand(char * partial)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 */
Cmd_ForwardToServer(void)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
Cmd_ForwardToServer_f(void)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
Cmd_ForwardToServer(void)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 */
Cmd_ExecuteString(char * text)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 */
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
729 /*
730 ============
731 Cmd_Init
732 ============
733 */
Cmd_Init(void)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