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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 422 int Cmd_Argc (void) 423 { 424 return cmd_argc; 425 } 426 427 /* 428 ============ 429 Cmd_Argv 430 ============ 431 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 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 */ 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