sv_ccmds.c (21111B)
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 21 #include "server.h" 22 23 /* 24 =============================================================================== 25 26 OPERATOR CONSOLE ONLY COMMANDS 27 28 These commands can only be entered from stdin or by a remote operator datagram 29 =============================================================================== 30 */ 31 32 /* 33 ==================== 34 SV_SetMaster_f 35 36 Specify a list of master servers 37 ==================== 38 */ 39 void SV_SetMaster_f (void) 40 { 41 int i, slot; 42 43 // only dedicated servers send heartbeats 44 if (!dedicated->value) 45 { 46 Com_Printf ("Only dedicated servers use masters.\n"); 47 return; 48 } 49 50 // make sure the server is listed public 51 Cvar_Set ("public", "1"); 52 53 for (i=1 ; i<MAX_MASTERS ; i++) 54 memset (&master_adr[i], 0, sizeof(master_adr[i])); 55 56 slot = 1; // slot 0 will always contain the id master 57 for (i=1 ; i<Cmd_Argc() ; i++) 58 { 59 if (slot == MAX_MASTERS) 60 break; 61 62 if (!NET_StringToAdr (Cmd_Argv(i), &master_adr[i])) 63 { 64 Com_Printf ("Bad address: %s\n", Cmd_Argv(i)); 65 continue; 66 } 67 if (master_adr[slot].port == 0) 68 master_adr[slot].port = BigShort (PORT_MASTER); 69 70 Com_Printf ("Master server at %s\n", NET_AdrToString (master_adr[slot])); 71 72 Com_Printf ("Sending a ping.\n"); 73 74 Netchan_OutOfBandPrint (NS_SERVER, master_adr[slot], "ping"); 75 76 slot++; 77 } 78 79 svs.last_heartbeat = -9999999; 80 } 81 82 83 84 /* 85 ================== 86 SV_SetPlayer 87 88 Sets sv_client and sv_player to the player with idnum Cmd_Argv(1) 89 ================== 90 */ 91 qboolean SV_SetPlayer (void) 92 { 93 client_t *cl; 94 int i; 95 int idnum; 96 char *s; 97 98 if (Cmd_Argc() < 2) 99 return false; 100 101 s = Cmd_Argv(1); 102 103 // numeric values are just slot numbers 104 if (s[0] >= '0' && s[0] <= '9') 105 { 106 idnum = atoi(Cmd_Argv(1)); 107 if (idnum < 0 || idnum >= maxclients->value) 108 { 109 Com_Printf ("Bad client slot: %i\n", idnum); 110 return false; 111 } 112 113 sv_client = &svs.clients[idnum]; 114 sv_player = sv_client->edict; 115 if (!sv_client->state) 116 { 117 Com_Printf ("Client %i is not active\n", idnum); 118 return false; 119 } 120 return true; 121 } 122 123 // check for a name match 124 for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++) 125 { 126 if (!cl->state) 127 continue; 128 if (!strcmp(cl->name, s)) 129 { 130 sv_client = cl; 131 sv_player = sv_client->edict; 132 return true; 133 } 134 } 135 136 Com_Printf ("Userid %s is not on the server\n", s); 137 return false; 138 } 139 140 141 /* 142 =============================================================================== 143 144 SAVEGAME FILES 145 146 =============================================================================== 147 */ 148 149 /* 150 ===================== 151 SV_WipeSavegame 152 153 Delete save/<XXX>/ 154 ===================== 155 */ 156 void SV_WipeSavegame (char *savename) 157 { 158 char name[MAX_OSPATH]; 159 char *s; 160 161 Com_DPrintf("SV_WipeSaveGame(%s)\n", savename); 162 163 Com_sprintf (name, sizeof(name), "%s/save/%s/server.ssv", FS_Gamedir (), savename); 164 remove (name); 165 Com_sprintf (name, sizeof(name), "%s/save/%s/game.ssv", FS_Gamedir (), savename); 166 remove (name); 167 168 Com_sprintf (name, sizeof(name), "%s/save/%s/*.sav", FS_Gamedir (), savename); 169 s = Sys_FindFirst( name, 0, 0 ); 170 while (s) 171 { 172 remove (s); 173 s = Sys_FindNext( 0, 0 ); 174 } 175 Sys_FindClose (); 176 Com_sprintf (name, sizeof(name), "%s/save/%s/*.sv2", FS_Gamedir (), savename); 177 s = Sys_FindFirst(name, 0, 0 ); 178 while (s) 179 { 180 remove (s); 181 s = Sys_FindNext( 0, 0 ); 182 } 183 Sys_FindClose (); 184 } 185 186 187 /* 188 ================ 189 CopyFile 190 ================ 191 */ 192 void CopyFile (char *src, char *dst) 193 { 194 FILE *f1, *f2; 195 int l; 196 byte buffer[65536]; 197 198 Com_DPrintf ("CopyFile (%s, %s)\n", src, dst); 199 200 f1 = fopen (src, "rb"); 201 if (!f1) 202 return; 203 f2 = fopen (dst, "wb"); 204 if (!f2) 205 { 206 fclose (f1); 207 return; 208 } 209 210 while (1) 211 { 212 l = fread (buffer, 1, sizeof(buffer), f1); 213 if (!l) 214 break; 215 fwrite (buffer, 1, l, f2); 216 } 217 218 fclose (f1); 219 fclose (f2); 220 } 221 222 223 /* 224 ================ 225 SV_CopySaveGame 226 ================ 227 */ 228 void SV_CopySaveGame (char *src, char *dst) 229 { 230 char name[MAX_OSPATH], name2[MAX_OSPATH]; 231 int l, len; 232 char *found; 233 234 Com_DPrintf("SV_CopySaveGame(%s, %s)\n", src, dst); 235 236 SV_WipeSavegame (dst); 237 238 // copy the savegame over 239 Com_sprintf (name, sizeof(name), "%s/save/%s/server.ssv", FS_Gamedir(), src); 240 Com_sprintf (name2, sizeof(name2), "%s/save/%s/server.ssv", FS_Gamedir(), dst); 241 FS_CreatePath (name2); 242 CopyFile (name, name2); 243 244 Com_sprintf (name, sizeof(name), "%s/save/%s/game.ssv", FS_Gamedir(), src); 245 Com_sprintf (name2, sizeof(name2), "%s/save/%s/game.ssv", FS_Gamedir(), dst); 246 CopyFile (name, name2); 247 248 Com_sprintf (name, sizeof(name), "%s/save/%s/", FS_Gamedir(), src); 249 len = strlen(name); 250 Com_sprintf (name, sizeof(name), "%s/save/%s/*.sav", FS_Gamedir(), src); 251 found = Sys_FindFirst(name, 0, 0 ); 252 while (found) 253 { 254 strcpy (name+len, found+len); 255 256 Com_sprintf (name2, sizeof(name2), "%s/save/%s/%s", FS_Gamedir(), dst, found+len); 257 CopyFile (name, name2); 258 259 // change sav to sv2 260 l = strlen(name); 261 strcpy (name+l-3, "sv2"); 262 l = strlen(name2); 263 strcpy (name2+l-3, "sv2"); 264 CopyFile (name, name2); 265 266 found = Sys_FindNext( 0, 0 ); 267 } 268 Sys_FindClose (); 269 } 270 271 272 /* 273 ============== 274 SV_WriteLevelFile 275 276 ============== 277 */ 278 void SV_WriteLevelFile (void) 279 { 280 char name[MAX_OSPATH]; 281 FILE *f; 282 283 Com_DPrintf("SV_WriteLevelFile()\n"); 284 285 Com_sprintf (name, sizeof(name), "%s/save/current/%s.sv2", FS_Gamedir(), sv.name); 286 f = fopen(name, "wb"); 287 if (!f) 288 { 289 Com_Printf ("Failed to open %s\n", name); 290 return; 291 } 292 fwrite (sv.configstrings, sizeof(sv.configstrings), 1, f); 293 CM_WritePortalState (f); 294 fclose (f); 295 296 Com_sprintf (name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name); 297 ge->WriteLevel (name); 298 } 299 300 /* 301 ============== 302 SV_ReadLevelFile 303 304 ============== 305 */ 306 void SV_ReadLevelFile (void) 307 { 308 char name[MAX_OSPATH]; 309 FILE *f; 310 311 Com_DPrintf("SV_ReadLevelFile()\n"); 312 313 Com_sprintf (name, sizeof(name), "%s/save/current/%s.sv2", FS_Gamedir(), sv.name); 314 f = fopen(name, "rb"); 315 if (!f) 316 { 317 Com_Printf ("Failed to open %s\n", name); 318 return; 319 } 320 FS_Read (sv.configstrings, sizeof(sv.configstrings), f); 321 CM_ReadPortalState (f); 322 fclose (f); 323 324 Com_sprintf (name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name); 325 ge->ReadLevel (name); 326 } 327 328 /* 329 ============== 330 SV_WriteServerFile 331 332 ============== 333 */ 334 void SV_WriteServerFile (qboolean autosave) 335 { 336 FILE *f; 337 cvar_t *var; 338 char name[MAX_OSPATH], string[128]; 339 char comment[32]; 340 time_t aclock; 341 struct tm *newtime; 342 343 Com_DPrintf("SV_WriteServerFile(%s)\n", autosave ? "true" : "false"); 344 345 Com_sprintf (name, sizeof(name), "%s/save/current/server.ssv", FS_Gamedir()); 346 f = fopen (name, "wb"); 347 if (!f) 348 { 349 Com_Printf ("Couldn't write %s\n", name); 350 return; 351 } 352 // write the comment field 353 memset (comment, 0, sizeof(comment)); 354 355 if (!autosave) 356 { 357 time (&aclock); 358 newtime = localtime (&aclock); 359 Com_sprintf (comment,sizeof(comment), "%2i:%i%i %2i/%2i ", newtime->tm_hour 360 , newtime->tm_min/10, newtime->tm_min%10, 361 newtime->tm_mon+1, newtime->tm_mday); 362 strncat (comment, sv.configstrings[CS_NAME], sizeof(comment)-1-strlen(comment) ); 363 } 364 else 365 { // autosaved 366 Com_sprintf (comment, sizeof(comment), "ENTERING %s", sv.configstrings[CS_NAME]); 367 } 368 369 fwrite (comment, 1, sizeof(comment), f); 370 371 // write the mapcmd 372 fwrite (svs.mapcmd, 1, sizeof(svs.mapcmd), f); 373 374 // write all CVAR_LATCH cvars 375 // these will be things like coop, skill, deathmatch, etc 376 for (var = cvar_vars ; var ; var=var->next) 377 { 378 if (!(var->flags & CVAR_LATCH)) 379 continue; 380 if (strlen(var->name) >= sizeof(name)-1 381 || strlen(var->string) >= sizeof(string)-1) 382 { 383 Com_Printf ("Cvar too long: %s = %s\n", var->name, var->string); 384 continue; 385 } 386 memset (name, 0, sizeof(name)); 387 memset (string, 0, sizeof(string)); 388 strcpy (name, var->name); 389 strcpy (string, var->string); 390 fwrite (name, 1, sizeof(name), f); 391 fwrite (string, 1, sizeof(string), f); 392 } 393 394 fclose (f); 395 396 // write game state 397 Com_sprintf (name, sizeof(name), "%s/save/current/game.ssv", FS_Gamedir()); 398 ge->WriteGame (name, autosave); 399 } 400 401 /* 402 ============== 403 SV_ReadServerFile 404 405 ============== 406 */ 407 void SV_ReadServerFile (void) 408 { 409 FILE *f; 410 char name[MAX_OSPATH], string[128]; 411 char comment[32]; 412 char mapcmd[MAX_TOKEN_CHARS]; 413 414 Com_DPrintf("SV_ReadServerFile()\n"); 415 416 Com_sprintf (name, sizeof(name), "%s/save/current/server.ssv", FS_Gamedir()); 417 f = fopen (name, "rb"); 418 if (!f) 419 { 420 Com_Printf ("Couldn't read %s\n", name); 421 return; 422 } 423 // read the comment field 424 FS_Read (comment, sizeof(comment), f); 425 426 // read the mapcmd 427 FS_Read (mapcmd, sizeof(mapcmd), f); 428 429 // read all CVAR_LATCH cvars 430 // these will be things like coop, skill, deathmatch, etc 431 while (1) 432 { 433 if (!fread (name, 1, sizeof(name), f)) 434 break; 435 FS_Read (string, sizeof(string), f); 436 Com_DPrintf ("Set %s = %s\n", name, string); 437 Cvar_ForceSet (name, string); 438 } 439 440 fclose (f); 441 442 // start a new game fresh with new cvars 443 SV_InitGame (); 444 445 strcpy (svs.mapcmd, mapcmd); 446 447 // read game state 448 Com_sprintf (name, sizeof(name), "%s/save/current/game.ssv", FS_Gamedir()); 449 ge->ReadGame (name); 450 } 451 452 453 //========================================================= 454 455 456 457 458 /* 459 ================== 460 SV_DemoMap_f 461 462 Puts the server in demo mode on a specific map/cinematic 463 ================== 464 */ 465 void SV_DemoMap_f (void) 466 { 467 SV_Map (true, Cmd_Argv(1), false ); 468 } 469 470 /* 471 ================== 472 SV_GameMap_f 473 474 Saves the state of the map just being exited and goes to a new map. 475 476 If the initial character of the map string is '*', the next map is 477 in a new unit, so the current savegame directory is cleared of 478 map files. 479 480 Example: 481 482 *inter.cin+jail 483 484 Clears the archived maps, plays the inter.cin cinematic, then 485 goes to map jail.bsp. 486 ================== 487 */ 488 void SV_GameMap_f (void) 489 { 490 char *map; 491 int i; 492 client_t *cl; 493 qboolean *savedInuse; 494 495 if (Cmd_Argc() != 2) 496 { 497 Com_Printf ("USAGE: gamemap <map>\n"); 498 return; 499 } 500 501 Com_DPrintf("SV_GameMap(%s)\n", Cmd_Argv(1)); 502 503 FS_CreatePath (va("%s/save/current/", FS_Gamedir())); 504 505 // check for clearing the current savegame 506 map = Cmd_Argv(1); 507 if (map[0] == '*') 508 { 509 // wipe all the *.sav files 510 SV_WipeSavegame ("current"); 511 } 512 else 513 { // save the map just exited 514 if (sv.state == ss_game) 515 { 516 // clear all the client inuse flags before saving so that 517 // when the level is re-entered, the clients will spawn 518 // at spawn points instead of occupying body shells 519 savedInuse = malloc(maxclients->value * sizeof(qboolean)); 520 for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++) 521 { 522 savedInuse[i] = cl->edict->inuse; 523 cl->edict->inuse = false; 524 } 525 526 SV_WriteLevelFile (); 527 528 // we must restore these for clients to transfer over correctly 529 for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++) 530 cl->edict->inuse = savedInuse[i]; 531 free (savedInuse); 532 } 533 } 534 535 // start up the next map 536 SV_Map (false, Cmd_Argv(1), false ); 537 538 // archive server state 539 strncpy (svs.mapcmd, Cmd_Argv(1), sizeof(svs.mapcmd)-1); 540 541 // copy off the level to the autosave slot 542 if (!dedicated->value) 543 { 544 SV_WriteServerFile (true); 545 SV_CopySaveGame ("current", "save0"); 546 } 547 } 548 549 /* 550 ================== 551 SV_Map_f 552 553 Goes directly to a given map without any savegame archiving. 554 For development work 555 ================== 556 */ 557 void SV_Map_f (void) 558 { 559 char *map; 560 char expanded[MAX_QPATH]; 561 562 // if not a pcx, demo, or cinematic, check to make sure the level exists 563 map = Cmd_Argv(1); 564 if (!strstr (map, ".")) 565 { 566 Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map); 567 if (FS_LoadFile (expanded, NULL) == -1) 568 { 569 Com_Printf ("Can't find %s\n", expanded); 570 return; 571 } 572 } 573 574 sv.state = ss_dead; // don't save current level when changing 575 SV_WipeSavegame("current"); 576 SV_GameMap_f (); 577 } 578 579 /* 580 ===================================================================== 581 582 SAVEGAMES 583 584 ===================================================================== 585 */ 586 587 588 /* 589 ============== 590 SV_Loadgame_f 591 592 ============== 593 */ 594 void SV_Loadgame_f (void) 595 { 596 char name[MAX_OSPATH]; 597 FILE *f; 598 char *dir; 599 600 if (Cmd_Argc() != 2) 601 { 602 Com_Printf ("USAGE: loadgame <directory>\n"); 603 return; 604 } 605 606 Com_Printf ("Loading game...\n"); 607 608 dir = Cmd_Argv(1); 609 if (strstr (dir, "..") || strstr (dir, "/") || strstr (dir, "\\") ) 610 { 611 Com_Printf ("Bad savedir.\n"); 612 } 613 614 // make sure the server.ssv file exists 615 Com_sprintf (name, sizeof(name), "%s/save/%s/server.ssv", FS_Gamedir(), Cmd_Argv(1)); 616 f = fopen (name, "rb"); 617 if (!f) 618 { 619 Com_Printf ("No such savegame: %s\n", name); 620 return; 621 } 622 fclose (f); 623 624 SV_CopySaveGame (Cmd_Argv(1), "current"); 625 626 SV_ReadServerFile (); 627 628 // go to the map 629 sv.state = ss_dead; // don't save current level when changing 630 SV_Map (false, svs.mapcmd, true); 631 } 632 633 634 635 /* 636 ============== 637 SV_Savegame_f 638 639 ============== 640 */ 641 void SV_Savegame_f (void) 642 { 643 char *dir; 644 645 if (sv.state != ss_game) 646 { 647 Com_Printf ("You must be in a game to save.\n"); 648 return; 649 } 650 651 if (Cmd_Argc() != 2) 652 { 653 Com_Printf ("USAGE: savegame <directory>\n"); 654 return; 655 } 656 657 if (Cvar_VariableValue("deathmatch")) 658 { 659 Com_Printf ("Can't savegame in a deathmatch\n"); 660 return; 661 } 662 663 if (!strcmp (Cmd_Argv(1), "current")) 664 { 665 Com_Printf ("Can't save to 'current'\n"); 666 return; 667 } 668 669 if (maxclients->value == 1 && svs.clients[0].edict->client->ps.stats[STAT_HEALTH] <= 0) 670 { 671 Com_Printf ("\nCan't savegame while dead!\n"); 672 return; 673 } 674 675 dir = Cmd_Argv(1); 676 if (strstr (dir, "..") || strstr (dir, "/") || strstr (dir, "\\") ) 677 { 678 Com_Printf ("Bad savedir.\n"); 679 } 680 681 Com_Printf ("Saving game...\n"); 682 683 // archive current level, including all client edicts. 684 // when the level is reloaded, they will be shells awaiting 685 // a connecting client 686 SV_WriteLevelFile (); 687 688 // save server state 689 SV_WriteServerFile (false); 690 691 // copy it off 692 SV_CopySaveGame ("current", dir); 693 694 Com_Printf ("Done.\n"); 695 } 696 697 //=============================================================== 698 699 /* 700 ================== 701 SV_Kick_f 702 703 Kick a user off of the server 704 ================== 705 */ 706 void SV_Kick_f (void) 707 { 708 if (!svs.initialized) 709 { 710 Com_Printf ("No server running.\n"); 711 return; 712 } 713 714 if (Cmd_Argc() != 2) 715 { 716 Com_Printf ("Usage: kick <userid>\n"); 717 return; 718 } 719 720 if (!SV_SetPlayer ()) 721 return; 722 723 SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked\n", sv_client->name); 724 // print directly, because the dropped client won't get the 725 // SV_BroadcastPrintf message 726 SV_ClientPrintf (sv_client, PRINT_HIGH, "You were kicked from the game\n"); 727 SV_DropClient (sv_client); 728 sv_client->lastmessage = svs.realtime; // min case there is a funny zombie 729 } 730 731 732 /* 733 ================ 734 SV_Status_f 735 ================ 736 */ 737 void SV_Status_f (void) 738 { 739 int i, j, l; 740 client_t *cl; 741 char *s; 742 int ping; 743 if (!svs.clients) 744 { 745 Com_Printf ("No server running.\n"); 746 return; 747 } 748 Com_Printf ("map : %s\n", sv.name); 749 750 Com_Printf ("num score ping name lastmsg address qport \n"); 751 Com_Printf ("--- ----- ---- --------------- ------- --------------------- ------\n"); 752 for (i=0,cl=svs.clients ; i<maxclients->value; i++,cl++) 753 { 754 if (!cl->state) 755 continue; 756 Com_Printf ("%3i ", i); 757 Com_Printf ("%5i ", cl->edict->client->ps.stats[STAT_FRAGS]); 758 759 if (cl->state == cs_connected) 760 Com_Printf ("CNCT "); 761 else if (cl->state == cs_zombie) 762 Com_Printf ("ZMBI "); 763 else 764 { 765 ping = cl->ping < 9999 ? cl->ping : 9999; 766 Com_Printf ("%4i ", ping); 767 } 768 769 Com_Printf ("%s", cl->name); 770 l = 16 - strlen(cl->name); 771 for (j=0 ; j<l ; j++) 772 Com_Printf (" "); 773 774 Com_Printf ("%7i ", svs.realtime - cl->lastmessage ); 775 776 s = NET_AdrToString ( cl->netchan.remote_address); 777 Com_Printf ("%s", s); 778 l = 22 - strlen(s); 779 for (j=0 ; j<l ; j++) 780 Com_Printf (" "); 781 782 Com_Printf ("%5i", cl->netchan.qport); 783 784 Com_Printf ("\n"); 785 } 786 Com_Printf ("\n"); 787 } 788 789 /* 790 ================== 791 SV_ConSay_f 792 ================== 793 */ 794 void SV_ConSay_f(void) 795 { 796 client_t *client; 797 int j; 798 char *p; 799 char text[1024]; 800 801 if (Cmd_Argc () < 2) 802 return; 803 804 strcpy (text, "console: "); 805 p = Cmd_Args(); 806 807 if (*p == '"') 808 { 809 p++; 810 p[strlen(p)-1] = 0; 811 } 812 813 strcat(text, p); 814 815 for (j = 0, client = svs.clients; j < maxclients->value; j++, client++) 816 { 817 if (client->state != cs_spawned) 818 continue; 819 SV_ClientPrintf(client, PRINT_CHAT, "%s\n", text); 820 } 821 } 822 823 824 /* 825 ================== 826 SV_Heartbeat_f 827 ================== 828 */ 829 void SV_Heartbeat_f (void) 830 { 831 svs.last_heartbeat = -9999999; 832 } 833 834 835 /* 836 =========== 837 SV_Serverinfo_f 838 839 Examine or change the serverinfo string 840 =========== 841 */ 842 void SV_Serverinfo_f (void) 843 { 844 Com_Printf ("Server info settings:\n"); 845 Info_Print (Cvar_Serverinfo()); 846 } 847 848 849 /* 850 =========== 851 SV_DumpUser_f 852 853 Examine all a users info strings 854 =========== 855 */ 856 void SV_DumpUser_f (void) 857 { 858 if (Cmd_Argc() != 2) 859 { 860 Com_Printf ("Usage: info <userid>\n"); 861 return; 862 } 863 864 if (!SV_SetPlayer ()) 865 return; 866 867 Com_Printf ("userinfo\n"); 868 Com_Printf ("--------\n"); 869 Info_Print (sv_client->userinfo); 870 871 } 872 873 874 /* 875 ============== 876 SV_ServerRecord_f 877 878 Begins server demo recording. Every entity and every message will be 879 recorded, but no playerinfo will be stored. Primarily for demo merging. 880 ============== 881 */ 882 void SV_ServerRecord_f (void) 883 { 884 char name[MAX_OSPATH]; 885 char buf_data[32768]; 886 sizebuf_t buf; 887 int len; 888 int i; 889 890 if (Cmd_Argc() != 2) 891 { 892 Com_Printf ("serverrecord <demoname>\n"); 893 return; 894 } 895 896 if (svs.demofile) 897 { 898 Com_Printf ("Already recording.\n"); 899 return; 900 } 901 902 if (sv.state != ss_game) 903 { 904 Com_Printf ("You must be in a level to record.\n"); 905 return; 906 } 907 908 // 909 // open the demo file 910 // 911 Com_sprintf (name, sizeof(name), "%s/demos/%s.dm2", FS_Gamedir(), Cmd_Argv(1)); 912 913 Com_Printf ("recording to %s.\n", name); 914 FS_CreatePath (name); 915 svs.demofile = fopen (name, "wb"); 916 if (!svs.demofile) 917 { 918 Com_Printf ("ERROR: couldn't open.\n"); 919 return; 920 } 921 922 // setup a buffer to catch all multicasts 923 SZ_Init (&svs.demo_multicast, svs.demo_multicast_buf, sizeof(svs.demo_multicast_buf)); 924 925 // 926 // write a single giant fake message with all the startup info 927 // 928 SZ_Init (&buf, buf_data, sizeof(buf_data)); 929 930 // 931 // serverdata needs to go over for all types of servers 932 // to make sure the protocol is right, and to set the gamedir 933 // 934 // send the serverdata 935 MSG_WriteByte (&buf, svc_serverdata); 936 MSG_WriteLong (&buf, PROTOCOL_VERSION); 937 MSG_WriteLong (&buf, svs.spawncount); 938 // 2 means server demo 939 MSG_WriteByte (&buf, 2); // demos are always attract loops 940 MSG_WriteString (&buf, Cvar_VariableString ("gamedir")); 941 MSG_WriteShort (&buf, -1); 942 // send full levelname 943 MSG_WriteString (&buf, sv.configstrings[CS_NAME]); 944 945 for (i=0 ; i<MAX_CONFIGSTRINGS ; i++) 946 if (sv.configstrings[i][0]) 947 { 948 MSG_WriteByte (&buf, svc_configstring); 949 MSG_WriteShort (&buf, i); 950 MSG_WriteString (&buf, sv.configstrings[i]); 951 } 952 953 // write it to the demo file 954 Com_DPrintf ("signon message length: %i\n", buf.cursize); 955 len = LittleLong (buf.cursize); 956 fwrite (&len, 4, 1, svs.demofile); 957 fwrite (buf.data, buf.cursize, 1, svs.demofile); 958 959 // the rest of the demo file will be individual frames 960 } 961 962 963 /* 964 ============== 965 SV_ServerStop_f 966 967 Ends server demo recording 968 ============== 969 */ 970 void SV_ServerStop_f (void) 971 { 972 if (!svs.demofile) 973 { 974 Com_Printf ("Not doing a serverrecord.\n"); 975 return; 976 } 977 fclose (svs.demofile); 978 svs.demofile = NULL; 979 Com_Printf ("Recording completed.\n"); 980 } 981 982 983 /* 984 =============== 985 SV_KillServer_f 986 987 Kick everyone off, possibly in preparation for a new game 988 989 =============== 990 */ 991 void SV_KillServer_f (void) 992 { 993 if (!svs.initialized) 994 return; 995 SV_Shutdown ("Server was killed.\n", false); 996 NET_Config ( false ); // close network sockets 997 } 998 999 /* 1000 =============== 1001 SV_ServerCommand_f 1002 1003 Let the game dll handle a command 1004 =============== 1005 */ 1006 void SV_ServerCommand_f (void) 1007 { 1008 if (!ge) 1009 { 1010 Com_Printf ("No game loaded.\n"); 1011 return; 1012 } 1013 1014 ge->ServerCommand(); 1015 } 1016 1017 //=========================================================== 1018 1019 /* 1020 ================== 1021 SV_InitOperatorCommands 1022 ================== 1023 */ 1024 void SV_InitOperatorCommands (void) 1025 { 1026 Cmd_AddCommand ("heartbeat", SV_Heartbeat_f); 1027 Cmd_AddCommand ("kick", SV_Kick_f); 1028 Cmd_AddCommand ("status", SV_Status_f); 1029 Cmd_AddCommand ("serverinfo", SV_Serverinfo_f); 1030 Cmd_AddCommand ("dumpuser", SV_DumpUser_f); 1031 1032 Cmd_AddCommand ("map", SV_Map_f); 1033 Cmd_AddCommand ("demomap", SV_DemoMap_f); 1034 Cmd_AddCommand ("gamemap", SV_GameMap_f); 1035 Cmd_AddCommand ("setmaster", SV_SetMaster_f); 1036 1037 if ( dedicated->value ) 1038 Cmd_AddCommand ("say", SV_ConSay_f); 1039 1040 Cmd_AddCommand ("serverrecord", SV_ServerRecord_f); 1041 Cmd_AddCommand ("serverstop", SV_ServerStop_f); 1042 1043 Cmd_AddCommand ("save", SV_Savegame_f); 1044 Cmd_AddCommand ("load", SV_Loadgame_f); 1045 1046 Cmd_AddCommand ("killserver", SV_KillServer_f); 1047 1048 Cmd_AddCommand ("sv", SV_ServerCommand_f); 1049 } 1050