1 /*
2 Copyright (C) 1997-2001 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 "qcommon.h"
23 #include "q_list.h"
24 
25 #define Cmd_Malloc( size )		        Z_TagMalloc( size, TAG_CMD )
26 #define Cmd_CopyString( string )		Z_TagCopyString( string, TAG_CMD )
27 
28 cmdAPI_t	cmd;
29 
30 /*
31 =============================================================================
32 
33 						COMMAND BUFFER
34 
35 =============================================================================
36 */
37 
38 byte			cmd_buffer_text[CMD_BUFFER_SIZE];
39 cmdbuf_t		cmd_buffer;
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 static void Cmd_Wait_f( void ) {
51 	if( Cmd_Argc() > 1 ) {
52 		cmd_buffer.waitCount = atoi( Cmd_Argv( 1 ) );
53 	} else {
54 		cmd_buffer.waitCount = 1;
55 	}
56 }
57 
58 /*
59 ============
60 Cbuf_Init
61 ============
62 */
Cbuf_Init(void)63 void Cbuf_Init( void ) {
64 	SZ_Init( &cmd_buffer.text, cmd_buffer_text, sizeof( cmd_buffer_text ) );
65 	cmd_buffer.exec = Cmd_ExecuteString;
66 }
67 
68 /*
69 ============
70 Cbuf_AddText
71 
72 Adds command text at the end of the buffer
73 ============
74 */
Cbuf_AddTextEx(cmdbuf_t * buf,const char * text)75 void Cbuf_AddTextEx( cmdbuf_t *buf, const char *text ) {
76 	int		l;
77 
78 	l = strlen( text );
79 
80 	if( buf->text.cursize + l >= buf->text.maxsize ) {
81 		Com_WPrintf( "Cbuf_AddText: overflow\n" );
82 		return;
83 	}
84 	SZ_Write( &buf->text, text, l );
85 }
86 
87 
88 /*
89 ============
90 Cbuf_InsertText
91 
92 Adds command text immediately after the current command.
93 Adds a \n to the text.
94 ============
95 */
Cbuf_InsertTextEx(cmdbuf_t * buf,const char * text)96 void Cbuf_InsertTextEx( cmdbuf_t *buf, const char *text ) {
97 	char	temp[CMD_BUFFER_SIZE];
98 	int		templen, l;
99 
100 	if( *text == 0 ) {
101 		return;
102 	}
103 
104 // copy off any commands still remaining in the exec buffer
105 	templen = buf->text.cursize;
106 	if( templen ) {
107 		memcpy( temp, buf->text.data, templen );
108 		SZ_Clear( &buf->text );
109 	}
110 
111 // add the entire text of the file
112 	l = strlen( text );
113 	if( templen + l >= buf->text.maxsize ) {
114 		Com_WPrintf( "Cbuf_InsertText: overflow\n" );
115 		return;
116 	}
117 
118 	SZ_Write( &buf->text, text, l );
119 
120 // add '\n' to the text
121 	if( text[l - 1] != '\n' ) {
122 		buf->text.data[buf->text.cursize++] = '\n';
123 	}
124 
125 // add the copied off data
126 	if( templen ) {
127 		SZ_Write( &buf->text, temp, templen );
128 	}
129 }
130 
131 /*
132 ============
133 Cbuf_ExecuteText
134 ============
135 */
Cbuf_ExecuteText(cbufExecWhen_t exec_when,const char * text)136 void Cbuf_ExecuteText( cbufExecWhen_t exec_when, const char *text ) {
137 	switch( exec_when ) {
138 	case EXEC_NOW:
139 		Cmd_ExecuteString( text );
140 		break;
141 	case EXEC_INSERT:
142 		Cbuf_InsertText( text );
143 		break;
144 	case EXEC_APPEND:
145 		Cbuf_AddText( text );
146 		break;
147 	default:
148 		Com_Error( ERR_FATAL, "Cbuf_ExecuteText: bad exec_when" );
149 	}
150 }
151 
152 /*
153 ============
154 Cbuf_Execute
155 ============
156 */
Cbuf_ExecuteEx(cmdbuf_t * buf)157 void Cbuf_ExecuteEx( cmdbuf_t *buf ) {
158 	int		i, bytes;
159 	char	*text;
160 	char	line[MAX_STRING_CHARS];
161 	int		quotes;
162 
163 	while( buf->text.cursize ) {
164 		if( buf->waitCount > 0 ) {
165 			// skip out while text still remains in buffer, leaving it
166 			// for next frame
167 			buf->waitCount--;
168             return;
169 		}
170 
171 // find a \n or ; line break
172 		text = ( char * )buf->text.data;
173 
174 		quotes = 0;
175 		for( i = 0; i < buf->text.cursize; i++ ) {
176 			if( text[i] == '"' )
177 				quotes++;
178 			if( !( quotes & 1 ) && text[i] == ';' )
179 				break;	// don't break if inside a quoted string
180 			if( text[i] == '\n' )
181 				break;
182 		}
183 
184         bytes = i;
185 
186         // strip trailing CR
187         if( bytes && text[ bytes - 1 ] == '\r' ) {
188             bytes--;
189         }
190 
191 		// check for overflow
192 		if( bytes > sizeof( line ) - 1 ) {
193 			bytes = sizeof( line ) - 1;
194 		}
195 
196 		memcpy( line, text, bytes );
197 		line[bytes] = 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 		if( i == buf->text.cursize ) {
203 			buf->text.cursize = 0;
204 		} else {
205 			i++;
206 			buf->text.cursize -= i;
207 			memmove( text, text + i, buf->text.cursize );
208 		}
209 
210 // execute the command line
211 		buf->exec( line );
212 
213 	}
214 
215 	buf->aliasCount = 0;		// don't allow infinite alias loops
216 }
217 
218 /*
219 ==============================================================================
220 
221 						SCRIPT COMMANDS
222 
223 ==============================================================================
224 */
225 
226 typedef struct cmdalias_s {
227 	listElem_t elem[2];
228 
229 	char	*value;
230 	char	name[1];
231 } cmdalias_t;
232 
233 #define ALIAS_HASH_SIZE	64
234 
235 static list_t		cmd_alias;
236 static list_t		cmd_aliasHash[ALIAS_HASH_SIZE];
237 
238 /*
239 ===============
240 Cmd_AliasFind
241 ===============
242 */
Cmd_AliasFind(const char * name)243 cmdalias_t *Cmd_AliasFind( const char *name ) {
244 	uint32 hash;
245 	cmdalias_t *alias;
246 	listElem_t *elem;
247 
248 	hash = Com_HashString( name, ALIAS_HASH_SIZE );
249 	for( elem = cmd_aliasHash[hash].first; elem; elem = elem->next ) {
250 		alias = ( cmdalias_t * )( elem - 1 );
251 		if( !strcmp( name, alias->name ) ) {
252 			return alias;
253 		}
254 	}
255 
256 	return NULL;
257 }
258 
Cmd_AliasCommand(const char * name)259 char *Cmd_AliasCommand( const char *name ) {
260 	cmdalias_t	*a;
261 
262 	a = Cmd_AliasFind( name );
263 	if( !a ) {
264 		return NULL;
265 	}
266 
267 	return a->value;
268 }
269 
Cmd_AliasSet(const char * name,const char * cmd)270 void Cmd_AliasSet( const char *name, const char *cmd ) {
271 	cmdalias_t	*a;
272 	uint32		hash;
273 
274 	// if the alias already exists, reuse it
275 	a = Cmd_AliasFind( name );
276 	if( a ) {
277 		Z_Free( a->value );
278 		a->value = Cmd_CopyString( cmd );
279 		return;
280 	}
281 
282 	a = Cmd_Malloc( sizeof( cmdalias_t ) + strlen( name ) );
283 	strcpy( a->name, name );
284 	a->value = Cmd_CopyString( cmd );
285 
286 	List_Append( &cmd_alias, &a->elem[0] );
287 
288 	hash = Com_HashString( name, ALIAS_HASH_SIZE );
289 	List_Append( &cmd_aliasHash[hash], &a->elem[1] );
290 }
291 
292 /*
293 ===============
294 Cmd_Alias_f
295 
296 Creates a new command that executes a command string (possibly ; seperated)
297 ===============
298 */
Cmd_Alias_f(void)299 void Cmd_Alias_f( void ) {
300 	cmdalias_t	*a;
301 	listElem_t	*elem;
302 	char		*s, *cmd;
303 
304 	if( Cmd_Argc() < 2 ) {
305 		if( !cmd_alias.first ) {
306 			Com_Printf( "No alias commands registered\n" );
307 			return;
308 		}
309 		Com_Printf( "Current alias commands:\n" );
310 		for( elem = cmd_alias.first ; elem; elem = elem->next ) {
311 			a = ( cmdalias_t * )elem;
312 			Com_Printf( "\"%s\" = \"%s\"\n", a->name, a->value );
313 		}
314 		return;
315 	}
316 
317 	s = Cmd_Argv( 1 );
318 	if( Cmd_Exists( s ) ) {
319 		Com_Printf( "\"%s\" already defined as a command\n", s );
320 		return;
321 	}
322 
323 	if( Cvar_Exists( s ) ) {
324 		Com_Printf( "\"%s\" already defined as a cvar\n", s );
325 		return;
326 	}
327 
328 	if( Cmd_Argc() < 3 ) {
329 		a = Cmd_AliasFind( s );
330 		if( a ) {
331 			Com_Printf( "\"%s\" = \"%s\"\n", a->name, a->value );
332 		} else {
333 			Com_Printf( "\"%s\" is undefined\n", s );
334 		}
335 		return;
336 	}
337 
338 	// copy the rest of the command line
339 	cmd = Cmd_ArgsFrom( 2 );
340 	Cmd_AliasSet( s, cmd );
341 }
342 
Cmd_UnAlias_f(void)343 static void Cmd_UnAlias_f( void ) {
344 	char *s;
345 	cmdalias_t	*a;
346 	listElem_t *elem, *next;
347 	uint32 hash;
348 
349 	if( Cmd_CheckParam( "-h", "--help" ) ) {
350 usage:
351 		Com_Printf( "Usage: %s [-h] [-a] [alias_name]\n"
352 			"-h|--help    : display this message\n"
353 			"-a|--all     : delete everything\n"
354 			"Either -a or alias_name should be given\n", Cmd_Argv( 0 ) );
355 		return;
356 	}
357 
358 	if( Cmd_CheckParam( "a", "all" ) ) {
359 		for( elem = cmd_alias.first; elem; elem = next ) {
360 			next = elem->next;
361 			a = ( cmdalias_t * )elem;
362 			Z_Free( a->value );
363 			Z_Free( a );
364 		}
365 		for( hash = 0; hash < ALIAS_HASH_SIZE; hash++ ) {
366 			List_Clear( &cmd_aliasHash[hash] );
367 		}
368 		List_Clear( &cmd_alias );
369 		Com_Printf( "Removed all aliases\n" );
370 		return;
371 	}
372 
373 	if( Cmd_Argc() < 2 ) {
374 		goto usage;
375 	}
376 	s = Cmd_Argv( 1 );
377 	a = Cmd_AliasFind( s );
378 	if( !a ) {
379 		Com_Printf( "\"%s\" is undefined\n", s );
380 		return;
381 	}
382 
383 	Z_Free( a->value );
384 
385 	List_DeleteElem( &a->elem[0] );
386 	List_DeleteElem( &a->elem[1] );
387 
388 	Z_Free( a );
389 }
390 
391 /*
392 =============================================================================
393 
394 					MESSAGE TRIGGERS
395 
396 =============================================================================
397 */
398 
399 typedef struct cmd_trigger_s {
400 	listElem_t	elem;
401 
402 	trigChannel_t	chan;
403 	char		*match;
404 	char		*command;
405 } cmd_trigger_t;
406 
407 static list_t	cmd_triggers;
408 
409 static struct {
410     const char *name;
411     const char *desc;
412     trigChannel_t chan;
413 } triggers[] = {
414     { "client", "misc client events", TRIG_CLIENT_SYSTEM },
415     { "chat", "client chat messages", TRIG_CLIENT_CHAT },
416     { "print", "client print messages (default)", TRIG_CLIENT_PRINT },
417     { "cprint", "client centerprint messages", TRIG_CLIENT_CENTERPRINT },
418     { "server", "misc server events", TRIG_SERVER_SYSTEM },
419     { "bprint", "game DLL broadcast messages", TRIG_SERVER_BPRINT },
420     { "dprint", "game DLL console messages", TRIG_SERVER_DPRINT },
421     { NULL, 0 }
422 };
423 
chan2string(trigChannel_t chan)424 static const char *chan2string( trigChannel_t chan ) {
425     int i;
426 
427     for( i = 0; triggers[i].name; i++ ) {
428         if( triggers[i].chan == chan ) {
429             return triggers[i].name;
430         }
431     }
432     return "unknown";
433 }
434 
435 /*
436 ============
437 Cmd_Trigger_f
438 ============
439 */
Cmd_Trigger_f(void)440 static void Cmd_Trigger_f( void ) {
441 	cmd_trigger_t *trigger;
442 	listElem_t *elem;
443 	char *command, *match, *name;
444 	int cmdLength, matchLength;
445 	trigChannel_t chan;
446     int i;
447 
448 	if( Cmd_Argc() == 1 ) {
449 		if( !cmd_triggers.first ) {
450 			Com_Printf( "No triggers registered\n" );
451 			return;
452 		}
453 		Com_Printf( "Current message triggers:\n" );
454 		for( elem = cmd_triggers.first; elem; elem = elem->next ) {
455 			trigger = ( cmd_trigger_t * )elem;
456 			Com_Printf( "\"%s\" = \"%s\" [%s]\n",
457 				trigger->match, trigger->command,
458                     chan2string( trigger->chan ) );
459 		}
460 		return;
461 	}
462 
463 	if( Cmd_Argc() < 3 ) {
464 usage:
465 		Com_Printf( "Usage: %s <command> <match> [channel]\n", Cmd_Argv( 0 ) );
466 		Com_Printf( "Valid channels:\n" );
467 		for( i = 0; triggers[i].name; i++ ) {
468 			Com_Printf( "%6s - %s\n", triggers[i].name, triggers[i].desc );
469 		}
470 		return;
471 	}
472 
473 	command = Cmd_Argv( 1 );
474 	match = Cmd_Argv( 2 );
475 
476 	chan = TRIG_CLIENT_PRINT;
477 	if( Cmd_Argc() > 3 ) {
478 		name = Cmd_Argv( 3 );
479 		for( i = 0; triggers[i].name; i++ ) {
480 			if( !strcmp( name, triggers[i].name ) ) {
481                 chan = triggers[i].chan;
482 				break;
483 			}
484 		}
485 		if( !triggers[i].name ) {
486 			Com_Printf( "Unknown channel '%s'\n", name );
487             goto usage;
488 		}
489 	}
490 
491 	// don't create the same trigger twice
492 	for( elem = cmd_triggers.first; elem; elem = elem->next ) {
493 		trigger = ( cmd_trigger_t * )elem;
494 		if( trigger->chan == chan &&
495 			!strcmp( trigger->command, command ) &&
496 			!strcmp( trigger->match, match ) )
497 		{
498 			return;
499 		}
500 	}
501 
502 	cmdLength = strlen( command ) + 1;
503 	matchLength = strlen( match ) + 1;
504 
505 	trigger = Cmd_Malloc( sizeof( cmd_trigger_t ) +
506             cmdLength + matchLength );
507 	trigger->chan = chan;
508 	trigger->command = ( char * )( trigger + 1 );
509 	trigger->match = trigger->command + cmdLength;
510 	List_Append( &cmd_triggers, trigger );
511 
512 	strcpy( trigger->command, command );
513 	strcpy( trigger->match, match );
514 }
515 
516 /*
517 ============
518 Cmd_UnTrigger_f
519 ============
520 */
Cmd_UnTrigger_f(void)521 static void Cmd_UnTrigger_f( void ) {
522 	cmd_trigger_t *trigger;
523 	listElem_t *elem, *next;
524 	char *command, *match;
525 
526 	if( Cmd_CheckParam( "-h", "--help" ) ) {
527 usage:
528 		Com_Printf( "Usage: %s [-h] [-a] [-c <command>] [-m <match>]\n"
529 			"-h|--help    : display this message\n"
530 			"-a|--all     : delete everything\n"
531 			"-c|--command : delete by command\n"
532 			"-m|--match   : delete by match\n"
533 			"Either -a or combination of -c and -m should be given\n",
534                 Cmd_Argv( 0 ) );
535 		return;
536 	}
537 
538 	if( Cmd_CheckParam( "a", "all" ) ) {
539 		for( elem = cmd_triggers.first; elem; elem = next ) {
540 			next = elem->next;
541 			Z_Free( elem );
542 		}
543 		List_Clear( &cmd_triggers );
544 		Com_Printf( "Removed all triggers\n" );
545 		return;
546 	}
547 
548 	command = Cmd_FindParam( "c", "command" );
549 	match = Cmd_FindParam( "m", "match" );
550 	if( !command && !match ) {
551 		goto usage;
552 	}
553 
554 	for( elem = cmd_triggers.first; elem; elem = next ) {
555 		next = elem->next;
556 		trigger = ( cmd_trigger_t * )elem;
557 		if( command && strcmp( trigger->command, command ) ) {
558 			continue;
559 		}
560 		if( match && strcmp( trigger->match, match ) ) {
561 			continue;
562 		}
563 
564 		Com_Printf( "Removed \"%s\" = \"%s\" [%s]\n",
565 			trigger->match, trigger->command,
566                 chan2string( trigger->chan ) );
567 
568 		List_DeleteElem( elem );
569 		Z_Free( elem );
570 	}
571 }
572 
573 /*
574 ============
575 Cmd_ExecTrigger
576 ============
577 */
Cmd_ExecTrigger(trigChannel_t chan,const char * string)578 void Cmd_ExecTrigger( trigChannel_t chan, const char *string ) {
579 	cmd_trigger_t *trigger;
580 	listElem_t *elem;
581     char *text;
582 
583 	// execute matching triggers
584 	for( elem = cmd_triggers.first; elem; elem = elem->next ) {
585 		trigger = ( cmd_trigger_t * )elem;
586 		if( trigger->chan != chan ) {
587 			continue;
588 		}
589 		text = Cmd_MacroExpandString( trigger->match, qfalse );
590 		if( text && Com_WildCmp( text, string, qtrue ) ) {
591 			Cbuf_AddText( trigger->command );
592 			Cbuf_AddText( "\n" );
593 		}
594 	}
595 }
596 
597 /*
598 =============================================================================
599 
600 					MACRO EXECUTION
601 
602 =============================================================================
603 */
604 
605 typedef struct cmd_macro_s {
606 	struct cmd_macro_s	*next;
607 	struct cmd_macro_s	*hashNext;
608 
609 	const char		*name;
610 	xmacro_t		function;
611 } cmd_macro_t;
612 
613 #define MACRO_HASH_SIZE	64
614 
615 static cmd_macro_t	*cmd_macros;
616 static cmd_macro_t	*cmd_macroHash[MACRO_HASH_SIZE];
617 
618 /*
619 ============
620 Cmd_MacroFind
621 ============
622 */
Cmd_MacroFind(const char * name)623 static cmd_macro_t *Cmd_MacroFind( const char *name ) {
624 	cmd_macro_t *macro;
625 	int hash;
626 
627 	hash = Com_HashString( name, MACRO_HASH_SIZE );
628 	for( macro=cmd_macroHash[hash] ; macro ; macro=macro->hashNext ) {
629 		if( !strcmp( macro->name, name ) ) {
630 			return macro;
631 		}
632 	}
633 
634 	return NULL;
635 }
636 
Cmd_FindMacroFunction(const char * name)637 xmacro_t Cmd_FindMacroFunction( const char *name ) {
638 	cmd_macro_t *macro;
639 
640 	macro = Cmd_MacroFind( name );
641 	if( !macro ) {
642 		return NULL;
643 	}
644 
645 	return macro->function;
646 }
647 
648 /*
649 ============
650 Cmd_AddMacro
651 ============
652 */
Cmd_AddMacro(const char * name,xmacro_t function)653 void Cmd_AddMacro( const char *name, xmacro_t function ) {
654 	cmd_macro_t	*macro;
655 	int hash;
656 
657 	if( Cvar_Exists( name ) ) {
658 		Com_WPrintf( "Cmd_AddMacro: %s already defined as a cvar\n", name );
659 		return;
660 	}
661 
662 // fail if the macro already exists
663 	if( Cmd_MacroFind( name ) ) {
664 		Com_WPrintf( "Cmd_AddMacro: %s already defined\n", name );
665 		return;
666 	}
667 
668 	hash = Com_HashString( name, MACRO_HASH_SIZE );
669 
670 	macro = Cmd_Malloc( sizeof( cmd_macro_t ) );
671 	macro->name = name;
672 	macro->function = function;
673 	macro->next = cmd_macros;
674 	cmd_macros = macro;
675 	macro->hashNext = cmd_macroHash[hash];
676 	cmd_macroHash[hash] = macro;
677 }
678 
679 
680 
681 
682 /*
683 =============================================================================
684 
685 					COMMAND EXECUTION
686 
687 =============================================================================
688 */
689 
690 #define CMD_HASH_SIZE	128
691 
692 typedef struct cmd_function_s {
693 	listElem_t		elem[2];
694 
695 	xcommand_t				function;
696 	xgenerator_t			generator;
697 	const char				*name;
698 } cmd_function_t;
699 
700 static	list_t		cmd_functions;		/* possible commands to execute */
701 static	list_t		cmd_hash[CMD_HASH_SIZE];
702 
703 static	int			cmd_argc;
704 static	char		*cmd_argv[MAX_STRING_TOKENS];
705 static	char		*cmd_null_string = "";
706 
707 /* complete command string, quotes preserved */
708 static	char		cmd_args[MAX_STRING_CHARS];
709 
710 /* offsets of individual tokens in cmd_args */
711 static	int			cmd_offsets[MAX_STRING_TOKENS];
712 
713 /* sequence of NULL-terminated tokens, each cmd_argv[] points here */
714 static	char		cmd_data[MAX_STRING_CHARS];
715 
Cmd_ArgOffset(int arg)716 int Cmd_ArgOffset( int arg ) {
717 	if( arg < 0 ) {
718 		return 0;
719 	}
720 	if( arg >= cmd_argc ) {
721 		return strlen( cmd_args );
722 	}
723 	return cmd_offsets[arg];
724 }
725 
Cmd_FindArgForOffset(int offset)726 int Cmd_FindArgForOffset( int offset ) {
727 	int i;
728 
729 	for( i = 1; i < cmd_argc; i++ ) {
730 		if( offset < cmd_offsets[i] ) {
731 			return i - 1;
732 		}
733 	}
734 	return i - 1;
735 }
736 
737 /*
738 ============
739 Cmd_Argc
740 ============
741 */
Cmd_Argc(void)742 int Cmd_Argc( void ) {
743 	return cmd_argc;
744 }
745 
746 /*
747 ============
748 Cmd_Argv
749 ============
750 */
Cmd_Argv(int arg)751 char *Cmd_Argv( int arg ) {
752 	if( arg < 0 || arg >= cmd_argc ) {
753 		return cmd_null_string;
754 	}
755 	return cmd_argv[arg];
756 }
757 
758 /*
759 ============
760 Cmd_ArgvBuffer
761 ============
762 */
Cmd_ArgvBuffer(int arg,char * buffer,int bufferSize)763 void Cmd_ArgvBuffer( int arg, char *buffer, int bufferSize ) {
764 	char *s;
765 
766 	if( arg < 0 || arg >= cmd_argc ) {
767 		s = cmd_null_string;
768 	} else {
769 		s = cmd_argv[arg];
770 	}
771 
772 	Q_strncpyz( buffer, s, bufferSize );
773 }
774 
775 
776 /*
777 ============
778 Cmd_Args
779 
780 Returns a single string containing argv(1) to argv(argc()-1)
781 ============
782 */
Cmd_Args(void)783 char *Cmd_Args( void ) {
784 	static char args[MAX_STRING_CHARS];
785 	int i;
786 
787 	if( cmd_argc < 2 ) {
788 		return cmd_null_string;
789 	}
790 
791 	args[0] = 0;
792 	for( i = 1; i < cmd_argc - 1; i++ ) {
793 		strcat( args, cmd_argv[i] );
794 		strcat( args, " " );
795 	}
796 	strcat( args, cmd_argv[i] );
797 
798 	return args;
799 }
800 
Cmd_RawArgs(void)801 char *Cmd_RawArgs( void ) {
802 	if( cmd_argc < 2 ) {
803 		return cmd_null_string;
804 	}
805 	return cmd_args + cmd_offsets[1];
806 }
807 
Cmd_RawString(void)808 char *Cmd_RawString( void ) {
809 	return cmd_args;
810 }
811 
812 
813 
814 /*
815 ============
816 Cmd_ArgsBuffer
817 ============
818 */
Cmd_ArgsBuffer(char * buffer,int bufferSize)819 void Cmd_ArgsBuffer( char *buffer, int bufferSize ) {
820 	Q_strncpyz( buffer, Cmd_Args(), bufferSize );
821 }
822 
823 /*
824 ============
825 Cmd_ArgsFrom
826 
827 Returns a single string containing argv(1) to argv(from-1)
828 ============
829 */
Cmd_ArgsFrom(int from)830 char *Cmd_ArgsFrom( int from ) {
831 	static char args[MAX_STRING_CHARS];
832 	int i;
833 
834 	if( from < 0 || from >= cmd_argc ) {
835 		return cmd_null_string;
836 	}
837 
838 	args[0] = 0;
839 	for( i = from; i < cmd_argc - 1; i++ ) {
840 		strcat( args, cmd_argv[i] );
841 		strcat( args, " " );
842 	}
843 	strcat( args, cmd_argv[i] );
844 
845 	return args;
846 }
847 
Cmd_RawArgsFrom(int from)848 char *Cmd_RawArgsFrom( int from ) {
849 	int offset;
850 
851 	if( from < 0 || from >= cmd_argc ) {
852 		return cmd_null_string;
853 	}
854 
855 	offset = cmd_offsets[from];
856 
857 	return cmd_args + offset;
858 }
859 
860 /*
861 ============
862 Cmd_EnumParam
863 ============
864 */
Cmd_EnumParam(int start,const char * sp,const char * lp)865 int Cmd_EnumParam( int start, const char *sp, const char *lp ) {
866 	int i;
867 	char *s;
868 
869 	if( start < 0 || start >= cmd_argc ) {
870 		return 0;
871 	}
872 	for( i = start; i < cmd_argc; i++ ) {
873 		if( *( s = cmd_argv[i] ) == '-' ) {
874 			if( *( ++s ) == '-' ) {
875 				if( !strcmp( s + 1, lp ) ) {
876 					return i;
877 				}
878 			} else if( !strcmp( s, sp ) ) {
879 				return i;
880 			}
881 		}
882 	}
883 
884 	return 0;
885 }
886 
887 /*
888 ============
889 Cmd_CheckParam
890 ============
891 */
Cmd_CheckParam(const char * sp,const char * lp)892 int Cmd_CheckParam( const char *sp, const char *lp ) {
893 	return Cmd_EnumParam( 1, sp, lp );
894 }
895 
896 /*
897 ============
898 Cmd_FindParam
899 ============
900 */
Cmd_FindParam(const char * sp,const char * lp)901 char *Cmd_FindParam( const char *sp, const char *lp ) {
902 	int i;
903 
904 	if( ( i = Cmd_EnumParam( 1, sp, lp ) ) && ++i != cmd_argc ) {
905 		return cmd_argv[i];
906 	}
907 
908 	return NULL;
909 }
910 
911 /*
912 ======================
913 Cmd_MacroExpandString
914 ======================
915 */
Cmd_MacroExpandString(const char * text,qboolean aliasHack)916 char *Cmd_MacroExpandString( const char *text, qboolean aliasHack ) {
917 	int		i, j, count, len;
918 	qboolean	inquote;
919 	char	*scan, *start;
920 	static	char	expanded[MAX_STRING_CHARS];
921 	char	temporary[MAX_STRING_CHARS];
922 	char	buffer[MAX_TOKEN_CHARS];
923 	char	*token;
924 	cmd_macro_t *macro;
925 	cvar_t	*var;
926 	qboolean	rescan;
927 
928 	len = strlen( text );
929 	if( len >= MAX_STRING_CHARS ) {
930 		Com_Printf( "Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS );
931 		return NULL;
932 	}
933 
934 	strcpy( expanded, text );
935 	scan = expanded;
936 
937 	inquote = qfalse;
938 	count = 0;
939 
940 	for( i = 0; i < len; i++ ) {
941 		if( !scan[i] ) {
942 			break;
943 		}
944 		if( scan[i] == '"' ) {
945 			inquote ^= 1;
946 		}
947 		if( inquote ) {
948 			continue;	/* don't expand inside quotes */
949 		}
950 		if( scan[i] != '$' ) {
951 			continue;
952 		}
953 
954 		/* scan out the complete macro */
955 		start = scan + i + 1;
956 
957 		if( !start[0] ) {
958 			break;	/* end of string */
959 		}
960 
961 		/* allow escape syntax */
962 		if( i && scan[i-1] == '\\' ) {
963 			memmove( scan + i - 1, scan + i, len - i + 1 );
964 			i--;
965 			continue;
966 		}
967 
968 		/* fix from jitspoe - skip leading spaces */
969 		while( *start && *start <= 32 ) {
970 			start++;
971 		}
972 
973 		token = temporary;
974 
975 		if( *start == '{' ) {
976 			/* allow ${variable} syntax */
977 			start++;
978             if( *start == '$' ) {
979                 start++;
980             }
981 			while( *start ) {
982 				if( *start == '}' ) {
983 					start++;
984 					break;
985 				}
986 				*token++ = *start++;
987 			}
988 		} else {
989 			/* parse single word */
990 			while( *start > 32 ) {
991 				*token++ = *start++;
992 			}
993 		}
994 
995 		*token = 0;
996 
997 		if( token == temporary ) {
998 			continue;
999 		}
1000 
1001 		rescan = qfalse;
1002 
1003 		if( aliasHack ) {
1004 			/* expand positional parameters only */
1005             if( temporary[1]  ) {
1006                 continue;
1007             }
1008             if( Q_isdigit( temporary[0] ) ) {
1009                 token = Cmd_Argv( temporary[0] - '0' );
1010             } else if( temporary[0] == '@' ) {
1011                 token = Cmd_Args();
1012             } else {
1013                 continue;
1014             }
1015 		} else {
1016 			/* check for macros first */
1017 			macro = Cmd_MacroFind( temporary );
1018 			if( macro ) {
1019 				macro->function( buffer, sizeof( buffer ) );
1020 				token = buffer;
1021             } else {
1022 				var = Cvar_FindVar( temporary );
1023 				if( var && !( var->flags & CVAR_PRIVATE ) ) {
1024 					token = var->string;
1025 					rescan = qtrue;
1026 				} else if( !strcmp( temporary, "qt" ) ) {
1027 					token = "\"";
1028 				} else if( !strcmp( temporary, "sc" ) ) {
1029 					token = ";";
1030 				} else {
1031 					token = "";
1032 				}
1033 			}
1034 		}
1035 
1036 		j = strlen( token );
1037 		len += j;
1038 		if( len >= MAX_STRING_CHARS ) {
1039 			Com_Printf( "Expanded line exceeded %i chars, discarded.\n",
1040                     MAX_STRING_CHARS );
1041 			return NULL;
1042 		}
1043 
1044 		strncpy( temporary, scan, i );
1045 		strcpy( temporary + i, token );
1046 		strcpy( temporary + i + j, start );
1047 
1048 		strcpy( expanded, temporary );
1049 		scan = expanded;
1050 		if( !rescan ) {
1051 			i += j;
1052 		}
1053 		i--;
1054 
1055 		if( ++count == 100 ) {
1056 			Com_Printf( "Macro expansion loop, discarded.\n" );
1057 			return NULL;
1058 		}
1059 	}
1060 
1061 	if( inquote ) {
1062 		Com_Printf( "Line has unmatched quote, discarded.\n" );
1063 		return NULL;
1064 	}
1065 
1066 	return scan;
1067 }
1068 
1069 /*
1070 ============
1071 Cmd_TokenizeString
1072 
1073 Parses the given string into command line tokens.
1074 $Cvars will be expanded unless they are in a quoted token
1075 ============
1076 */
Cmd_TokenizeString(const char * text,qboolean macroExpand)1077 void Cmd_TokenizeString( const char *text, qboolean macroExpand ) {
1078 	int		i;
1079 	char	*data, *start, *dest;
1080 
1081 // clear the args from the last string
1082 	for( i = 0; i < cmd_argc; i++ ) {
1083 		cmd_argv[i] = NULL;
1084 		cmd_offsets[i] = 0;
1085 	}
1086 
1087 	cmd_argc = 0;
1088 	cmd_args[0] = 0;
1089 
1090     if( !text[0] ) {
1091         return;
1092     }
1093 
1094 // macro expand the text
1095 	if( macroExpand ) {
1096 		text = Cmd_MacroExpandString( text, qfalse );
1097 		if( !text ) {
1098 			return;
1099 		}
1100 	}
1101 
1102 	Q_strncpyz( cmd_args, text, sizeof( cmd_args ) );
1103 
1104 	dest = cmd_data;
1105 	start = data = cmd_args;
1106 	do {
1107 // skip whitespace up to a /n
1108 		while( *data <= 32 ) {
1109 			if( *data == 0 ) {
1110 				return; // end of text
1111 			}
1112 			if( *data == '\n' ) {
1113 				return; // a newline seperates commands in the buffer
1114 			}
1115 			data++;
1116 		}
1117 
1118 // skip C++ style comments
1119 		if( data[0] == '/' && data[1] == '/' ) {
1120 			return; // skipped to newline
1121 		}
1122 
1123 // add new argument
1124 		cmd_offsets[cmd_argc] = data - start;
1125 		cmd_argv[cmd_argc] = dest;
1126 		cmd_argc++;
1127 
1128 		if( *data == ';' ) {
1129 			data++;
1130 			*dest++ = ';';
1131 			*dest++ = 0;
1132 			continue;
1133 		}
1134 
1135 // parse quoted string
1136 		if( *data == '\"' ) {
1137 			data++;
1138 			while( *data != '\"' ) {
1139 				if( *data == 0 ) {
1140 					return; // end of data
1141 				}
1142 				*dest++ = *data++;
1143 			}
1144 			data++;
1145 			*dest++ = 0;
1146 			continue;
1147 		}
1148 
1149 // parse reqular token
1150 		while( *data > 32 ) {
1151 			if( *data == '\"' ) {
1152 				break;
1153 			}
1154 			if( *data == ';' ) {
1155 				break;
1156 			}
1157 			if( data[0] == '/' && data[1] == '/' ) {
1158 				break; // skipped to newline
1159 			}
1160 			*dest++ = *data++;
1161 		}
1162 		*dest++ = 0;
1163 
1164 		if( *data == 0 ) {
1165 			return; // end of text
1166 		}
1167 	} while( cmd_argc != MAX_STRING_TOKENS );
1168 
1169 }
1170 
1171 /*
1172 ============
1173 Cmd_Find
1174 ============
1175 */
Cmd_Find(const char * name)1176 cmd_function_t *Cmd_Find( const char *name ) {
1177 	cmd_function_t *cmd;
1178 	listElem_t *elem;
1179 	uint32 hash;
1180 
1181 	hash = Com_HashString( name, CMD_HASH_SIZE );
1182 	for( elem = cmd_hash[hash].first; elem; elem = elem->next ) {
1183 		cmd = ( cmd_function_t * )( elem - 1 );
1184 		if( !strcmp( cmd->name, name ) ) {
1185 			return cmd;
1186 		}
1187 	}
1188 
1189 	return NULL;
1190 }
1191 
1192 /*
1193 ============
1194 Cmd_AddCommand
1195 ============
1196 */
Cmd_AddCommandEx(const char * cmd_name,xcommand_t function,xgenerator_t generator)1197 void Cmd_AddCommandEx( const char *cmd_name, xcommand_t function,
1198         xgenerator_t generator )
1199 {
1200 	cmd_function_t	*cmd;
1201 	uint32 hash;
1202 
1203 // fail if the command is a variable name
1204 	if( Cvar_FindVar( cmd_name ) ) {
1205 		Com_WPrintf( "Cmd_AddCommand: %s already defined as a cvar\n",
1206             cmd_name );
1207 		return;
1208 	}
1209 
1210 // fail if the command already exists
1211     cmd = Cmd_Find( cmd_name );
1212 	if( cmd ) {
1213         Com_WPrintf( "Cmd_AddCommand: %s already defined\n", cmd_name );
1214         return;
1215 	}
1216 
1217     cmd = Cmd_Malloc( sizeof( *cmd ) );
1218     cmd->name = cmd_name;
1219     cmd->function = function;
1220     cmd->generator = generator;
1221 
1222     List_Append( &cmd_functions, &cmd->elem[0] );
1223 
1224     hash = Com_HashString( cmd_name, CMD_HASH_SIZE );
1225     List_Append( &cmd_hash[hash], &cmd->elem[1] );
1226 
1227 }
1228 
Cmd_AddCommand(const char * cmd_name,xcommand_t function)1229 void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) {
1230 	Cmd_AddCommandEx( cmd_name, function, NULL );
1231 }
1232 
1233 /*
1234 ============
1235 Cmd_RemoveCommand
1236 
1237 Can't just free the command since lua still may have a pointer to it.
1238 ============
1239 */
Cmd_RemoveCommand(const char * name)1240 void Cmd_RemoveCommand( const char *name ) {
1241 	cmd_function_t	*cmd;
1242 
1243 	cmd = Cmd_Find( name );
1244 	if( !cmd ) {
1245 		Com_WPrintf( "Cmd_RemoveCommand: %s not added\n", name );
1246 		return;
1247 	}
1248 
1249     List_DeleteElem( &cmd->elem[0] );
1250     List_DeleteElem( &cmd->elem[1] );
1251     Z_Free( cmd );
1252 
1253 }
1254 
1255 /*
1256 ============
1257 Cmd_Exists
1258 ============
1259 */
Cmd_Exists(const char * name)1260 qboolean Cmd_Exists( const char *name ) {
1261 	cmd_function_t *cmd;
1262 
1263     cmd = Cmd_Find( name );
1264 	if( !cmd ) {
1265 		return qfalse;
1266 	}
1267 
1268 	return qtrue;
1269 }
1270 
Cmd_FindFunction(const char * name)1271 xcommand_t Cmd_FindFunction( const char *name ) {
1272 	cmd_function_t *cmd;
1273 
1274 	cmd = Cmd_Find( name );
1275 	if( !cmd ) {
1276 		return NULL;
1277 	}
1278 
1279 	return cmd->function;
1280 }
1281 
Cmd_FindGenerator(const char * name)1282 xgenerator_t Cmd_FindGenerator( const char *name ) {
1283 	cmd_function_t *cmd;
1284 
1285 	cmd = Cmd_Find( name );
1286 	if( !cmd ) {
1287 		return NULL;
1288 	}
1289 
1290 	return cmd->generator;
1291 }
1292 
Cmd_CommandGenerator(const char * partial,int state)1293 const char *Cmd_CommandGenerator( const char *partial, int state ) {
1294     static int length;
1295     static cmd_function_t *cmd;
1296     const char *name;
1297 
1298     if( !state ) {
1299         length = strlen( partial );
1300 		cmd = ( cmd_function_t * )cmd_functions.first;
1301     }
1302 
1303     while( cmd ) {
1304         name = cmd->name;
1305 		cmd = ( cmd_function_t * )cmd->elem[0].next;
1306 		if( !strncmp( partial, name, length ) ) {
1307             return name;
1308         }
1309     }
1310 
1311     return NULL;
1312 }
1313 
Cmd_AliasGenerator(const char * partial,int state)1314 const char *Cmd_AliasGenerator( const char *partial, int state ) {
1315     static int length;
1316     static cmdalias_t *alias;
1317     const char *name;
1318 
1319     if( !state ) {
1320         length = strlen( partial );
1321 		alias = ( cmdalias_t * )cmd_alias.first;
1322     }
1323 
1324     while( alias ) {
1325         name = alias->name;
1326         alias = ( cmdalias_t * )alias->elem[0].next;
1327 		if( !strncmp( partial, name, length ) ) {
1328             return name;
1329         }
1330     }
1331 
1332     return NULL;
1333 }
1334 
1335 /*
1336 ============
1337 Cmd_ExecuteString
1338 
1339 A complete command line has been parsed, so try to execute it
1340 ============
1341 */
Cmd_ExecuteString(const char * text)1342 void Cmd_ExecuteString( const char *text ) {
1343 	cmd_function_t	*cmd;
1344 	cmdalias_t		*a;
1345 
1346 	Cmd_TokenizeString( text, qtrue );
1347 
1348 	// execute the command line
1349 	if( !cmd_argc ) {
1350 		return;		// no tokens
1351 	}
1352 
1353 	// check functions
1354 	cmd = Cmd_Find( cmd_argv[0] );
1355 	if( cmd ) {
1356         if( cmd->function ) {
1357             cmd->function();
1358         } else {
1359             Cmd_ForwardToServer();
1360         }
1361 		return;
1362 	}
1363 
1364 	// check aliases
1365 	a = Cmd_AliasFind( cmd_argv[0] );
1366 	if( a ) {
1367 		if( ++cmd_buffer.aliasCount == ALIAS_LOOP_COUNT ) {
1368 			Com_WPrintf( "Runaway alias loop\n" );
1369 			return;
1370 		}
1371 		text = Cmd_MacroExpandString( a->value, qtrue );
1372 		if( text ) {
1373 			Cbuf_InsertText( text );
1374 		}
1375 		return;
1376 	}
1377 
1378 	// check cvars
1379 	if( Cvar_Command() ) {
1380 		return;
1381     }
1382 
1383 	// send it as a server command if we are connected
1384 	Cmd_ForwardToServer();
1385 }
1386 
1387 /*
1388 ===============
1389 Cmd_Exec_f
1390 ===============
1391 */
Cmd_Exec_f(void)1392 static void Cmd_Exec_f( void ) {
1393 	char	buffer[MAX_QPATH];
1394 	char	*f, *ext;
1395 	int		len;
1396 
1397 	if( Cmd_Argc () != 2 ) {
1398 		Com_Printf( "exec <filename> : execute a script file\n" );
1399 		return;
1400 	}
1401 
1402 	Q_strncpyz( buffer, Cmd_Argv( 1 ), sizeof( buffer ) );
1403 
1404 	len = FS_LoadFile( buffer, ( void ** )&f );
1405 	if( !f ) {
1406 		ext = COM_FileExtension( buffer );
1407 		if( *ext == 0 ) {
1408 			/* try with *.cfg extension */
1409 			COM_DefaultExtension( buffer, ".cfg", sizeof( buffer ) );
1410 			len = FS_LoadFile( buffer, ( void ** )&f );
1411 		}
1412 
1413 		if( !f ) {
1414 			Com_Printf( "Couldn't exec %s\n", buffer );
1415 			return;
1416 		}
1417 	}
1418 
1419 	Com_Printf( "Execing %s\n", buffer );
1420 
1421 	Cbuf_InsertText( f );
1422 
1423 	FS_FreeFile( f );
1424 }
1425 
Cmd_Exec_g(const char * partial,int state)1426 static const char *Cmd_Exec_g( const char *partial, int state ) {
1427 	return Com_FileNameGeneratorByFilter( "", "*.cfg", partial, qtrue, state );
1428 }
1429 
1430 /*
1431 ===============
1432 Cmd_Echo_f
1433 
1434 Just prints the rest of the line to the console
1435 ===============
1436 */
Cmd_Echo_f(void)1437 static void Cmd_Echo_f( void ) {
1438 	Com_Printf( "%s\n", Cmd_RawArgs() );
1439 }
1440 
Cmd_ColoredEcho_f(void)1441 static void Cmd_ColoredEcho_f( void ) {
1442 	char buffer[MAX_STRING_CHARS];
1443 	char *src, *dst;
1444 
1445 	src = Cmd_RawArgs();
1446 	dst = buffer;
1447 	while( *src ) {
1448 		if( src[0] == '^' && src[1] ) {
1449 			if( src[1] == '^' ) {
1450 				*dst++ = '^';
1451 			} else {
1452 				dst[0] = Q_COLOR_ESCAPE;
1453 				dst[1] = src[1];
1454 				dst += 2;
1455 			}
1456 			src += 2;
1457 		} else {
1458 			*dst++ = *src++;
1459 		}
1460 	}
1461 	*dst = 0;
1462 	Com_Printf( "%s\n", buffer );
1463 }
1464 
1465 /*
1466 ============
1467 Cmd_List_f
1468 ============
1469 */
Cmd_List_f(void)1470 static void Cmd_List_f( void ) {
1471 	cmd_function_t	*cmd;
1472 	listElem_t *elem;
1473 	int				i, total;
1474 	char		*filter = NULL;
1475 
1476 	if( cmd_argc > 1 ) {
1477 		filter = cmd_argv[1];
1478 	}
1479 
1480 	i = total = 0;
1481 	for( elem = cmd_functions.first; elem; elem = elem->next ) {
1482 		cmd = ( cmd_function_t * )elem;
1483 		total++;
1484 		if( filter && !Com_WildCmp( filter, cmd->name, qfalse ) ) {
1485 			continue;
1486 		}
1487 		Com_Printf( "%s\n", cmd->name );
1488 		i++;
1489 	}
1490 	Com_Printf( "%i of %i commands\n", i, total );
1491 }
1492 
1493 /*
1494 ============
1495 Cmd_MacroList_f
1496 ============
1497 */
Cmd_MacroList_f(void)1498 static void Cmd_MacroList_f( void ) {
1499 	cmd_macro_t	*macro;
1500 	int				i, total;
1501 	char		*filter = NULL;
1502 	char		buffer[MAX_QPATH];
1503 
1504 	if( cmd_argc > 1 ) {
1505 		filter = cmd_argv[1];
1506 	}
1507 
1508 	i = 0;
1509 	for( macro = cmd_macros, total = 0; macro; macro = macro->next, total++ ) {
1510 		if( filter && !Com_WildCmp( filter, macro->name, qfalse ) ) {
1511 			continue;
1512 		}
1513 		macro->function( buffer, sizeof( buffer ) );
1514 		Com_Printf( "%-16s %s\n", macro->name, buffer );
1515 		i++;
1516 	}
1517 	Com_Printf( "%i of %i macros\n", i, total );
1518 }
1519 
1520 /*
1521 ============
1522 Cmd_FillAPI
1523 ============
1524 */
Cmd_FillAPI(cmdAPI_t * api)1525 void Cmd_FillAPI( cmdAPI_t *api ) {
1526 	api->AddCommand = Cmd_AddCommand;
1527 	api->AddCommandEx = Cmd_AddCommandEx;
1528 	api->RemoveCommand = Cmd_RemoveCommand;
1529 	api->Argc = Cmd_Argc;
1530 	api->Argv = Cmd_Argv;
1531 	api->ArgsFrom = Cmd_ArgsFrom;
1532 	api->ExecuteText = Cbuf_ExecuteText;
1533 	api->FindFunction = Cmd_FindFunction;
1534 	api->FindMacroFunction = Cmd_FindMacroFunction;
1535 	api->FindGenerator = Cmd_FindGenerator;
1536 }
1537 
1538 /*
1539 ============
1540 Cmd_Init
1541 ============
1542 */
Cmd_Init(void)1543 void Cmd_Init( void ) {
1544 //
1545 // register our commands
1546 //
1547 	Cmd_AddCommand( "cmdlist", Cmd_List_f );
1548 	Cmd_AddCommand( "macrolist", Cmd_MacroList_f );
1549 	Cmd_AddCommandEx( "exec", Cmd_Exec_f, Cmd_Exec_g );
1550 	Cmd_AddCommand( "echo", Cmd_Echo_f );
1551 	Cmd_AddCommand( "_echo", Cmd_ColoredEcho_f );
1552 	Cmd_AddCommandEx( "alias", Cmd_Alias_f, Cmd_AliasGenerator );
1553 	Cmd_AddCommandEx( "unalias", Cmd_UnAlias_f, Cmd_AliasGenerator );
1554 	Cmd_AddCommand( "wait", Cmd_Wait_f );
1555 	Cmd_AddCommand( "trigger", Cmd_Trigger_f );
1556 	Cmd_AddCommand( "untrigger", Cmd_UnTrigger_f );
1557 
1558 	Cmd_FillAPI( &cmd );
1559 }
1560 
1561