1 /**
2 * @file
3 * @brief Script command processing module
4 * Command text buffering. Any number of commands can be added in a frame, from several different sources.
5 * Most commands come from either keyBindings or console line input, but remote
6 * servers can also send across commands and entire text files can be accessed.
7 *
8 * The + command line options are also added to the command buffer.
9 *
10 * Command execution takes a null terminated string, breaks it into tokens,
11 * then searches for a command or variable that matches the first token.
12 */
13
14 /*
15 Copyright (C) 1997-2001 Id Software, Inc.
16
17 This program is free software; you can redistribute it and/or
18 modify it under the terms of the GNU General Public License
19 as published by the Free Software Foundation; either version 2
20 of the License, or (at your option) any later version.
21
22 This program is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
25
26 See the GNU General Public License for more details.
27
28 You should have received a copy of the GNU General Public License
29 along with this program; if not, write to the Free Software
30 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31
32 */
33
34 #include "cmd.h"
35 #include "common.h"
36 #include "msg.h"
37 #include "../shared/parse.h"
38
39 void Cmd_ForwardToServer(void);
40 #define ALIAS_HASH_SIZE 32
41
42 #define MAX_ALIAS_NAME 32
43
44 typedef struct cmd_alias_s {
45 char name[MAX_ALIAS_NAME];
46 char* value;
47 bool archive; /**< store the alias and reload it on the next game start */
48 struct cmd_alias_s* hash_next;
49 struct cmd_alias_s* next;
50 } cmd_alias_t;
51
52 static cmd_alias_t* cmd_alias;
53 static cmd_alias_t* cmd_alias_hash[ALIAS_HASH_SIZE];
54 static bool cmdWait;
55 static bool cmdClosed;
56
57 #define ALIAS_LOOP_COUNT 16
58 static int alias_count; /* for detecting runaway loops */
59
60
61 /**
62 * @brief Reopens the command buffer for writing
63 * @sa Cmd_Close_f
64 */
Cmd_Open_f(void)65 static void Cmd_Open_f (void)
66 {
67 Com_DPrintf(DEBUG_COMMANDS, "Cmd_Close_f: command buffer opened again\n");
68 cmdClosed = false;
69 }
70
71 /**
72 * @brief Will no longer add any command to command buffer
73 * ...until cmd_close is false again
74 * @sa Cmd_Open_f
75 */
Cmd_Close_f(void)76 static void Cmd_Close_f (void)
77 {
78 Com_DPrintf(DEBUG_COMMANDS, "Cmd_Close_f: command buffer closed\n");
79 cmdClosed = true;
80 }
81
82 /**
83 * @brief Causes execution of the remainder of the command buffer to be delayed until
84 * next frame. This allows commands like:
85 * bind g "impulse 5; +attack; wait; -attack; impulse 2"
86 */
Cmd_Wait_f(void)87 static void Cmd_Wait_f (void)
88 {
89 cmdWait = true;
90 }
91
92 /*
93 =============================================================================
94 COMMAND BUFFER
95 =============================================================================
96 */
97
98 #define CMD_BUFFER_SIZE 8192
99 static sizebuf_t cmd_text;
100 static byte cmd_text_buf[CMD_BUFFER_SIZE];
101 static char defer_text_buf[CMD_BUFFER_SIZE];
102
103 /**
104 * @note The initial buffer will grow as needed.
105 */
Cbuf_Init(void)106 void Cbuf_Init (void)
107 {
108 SZ_Init(&cmd_text, cmd_text_buf, sizeof(cmd_text_buf));
109 }
110
111 /**
112 * @note Reset the Cbuf memory.
113 */
Cbuf_Shutdown(void)114 void Cbuf_Shutdown (void)
115 {
116 SZ_Init(&cmd_text, nullptr, 0);
117 }
118
119 /**
120 * @brief Adds command text at the end of the buffer
121 * @note Normally when a command is generate from the console or keyBindings, it will be added to the end of the command buffer.
122 */
Cbuf_AddText(const char * format,...)123 void Cbuf_AddText (const char* format, ...)
124 {
125 va_list argptr;
126 static char text[CMD_BUFFER_SIZE];
127 va_start(argptr, format);
128 Q_vsnprintf(text, sizeof(text), format, argptr);
129 va_end(argptr);
130
131 if (cmdClosed) {
132 if (!Q_strstart(text, "cmdopen")) {
133 Com_DPrintf(DEBUG_COMMANDS, "Cbuf_AddText: currently closed\n");
134 return;
135 }
136 }
137
138 const int l = strlen(text);
139
140 if (cmd_text.cursize + l >= cmd_text.maxsize) {
141 Com_Printf("Cbuf_AddText: overflow (%i) (%s)\n", cmd_text.maxsize, text);
142 Com_Printf("buffer content: %s\n", cmd_text_buf);
143 return;
144 }
145 SZ_Write(&cmd_text, text, l);
146 }
147
148
149 /**
150 * @brief Adds command text immediately after the current command
151 * @note Adds a @c \\n to the text
152 * @todo actually change the command buffer to do less copying
153 */
Cbuf_InsertText(const char * text)154 void Cbuf_InsertText (const char* text)
155 {
156 char* temp;
157 int templen;
158
159 if (Q_strnull(text))
160 return;
161
162 /* copy off any commands still remaining in the exec buffer */
163 templen = cmd_text.cursize;
164 if (templen) {
165 temp = Mem_AllocTypeN(char, templen);
166 memcpy(temp, cmd_text.data, templen);
167 SZ_Clear(&cmd_text);
168 } else {
169 temp = nullptr; /* shut up compiler */
170 }
171
172 /* add the entire text of the file */
173 Cbuf_AddText("%s\n", text);
174
175 /* add the copied off data */
176 if (templen) {
177 SZ_Write(&cmd_text, temp, templen);
178 Mem_Free(temp);
179 }
180 }
181
182
183 /**
184 * @brief Defers any outstanding commands.
185 *
186 * Used when loading a map, for example.
187 * Copies then clears the command buffer to a temporary area.
188 */
Cbuf_CopyToDefer(void)189 void Cbuf_CopyToDefer (void)
190 {
191 memcpy(defer_text_buf, cmd_text_buf, cmd_text.cursize);
192 defer_text_buf[cmd_text.cursize] = 0;
193 cmd_text.cursize = 0;
194 }
195
196 /**
197 * @brief Copies back any deferred commands.
198 */
Cbuf_InsertFromDefer(void)199 void Cbuf_InsertFromDefer (void)
200 {
201 Cbuf_InsertText(defer_text_buf);
202 defer_text_buf[0] = 0;
203 }
204
205 /**
206 * @sa Cmd_ExecuteString
207 * Pulls off @c \\n terminated lines of text from the command buffer and sends them
208 * through Cmd_ExecuteString, stopping when the buffer is empty.
209 * Normally called once per frame, but may be explicitly invoked.
210 * @note Do not call inside a command function!
211 */
Cbuf_Execute(void)212 void Cbuf_Execute (void)
213 {
214 unsigned int i;
215 char line[1024];
216
217 /* don't allow infinite alias loops */
218 alias_count = 0;
219
220 while (cmd_text.cursize) {
221 /* find a \n or ; line break */
222 char* text = (char*) cmd_text.data;
223 int quotes = 0;
224
225 for (i = 0; i < cmd_text.cursize; i++) {
226 if (text[i] == '"')
227 quotes++;
228 /* don't break if inside a quoted string */
229 if (!(quotes & 1) && text[i] == ';')
230 break;
231 if (text[i] == '\n')
232 break;
233 }
234
235 if (i > sizeof(line) - 1)
236 i = sizeof(line) - 1;
237
238 memcpy(line, text, i);
239 line[i] = 0;
240
241 /* delete the text from the command buffer and move remaining commands down
242 * this is necessary because commands (exec, alias) can insert data at the
243 * beginning of the text buffer */
244 if (i == cmd_text.cursize)
245 cmd_text.cursize = 0;
246 else {
247 i++;
248 cmd_text.cursize -= i;
249 memmove(text, text + i, cmd_text.cursize);
250 }
251
252 /* execute the command line */
253 Cmd_ExecuteString("%s", line);
254
255 if (cmdWait) {
256 /* skip out while text still remains in buffer, leaving it
257 * for next frame */
258 cmdWait = false;
259 break;
260 }
261 }
262 }
263
264
265 /**
266 * @brief Adds command line parameters as script statements
267 * Commands lead with a +, and continue until another +
268 * Set commands are added early, so they are guaranteed to be set before
269 * the client and server initialize for the first time.
270 * Other commands are added late, after all initialization is complete.
271 * @sa Cbuf_AddLateCommands
272 */
Cbuf_AddEarlyCommands(bool clear)273 void Cbuf_AddEarlyCommands (bool clear)
274 {
275 const int argc = Com_Argc();
276 for (int i = 1; i < argc; i++) {
277 const char* s = Com_Argv(i);
278 if (!Q_streq(s, "+set"))
279 continue;
280 Cbuf_AddText("set %s %s\n", Com_Argv(i + 1), Com_Argv(i + 2));
281 if (clear) {
282 Com_ClearArgv(i);
283 Com_ClearArgv(i + 1);
284 Com_ClearArgv(i + 2);
285 }
286 i += 2;
287 }
288 }
289
290 /**
291 * @brief Adds command line parameters as script statements
292 * @note Commands lead with a + and continue until another + or -
293 * @return true if any late commands were added
294 * @sa Cbuf_AddEarlyCommands
295 */
Cbuf_AddLateCommands(void)296 bool Cbuf_AddLateCommands (void)
297 {
298 /* build the combined string to parse from */
299 int s = 0;
300 const int argc = Com_Argc();
301 for (int i = 1; i < argc; i++) {
302 s += strlen(Com_Argv(i)) + 1;
303 }
304 if (!s)
305 return false;
306
307 char* text = Mem_AllocTypeN(char, s + 1);
308 for (int i = 1; i < argc; i++) {
309 Q_strcat(text, s, "%s", Com_Argv(i));
310 if (i != argc - 1)
311 Q_strcat(text, s, " ");
312 }
313
314 /* pull out the commands */
315 char* build = Mem_AllocTypeN(char, s + 1);
316
317 for (int i = 0; i < s - 1; i++) {
318 if (text[i] != '+')
319 continue;
320 i++;
321
322 int j;
323 for (j = i; text[j] != '+' && text[j] != '-' && text[j] != '\0'; j++) {}
324
325 const char c = text[j];
326 text[j] = '\0';
327
328 Q_strcat(build, s, "%s\n", text + i);
329 text[j] = c;
330 i = j - 1;
331 }
332
333 const bool ret = build[0] != '\0';
334 if (ret)
335 Cbuf_AddText("%s", build);
336
337 Mem_Free(text);
338 Mem_Free(build);
339
340 return ret;
341 }
342
343
344 /*
345 ==============================================================================
346 SCRIPT COMMANDS
347 ==============================================================================
348 */
349
Cmd_Exec_f(void)350 static void Cmd_Exec_f (void)
351 {
352 byte* f;
353 char* f2;
354 int len;
355
356 if (Cmd_Argc() != 2) {
357 Com_Printf("Usage: %s <filename> : execute a script file\n", Cmd_Argv(0));
358 return;
359 }
360
361 len = FS_LoadFile(Cmd_Argv(1), &f);
362 if (!f) {
363 Com_Printf("couldn't execute %s\n", Cmd_Argv(1));
364 return;
365 }
366 Com_Printf("executing %s\n", Cmd_Argv(1));
367
368 /* the file doesn't have a trailing 0, so we need to copy it off */
369 f2 = Mem_AllocTypeN(char, len + 1);
370 memcpy(f2, f, len);
371 /* make really sure that there is a newline */
372 f2[len] = 0;
373
374 Cbuf_InsertText(f2);
375
376 Mem_Free(f2);
377 FS_FreeFile(f);
378 }
379
380
381 /**
382 * @brief Just prints the rest of the line to the console
383 */
Cmd_Echo_f(void)384 static void Cmd_Echo_f (void)
385 {
386 int i;
387
388 for (i = 1; i < Cmd_Argc(); i++)
389 Com_Printf("%s ", Cmd_Argv(i));
390 Com_Printf("\n");
391 }
392
393 /**
394 * @brief Creates a new command that executes a command string (possibly ; separated)
395 */
Cmd_Alias_f(void)396 static void Cmd_Alias_f (void)
397 {
398 cmd_alias_t* a;
399 char cmd[MAX_STRING_CHARS];
400 size_t len;
401 unsigned int hash;
402 int i, c;
403 const char* s;
404
405 if (Cmd_Argc() == 1) {
406 Com_Printf("Current alias commands:\n");
407 for (a = cmd_alias; a; a = a->next)
408 Com_Printf("%s : %s\n", a->name, a->value);
409 return;
410 }
411
412 s = Cmd_Argv(1);
413 len = strlen(s);
414 if (len == 0)
415 return;
416
417 if (len >= MAX_ALIAS_NAME) {
418 Com_Printf("Alias name is too long\n");
419 return;
420 }
421
422 /* if the alias already exists, reuse it */
423 hash = Com_HashKey(s, ALIAS_HASH_SIZE);
424 for (a = cmd_alias_hash[hash]; a; a = a->hash_next) {
425 if (Q_streq(s, a->name)) {
426 Mem_Free(a->value);
427 break;
428 }
429 }
430
431 if (!a) {
432 a = Mem_PoolAllocType(cmd_alias_t, com_aliasSysPool);
433 a->next = cmd_alias;
434 /* cmd_alias_hash should be null on the first run */
435 a->hash_next = cmd_alias_hash[hash];
436 cmd_alias_hash[hash] = a;
437 cmd_alias = a;
438 }
439 Q_strncpyz(a->name, s, sizeof(a->name));
440
441 /* copy the rest of the command line */
442 cmd[0] = 0; /* start out with a null string */
443 c = Cmd_Argc();
444 for (i = 2; i < c; i++) {
445 Q_strcat(cmd, sizeof(cmd), "%s", Cmd_Argv(i));
446 if (i != (c - 1))
447 Q_strcat(cmd, sizeof(cmd), " ");
448 }
449
450 if (Q_streq(Cmd_Argv(0), "aliasa"))
451 a->archive = true;
452
453 a->value = Mem_PoolStrDup(cmd, com_aliasSysPool, 0);
454 }
455
456 /**
457 * @brief Write lines containing "aliasa alias value" for all aliases
458 * with the archive flag set to true
459 * @param f Filehandle to write the aliases to
460 */
Cmd_WriteAliases(qFILE * f)461 void Cmd_WriteAliases (qFILE *f)
462 {
463 cmd_alias_t* a;
464
465 for (a = cmd_alias; a; a = a->next)
466 if (a->archive) {
467 int i;
468 FS_Printf(f, "aliasa %s \"", a->name);
469 for (i = 0; i < strlen(a->value); i++) {
470 if (a->value[i] == '"')
471 FS_Printf(f, "\\\"");
472 else
473 FS_Printf(f, "%c", a->value[i]);
474 }
475 FS_Printf(f, "\"\n");
476 }
477 }
478
479 /*
480 =============================================================================
481 COMMAND EXECUTION
482 =============================================================================
483 */
484
485 #define CMD_HASH_SIZE 32
486
487 typedef struct cmd_function_s {
488 struct cmd_function_s* next;
489 struct cmd_function_s* hash_next;
490 const char* name;
491 const char* description;
492 xcommand_t function;
493 int (*completeParam) (const char* partial, const char** match);
494 void* userdata;
495 } cmd_function_t;
496
497 static int cmd_argc;
498 static char* cmd_argv[MAX_STRING_TOKENS];
499 static char cmd_args[MAX_STRING_CHARS];
500 static void* cmd_userdata;
501
502 static cmd_function_t* cmd_functions; /* possible commands to execute */
503 static cmd_function_t* cmd_functions_hash[CMD_HASH_SIZE];
504
505 /**
506 * @brief Return the number of arguments of the current command.
507 * "command parameter" will result in a @c argc of 2, not 1.
508 * @return the number of arguments including the command itself.
509 * @sa Cmd_Argv
510 */
Cmd_Argc(void)511 int Cmd_Argc (void)
512 {
513 return cmd_argc;
514 }
515
516 /**
517 * @brief Returns a given argument
518 * @param[in] arg The argument at position arg in cmd_argv. @c 0 will return the command name.
519 * @return The argument from @c cmd_argv
520 * @sa Cmd_Argc
521 */
Cmd_Argv(int arg)522 const char* Cmd_Argv (int arg)
523 {
524 if (arg >= cmd_argc)
525 return "";
526 return cmd_argv[arg];
527 }
528
529 /**
530 * @brief Returns a single string containing argv(1) to argv(argc()-1)
531 */
Cmd_Args(void)532 const char* Cmd_Args (void)
533 {
534 return cmd_args;
535 }
536
537 /**
538 * @brief Return the userdata of the called command
539 */
Cmd_Userdata(void)540 void* Cmd_Userdata (void)
541 {
542 return cmd_userdata;
543 }
544
545 /**
546 * @brief Clears the argv vector and set argc to zero
547 * @sa Cmd_TokenizeString
548 */
Cmd_BufClear(void)549 void Cmd_BufClear (void)
550 {
551 int i;
552
553 /* clear the args from the last string */
554 for (i = 0; i < cmd_argc; i++) {
555 Mem_Free(cmd_argv[i]);
556 cmd_argv[i] = nullptr;
557 }
558
559 cmd_argc = 0;
560 cmd_args[0] = 0;
561 cmd_userdata = nullptr;
562 }
563
564 /**
565 * @brief Parses the given string into command line tokens.
566 * @note @c cmd_argv and @c cmd_argv are filled and set here
567 * @note *cvars will be expanded unless they are in a quoted token
568 * @sa Com_MacroExpandString
569 * @param[in] text The text to parse and tokenize
570 * @param[in] macroExpand expand cvar string with their values
571 * @param[in] replaceWhitespaces Replace \\t and \\n to \t and \n
572 */
Cmd_TokenizeString(const char * text,bool macroExpand,bool replaceWhitespaces)573 void Cmd_TokenizeString (const char* text, bool macroExpand, bool replaceWhitespaces)
574 {
575 const char* expanded;
576
577 Cmd_BufClear();
578
579 /* macro expand the text */
580 if (macroExpand) {
581 expanded = Com_MacroExpandString(text);
582 if (expanded)
583 text = expanded;
584 }
585
586 while (1) {
587 /* skip whitespace up to a newline */
588 while (*text && *text <= ' ' && *text != '\n') {
589 text++;
590 }
591
592 if (*text == '\n') { /* a newline seperates commands in the buffer */
593 text++;
594 break;
595 }
596
597 if (!*text)
598 return;
599
600 /* set cmd_args to everything after the first arg */
601 if (cmd_argc == 1) {
602 Q_strncpyz(cmd_args, text, sizeof(cmd_args));
603 Com_Chop(cmd_args);
604 }
605
606 const char* com_token = Com_Parse(&text, nullptr, 0, replaceWhitespaces);
607 if (!text)
608 return;
609
610 if (cmd_argc < MAX_STRING_TOKENS) {
611 /* check first char of string if it is a variable */
612 if (com_token[0] == '*') {
613 com_token++;
614 com_token = Cvar_GetString(com_token);
615 }
616 assert(!cmd_argv[cmd_argc]);
617 cmd_argv[cmd_argc] = Mem_PoolStrDup(com_token, com_cmdSysPool, 0);
618 cmd_argc++;
619 }
620 }
621 }
622
623 /**
624 * @brief Returns the command description for a given command
625 * @param[in] cmd_name Command id in global command array
626 * @note never returns a nullptr pointer
627 * @todo - search alias, too
628 */
Cmd_GetCommandDesc(const char * cmd_name)629 const char* Cmd_GetCommandDesc (const char* cmd_name)
630 {
631 cmd_function_t* cmd;
632 char* sep = nullptr;
633 unsigned int hash;
634 char searchName[MAX_VAR];
635
636 /* remove parameters */
637 Q_strncpyz(searchName, cmd_name, sizeof(searchName));
638 sep = strstr(searchName, " ");
639 if (sep)
640 *sep = '\0';
641
642 /* fail if the command already exists */
643 hash = Com_HashKey(searchName, CMD_HASH_SIZE);
644 for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
645 if (!Q_streq(searchName, cmd->name))
646 continue;
647 if (cmd->description)
648 return cmd->description;
649 return "";
650 }
651 return "";
652 }
653
Cmd_GenericCompleteFunction(char const * candidate,char const * partial,char const ** match)654 bool Cmd_GenericCompleteFunction(char const* candidate, char const* partial, char const** match)
655 {
656 static char matchString[MAX_QPATH];
657
658 if (!Q_strstart(candidate, partial))
659 return false;
660
661 if (!*match) {
662 /* First match. */
663 Q_strncpyz(matchString, candidate, sizeof(matchString));
664 *match = matchString;
665 } else {
666 /* Subsequent match, determine common prefix with previous match(es). */
667 char* dst = matchString;
668 char const* src = candidate;
669 while (*dst == *src) {
670 ++dst;
671 ++src;
672 }
673 *dst = '\0';
674 }
675
676 return true;
677 }
678
679 /**
680 * @param[in] cmd_name The name the command we want to add the complete function
681 * @param[in] function The complete function pointer
682 * @sa Cmd_AddCommand
683 * @sa Cmd_CompleteCommandParameters
684 */
Cmd_AddParamCompleteFunction(const char * cmd_name,int (* function)(const char * partial,const char ** match))685 void Cmd_AddParamCompleteFunction (const char* cmd_name, int (*function)(const char* partial, const char** match))
686 {
687 cmd_function_t* cmd;
688 unsigned int hash;
689
690 if (!cmd_name || !cmd_name[0])
691 return;
692
693 hash = Com_HashKey(cmd_name, CMD_HASH_SIZE);
694 for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
695 if (Q_streq(cmd_name, cmd->name)) {
696 cmd->completeParam = function;
697 return;
698 }
699 }
700 }
701
702 /**
703 * @brief Fetches the userdata for a console command.
704 * @param[in] cmd_name The name the command we want to add edit
705 * @return @c nullptr if no userdata was set or the command wasn't found, the userdata
706 * pointer if it was found and set
707 * @sa Cmd_AddCommand
708 * @sa Cmd_CompleteCommandParameters
709 * @sa Cmd_AddUserdata
710 */
Cmd_GetUserdata(const char * cmd_name)711 void* Cmd_GetUserdata (const char* cmd_name)
712 {
713 cmd_function_t* cmd;
714 unsigned int hash;
715
716 if (!cmd_name || !cmd_name[0]) {
717 Com_Printf("Cmd_GetUserdata: Invalide parameter\n");
718 return nullptr;
719 }
720
721 hash = Com_HashKey(cmd_name, CMD_HASH_SIZE);
722 for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
723 if (Q_streq(cmd_name, cmd->name)) {
724 return cmd->userdata;
725 }
726 }
727
728 Com_Printf("Cmd_GetUserdata: '%s' not found\n", cmd_name);
729 return nullptr;
730 }
731
732 /**
733 * @brief Adds userdata to the console command.
734 * @param[in] cmd_name The name the command we want to add edit
735 * @param[in] userdata for this function
736 * @sa Cmd_AddCommand
737 * @sa Cmd_CompleteCommandParameters
738 * @sa Cmd_GetUserdata
739 */
Cmd_AddUserdata(const char * cmd_name,void * userdata)740 void Cmd_AddUserdata (const char* cmd_name, void* userdata)
741 {
742 cmd_function_t* cmd;
743 unsigned int hash;
744
745 if (!cmd_name || !cmd_name[0])
746 return;
747
748 hash = Com_HashKey(cmd_name, CMD_HASH_SIZE);
749 for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
750 if (Q_streq(cmd_name, cmd->name)) {
751 cmd->userdata = userdata;
752 return;
753 }
754 }
755 }
756
757 /**
758 * @brief Add a new command to the script interface
759 * @param[in] cmdName The name the command is available via script interface
760 * @param[in] function The function pointer
761 * @param[in] desc A short description of what the cmd does. It is shown for e.g. the tab
762 * completion or the command list.
763 * @sa Cmd_RemoveCommand
764 */
Cmd_AddCommand(const char * cmdName,xcommand_t function,const char * desc)765 void Cmd_AddCommand (const char* cmdName, xcommand_t function, const char* desc)
766 {
767 if (!Q_strvalid(cmdName))
768 return;
769
770 /* fail if the command is a variable name */
771 if (Cvar_GetString(cmdName)[0]) {
772 Com_Printf("Cmd_AddCommand: %s already defined as a var\n", cmdName);
773 return;
774 }
775
776 /* fail if the command already exists */
777 const unsigned int hash = Com_HashKey(cmdName, CMD_HASH_SIZE);
778 for (cmd_function_t* cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
779 if (Q_streq(cmdName, cmd->name)) {
780 Com_DPrintf(DEBUG_COMMANDS, "Cmd_AddCommand: %s already defined\n", cmdName);
781 return;
782 }
783 }
784
785 cmd_function_t* const cmd = Mem_PoolAllocType(cmd_function_t, com_cmdSysPool);
786 cmd->name = cmdName;
787 cmd->description = desc;
788 cmd->function = function;
789 cmd->completeParam = nullptr;
790 HASH_Add(cmd_functions_hash, cmd, hash);
791 cmd->next = cmd_functions;
792 cmd_functions = cmd;
793 }
794
795 /**
796 * @brief Removes a command from script interface
797 * @param[in] cmd_name The script interface function name to remove
798 * @sa Cmd_AddCommand
799 */
Cmd_RemoveCommand(const char * cmd_name)800 void Cmd_RemoveCommand (const char* cmd_name)
801 {
802 cmd_function_t* cmd, **back;
803 unsigned int hash;
804 hash = Com_HashKey(cmd_name, CMD_HASH_SIZE);
805 back = &cmd_functions_hash[hash];
806
807 while (1) {
808 cmd = *back;
809 if (!cmd) {
810 Com_Printf("Cmd_RemoveCommand: %s not added\n", cmd_name);
811 return;
812 }
813 if (!Q_strcasecmp(cmd_name, cmd->name)) {
814 *back = cmd->hash_next;
815 break;
816 }
817 back = &cmd->hash_next;
818 }
819
820 back = &cmd_functions;
821 while (1) {
822 cmd = *back;
823 if (!cmd) {
824 Com_Printf("Cmd_RemoveCommand: %s not added\n", cmd_name);
825 return;
826 }
827 if (Q_streq(cmd_name, cmd->name)) {
828 *back = cmd->next;
829 Mem_Free(cmd);
830 return;
831 }
832 back = &cmd->next;
833 }
834 }
835
836 /**
837 * @brief Checks whether a function exists already
838 * @param[in] cmd_name The script interface function name to search for
839 */
Cmd_Exists(const char * cmd_name)840 bool Cmd_Exists (const char* cmd_name)
841 {
842 cmd_function_t* cmd;
843 unsigned int hash;
844 hash = Com_HashKey(cmd_name, CMD_HASH_SIZE);
845
846 for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
847 if (Q_streq(cmd_name, cmd->name))
848 return true;
849 }
850
851 return false;
852 }
853
854 /**
855 * @brief Unix like tab completion for console commands parameters
856 * @param[in] command The command we try to complete the parameter for
857 * @param[in] partial The beginning of the parameter we try to complete
858 * @param[out] match The command we are writing back (if something was found)
859 * @sa Cvar_CompleteVariable
860 * @sa Key_CompleteCommand
861 */
Cmd_CompleteCommandParameters(const char * command,const char * partial,const char ** match)862 int Cmd_CompleteCommandParameters (const char* command, const char* partial, const char** match)
863 {
864 const cmd_function_t* cmd;
865 unsigned int hash;
866
867 /* check for partial matches in commands */
868 hash = Com_HashKey(command, CMD_HASH_SIZE);
869 for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
870 if (!Q_strcasecmp(command, cmd->name)) {
871 if (!cmd->completeParam)
872 return 0;
873 return cmd->completeParam(partial, match);
874 }
875 }
876 return 0;
877 }
878
879 /**
880 * @brief Unix like tab completion for console commands
881 * @param[in] partial The beginning of the command we try to complete
882 * @param[out] match The found entry of the list we are searching, in case of more than one entry their common suffix is returned.
883 * @sa Cvar_CompleteVariable
884 * @sa Key_CompleteCommand
885 */
Cmd_CompleteCommand(const char * partial,const char ** match)886 int Cmd_CompleteCommand (const char* partial, const char** match)
887 {
888 if (partial[0] == '\0')
889 return 0;
890
891 int n = 0;
892
893 /* check for partial matches in commands */
894 for (cmd_function_t const* cmd = cmd_functions; cmd; cmd = cmd->next) {
895 if (Cmd_GenericCompleteFunction(cmd->name, partial, match)) {
896 Com_Printf("[cmd] %s\n", cmd->name);
897 if (cmd->description)
898 Com_Printf(S_COLOR_GREEN " %s\n", cmd->description);
899 ++n;
900 }
901 }
902
903 /* and then aliases */
904 for (cmd_alias_t const* a = cmd_alias; a; a = a->next) {
905 if (Cmd_GenericCompleteFunction(a->name, partial, match)) {
906 Com_Printf("[ali] %s\n", a->name);
907 ++n;
908 }
909 }
910
911 return n;
912 }
913
Cmd_vExecuteString(const char * fmt,va_list ap)914 void Cmd_vExecuteString (const char* fmt, va_list ap)
915 {
916 char text[1024];
917
918 Q_vsnprintf(text, sizeof(text), fmt, ap);
919
920 const cmd_function_t* cmd;
921 const cmd_alias_t* a;
922 const char* str;
923 unsigned int hash;
924
925 Com_DPrintf(DEBUG_COMMANDS, "ExecuteString: '%s'\n", text);
926
927 Cmd_TokenizeString(text, true);
928
929 /* execute the command line */
930 if (!Cmd_Argc())
931 /* no tokens */
932 return;
933
934 str = Cmd_Argv(0);
935
936 /* check functions */
937 hash = Com_HashKey(str, CMD_HASH_SIZE);
938 for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
939 if (!Q_strcasecmp(str, cmd->name)) {
940 if (!cmd->function) { /* forward to server command */
941 Cmd_ExecuteString("cmd %s", text);
942 } else {
943 cmd_userdata = cmd->userdata;
944 cmd->function();
945 }
946 return;
947 }
948 }
949
950 /* check alias */
951 hash = Com_HashKey(str, ALIAS_HASH_SIZE);
952 for (a = cmd_alias_hash[hash]; a; a = a->hash_next) {
953 if (!Q_strcasecmp(str, a->name)) {
954 if (++alias_count == ALIAS_LOOP_COUNT) {
955 Com_Printf("ALIAS_LOOP_COUNT\n");
956 return;
957 }
958 Cbuf_InsertText(a->value);
959 return;
960 }
961 }
962
963 /* check cvars */
964 if (Cvar_Command())
965 return;
966
967 /* send it as a server command if we are connected */
968 Cmd_ForwardToServer();
969 }
970
971 /**
972 * @brief A complete command line has been parsed, so try to execute it
973 * @todo lookupnoadd the token to speed search?
974 */
Cmd_ExecuteString(const char * text,...)975 void Cmd_ExecuteString (const char* text, ...)
976 {
977 va_list ap;
978
979 va_start(ap, text);
980 Cmd_vExecuteString(text, ap);
981 va_end(ap);
982 }
983
984 /**
985 * @brief List all available script interface functions
986 */
Cmd_List_f(void)987 static void Cmd_List_f (void)
988 {
989 const cmd_function_t* cmd;
990 const cmd_alias_t* alias;
991 int i = 0, j = 0, c, l = 0;
992 const char* token = nullptr;
993
994 c = Cmd_Argc();
995
996 if (c == 2) {
997 token = Cmd_Argv(1);
998 l = strlen(token);
999 }
1000
1001 for (cmd = cmd_functions; cmd; cmd = cmd->next, i++) {
1002 if (c == 2 && strncmp(cmd->name, token, l)) {
1003 i--;
1004 continue;
1005 }
1006 Com_Printf("[cmd] %s\n", cmd->name);
1007 if (cmd->description)
1008 Com_Printf(S_COLOR_GREEN " %s\n", cmd->description);
1009 }
1010 /* check alias */
1011 for (alias = cmd_alias; alias; alias = alias->next, j++) {
1012 if (c == 2 && strncmp(alias->name, token, l)) {
1013 j--;
1014 continue;
1015 }
1016 Com_Printf("[ali] %s\n", alias->name);
1017 }
1018 Com_Printf("%i commands\n", i);
1019 Com_Printf("%i macros\n", j);
1020 }
1021
1022
1023 /**
1024 * @brief Autocomplete function for exec command
1025 * @sa Cmd_AddParamCompleteFunction
1026 */
Cmd_CompleteExecCommand(const char * partial,const char ** match)1027 static int Cmd_CompleteExecCommand (const char* partial, const char** match)
1028 {
1029 int n = 0;
1030 while (char const* filename = FS_NextFileFromFileList("*.cfg")) {
1031 if (Cmd_GenericCompleteFunction(filename, partial, match)) {
1032 Com_Printf("%s\n", filename);
1033 ++n;
1034 }
1035 }
1036 FS_NextFileFromFileList(nullptr);
1037
1038 return n;
1039 }
1040
1041 /**
1042 * @brief Dummy binding if you don't want unknown commands forwarded to the server
1043 */
Cmd_Dummy_f(void)1044 void Cmd_Dummy_f (void)
1045 {
1046 }
1047
1048 #ifdef DEBUG
1049 /**
1050 * @brief Tries to call every command (except the quit command) that's currently
1051 * in the command list - Use this function to test whether some console bindings
1052 * produce asserts or segfaults in some situations.
1053 */
Cmd_Test_f(void)1054 static void Cmd_Test_f (void)
1055 {
1056 cmd_function_t* cmd;
1057
1058 for (cmd = cmd_functions; cmd; cmd = cmd->next) {
1059 if (!Q_streq(cmd->name, "quit"))
1060 Cmd_ExecuteString("%s", cmd->name);
1061 }
1062 }
1063
Cmd_PrintDebugCommands(void)1064 void Cmd_PrintDebugCommands (void)
1065 {
1066 const cmd_function_t* cmd;
1067 const char* otherCommands[] = {"mem_stats", "cl_configstrings", "cl_userinfo", "devmap"};
1068 int num = lengthof(otherCommands);
1069
1070 Com_Printf("Debug commands:\n");
1071 for (cmd = cmd_functions; cmd; cmd = cmd->next) {
1072 if (Q_strstart(cmd->name, "debug_"))
1073 Com_Printf(" * %s\n %s\n", cmd->name, cmd->description);
1074 }
1075
1076 Com_Printf("Other useful commands:\n");
1077 while (num) {
1078 const char* desc = Cmd_GetCommandDesc(otherCommands[num - 1]);
1079 Com_Printf(" * %s\n %s\n", otherCommands[num - 1], desc);
1080 num--;
1081 }
1082 Com_Printf(" * sv debug_showall\n"
1083 " make everything visible to everyone\n"
1084 " * sv debug_actorinvlist\n"
1085 " Show the whole inv of all actors on the server console\n"
1086 );
1087 Com_Printf("\n");
1088 }
1089 #endif
1090
Cmd_Init(void)1091 void Cmd_Init (void)
1092 {
1093 /* register our commands */
1094 Cmd_AddCommand("cmdlist", Cmd_List_f, "List all commands to game console");
1095 Cmd_AddCommand("exec", Cmd_Exec_f, "Execute a script file");
1096 Cmd_AddParamCompleteFunction("exec", Cmd_CompleteExecCommand);
1097 Cmd_AddCommand("echo", Cmd_Echo_f, "Print to game console");
1098 Cmd_AddCommand("wait", Cmd_Wait_f);
1099 Cmd_AddCommand("alias", Cmd_Alias_f, "Creates a new command that executes a command string");
1100 Cmd_AddCommand("aliasa", Cmd_Alias_f, "Creates a new, persistent command that executes a command string");
1101 Cmd_AddCommand("cmdclose", Cmd_Close_f, "Close the command buffer");
1102 Cmd_AddCommand("cmdopen", Cmd_Open_f, "Open the command buffer again");
1103 #ifdef DEBUG
1104 Cmd_AddCommand("debug_cmdtest", Cmd_Test_f, "Calls every command in the current list");
1105 #endif
1106 }
1107
Cmd_Shutdown(void)1108 void Cmd_Shutdown (void)
1109 {
1110 OBJZERO(cmd_functions_hash);
1111 cmd_functions = nullptr;
1112
1113 OBJZERO(cmd_alias_hash);
1114 cmd_alias = nullptr;
1115 alias_count = 0;
1116
1117 OBJZERO(cmd_argv);
1118 cmd_argc = 0;
1119
1120 cmdWait = false;
1121 cmdClosed = false;
1122
1123 Mem_FreePool(com_cmdSysPool);
1124 }
1125