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