sv_ccmds.c (18051B)
1 /* 2 =========================================================================== 3 Copyright (C) 1999-2005 Id Software, Inc. 4 5 This file is part of Quake III Arena source code. 6 7 Quake III Arena source code is free software; you can redistribute it 8 and/or modify it under the terms of the GNU General Public License as 9 published by the Free Software Foundation; either version 2 of the License, 10 or (at your option) any later version. 11 12 Quake III Arena source code is distributed in the hope that it will be 13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with Foobar; if not, write to the Free Software 19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 =========================================================================== 21 */ 22 23 #include "server.h" 24 25 /* 26 =============================================================================== 27 28 OPERATOR CONSOLE ONLY COMMANDS 29 30 These commands can only be entered from stdin or by a remote operator datagram 31 =============================================================================== 32 */ 33 34 35 /* 36 ================== 37 SV_GetPlayerByName 38 39 Returns the player with name from Cmd_Argv(1) 40 ================== 41 */ 42 static client_t *SV_GetPlayerByName( void ) { 43 client_t *cl; 44 int i; 45 char *s; 46 char cleanName[64]; 47 48 // make sure server is running 49 if ( !com_sv_running->integer ) { 50 return NULL; 51 } 52 53 if ( Cmd_Argc() < 2 ) { 54 Com_Printf( "No player specified.\n" ); 55 return NULL; 56 } 57 58 s = Cmd_Argv(1); 59 60 // check for a name match 61 for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { 62 if ( !cl->state ) { 63 continue; 64 } 65 if ( !Q_stricmp( cl->name, s ) ) { 66 return cl; 67 } 68 69 Q_strncpyz( cleanName, cl->name, sizeof(cleanName) ); 70 Q_CleanStr( cleanName ); 71 if ( !Q_stricmp( cleanName, s ) ) { 72 return cl; 73 } 74 } 75 76 Com_Printf( "Player %s is not on the server\n", s ); 77 78 return NULL; 79 } 80 81 /* 82 ================== 83 SV_GetPlayerByNum 84 85 Returns the player with idnum from Cmd_Argv(1) 86 ================== 87 */ 88 static client_t *SV_GetPlayerByNum( void ) { 89 client_t *cl; 90 int i; 91 int idnum; 92 char *s; 93 94 // make sure server is running 95 if ( !com_sv_running->integer ) { 96 return NULL; 97 } 98 99 if ( Cmd_Argc() < 2 ) { 100 Com_Printf( "No player specified.\n" ); 101 return NULL; 102 } 103 104 s = Cmd_Argv(1); 105 106 for (i = 0; s[i]; i++) { 107 if (s[i] < '0' || s[i] > '9') { 108 Com_Printf( "Bad slot number: %s\n", s); 109 return NULL; 110 } 111 } 112 idnum = atoi( s ); 113 if ( idnum < 0 || idnum >= sv_maxclients->integer ) { 114 Com_Printf( "Bad client slot: %i\n", idnum ); 115 return NULL; 116 } 117 118 cl = &svs.clients[idnum]; 119 if ( !cl->state ) { 120 Com_Printf( "Client %i is not active\n", idnum ); 121 return NULL; 122 } 123 return cl; 124 125 return NULL; 126 } 127 128 //========================================================= 129 130 131 /* 132 ================== 133 SV_Map_f 134 135 Restart the server on a different map 136 ================== 137 */ 138 static void SV_Map_f( void ) { 139 char *cmd; 140 char *map; 141 qboolean killBots, cheat; 142 char expanded[MAX_QPATH]; 143 char mapname[MAX_QPATH]; 144 145 map = Cmd_Argv(1); 146 if ( !map ) { 147 return; 148 } 149 150 // make sure the level exists before trying to change, so that 151 // a typo at the server console won't end the game 152 Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map); 153 if ( FS_ReadFile (expanded, NULL) == -1 ) { 154 Com_Printf ("Can't find map %s\n", expanded); 155 return; 156 } 157 158 // force latched values to get set 159 Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH ); 160 161 cmd = Cmd_Argv(0); 162 if( Q_stricmpn( cmd, "sp", 2 ) == 0 ) { 163 Cvar_SetValue( "g_gametype", GT_SINGLE_PLAYER ); 164 Cvar_SetValue( "g_doWarmup", 0 ); 165 // may not set sv_maxclients directly, always set latched 166 Cvar_SetLatched( "sv_maxclients", "8" ); 167 cmd += 2; 168 cheat = qfalse; 169 killBots = qtrue; 170 } 171 else { 172 if ( !Q_stricmp( cmd, "devmap" ) || !Q_stricmp( cmd, "spdevmap" ) ) { 173 cheat = qtrue; 174 killBots = qtrue; 175 } else { 176 cheat = qfalse; 177 killBots = qfalse; 178 } 179 if( sv_gametype->integer == GT_SINGLE_PLAYER ) { 180 Cvar_SetValue( "g_gametype", GT_FFA ); 181 } 182 } 183 184 // save the map name here cause on a map restart we reload the q3config.cfg 185 // and thus nuke the arguments of the map command 186 Q_strncpyz(mapname, map, sizeof(mapname)); 187 188 // start up the map 189 SV_SpawnServer( mapname, killBots ); 190 191 // set the cheat value 192 // if the level was started with "map <levelname>", then 193 // cheats will not be allowed. If started with "devmap <levelname>" 194 // then cheats will be allowed 195 if ( cheat ) { 196 Cvar_Set( "sv_cheats", "1" ); 197 } else { 198 Cvar_Set( "sv_cheats", "0" ); 199 } 200 } 201 202 /* 203 ================ 204 SV_MapRestart_f 205 206 Completely restarts a level, but doesn't send a new gamestate to the clients. 207 This allows fair starts with variable load times. 208 ================ 209 */ 210 static void SV_MapRestart_f( void ) { 211 int i; 212 client_t *client; 213 char *denied; 214 qboolean isBot; 215 int delay; 216 217 // make sure we aren't restarting twice in the same frame 218 if ( com_frameTime == sv.serverId ) { 219 return; 220 } 221 222 // make sure server is running 223 if ( !com_sv_running->integer ) { 224 Com_Printf( "Server is not running.\n" ); 225 return; 226 } 227 228 if ( sv.restartTime ) { 229 return; 230 } 231 232 if (Cmd_Argc() > 1 ) { 233 delay = atoi( Cmd_Argv(1) ); 234 } 235 else { 236 delay = 5; 237 } 238 if( delay && !Cvar_VariableValue("g_doWarmup") ) { 239 sv.restartTime = svs.time + delay * 1000; 240 SV_SetConfigstring( CS_WARMUP, va("%i", sv.restartTime) ); 241 return; 242 } 243 244 // check for changes in variables that can't just be restarted 245 // check for maxclients change 246 if ( sv_maxclients->modified || sv_gametype->modified ) { 247 char mapname[MAX_QPATH]; 248 249 Com_Printf( "variable change -- restarting.\n" ); 250 // restart the map the slow way 251 Q_strncpyz( mapname, Cvar_VariableString( "mapname" ), sizeof( mapname ) ); 252 253 SV_SpawnServer( mapname, qfalse ); 254 return; 255 } 256 257 // toggle the server bit so clients can detect that a 258 // map_restart has happened 259 svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; 260 261 // generate a new serverid 262 // TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart 263 sv.serverId = com_frameTime; 264 Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); 265 266 // reset all the vm data in place without changing memory allocation 267 // note that we do NOT set sv.state = SS_LOADING, so configstrings that 268 // had been changed from their default values will generate broadcast updates 269 sv.state = SS_LOADING; 270 sv.restarting = qtrue; 271 272 SV_RestartGameProgs(); 273 274 // run a few frames to allow everything to settle 275 for ( i = 0 ;i < 3 ; i++ ) { 276 VM_Call( gvm, GAME_RUN_FRAME, svs.time ); 277 svs.time += 100; 278 } 279 280 sv.state = SS_GAME; 281 sv.restarting = qfalse; 282 283 // connect and begin all the clients 284 for (i=0 ; i<sv_maxclients->integer ; i++) { 285 client = &svs.clients[i]; 286 287 // send the new gamestate to all connected clients 288 if ( client->state < CS_CONNECTED) { 289 continue; 290 } 291 292 if ( client->netchan.remoteAddress.type == NA_BOT ) { 293 isBot = qtrue; 294 } else { 295 isBot = qfalse; 296 } 297 298 // add the map_restart command 299 SV_AddServerCommand( client, "map_restart\n" ); 300 301 // connect the client again, without the firstTime flag 302 denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) ); 303 if ( denied ) { 304 // this generally shouldn't happen, because the client 305 // was connected before the level change 306 SV_DropClient( client, denied ); 307 Com_Printf( "SV_MapRestart_f(%d): dropped client %i - denied!\n", delay, i ); // bk010125 308 continue; 309 } 310 311 client->state = CS_ACTIVE; 312 313 SV_ClientEnterWorld( client, &client->lastUsercmd ); 314 } 315 316 // run another frame to allow things to look at all the players 317 VM_Call( gvm, GAME_RUN_FRAME, svs.time ); 318 svs.time += 100; 319 } 320 321 //=============================================================== 322 323 /* 324 ================== 325 SV_Kick_f 326 327 Kick a user off of the server FIXME: move to game 328 ================== 329 */ 330 static void SV_Kick_f( void ) { 331 client_t *cl; 332 int i; 333 334 // make sure server is running 335 if ( !com_sv_running->integer ) { 336 Com_Printf( "Server is not running.\n" ); 337 return; 338 } 339 340 if ( Cmd_Argc() != 2 ) { 341 Com_Printf ("Usage: kick <player name>\nkick all = kick everyone\nkick allbots = kick all bots\n"); 342 return; 343 } 344 345 cl = SV_GetPlayerByName(); 346 if ( !cl ) { 347 if ( !Q_stricmp(Cmd_Argv(1), "all") ) { 348 for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { 349 if ( !cl->state ) { 350 continue; 351 } 352 if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { 353 continue; 354 } 355 SV_DropClient( cl, "was kicked" ); 356 cl->lastPacketTime = svs.time; // in case there is a funny zombie 357 } 358 } 359 else if ( !Q_stricmp(Cmd_Argv(1), "allbots") ) { 360 for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { 361 if ( !cl->state ) { 362 continue; 363 } 364 if( cl->netchan.remoteAddress.type != NA_BOT ) { 365 continue; 366 } 367 SV_DropClient( cl, "was kicked" ); 368 cl->lastPacketTime = svs.time; // in case there is a funny zombie 369 } 370 } 371 return; 372 } 373 if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { 374 SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); 375 return; 376 } 377 378 SV_DropClient( cl, "was kicked" ); 379 cl->lastPacketTime = svs.time; // in case there is a funny zombie 380 } 381 382 /* 383 ================== 384 SV_Ban_f 385 386 Ban a user from being able to play on this server through the auth 387 server 388 ================== 389 */ 390 static void SV_Ban_f( void ) { 391 client_t *cl; 392 393 // make sure server is running 394 if ( !com_sv_running->integer ) { 395 Com_Printf( "Server is not running.\n" ); 396 return; 397 } 398 399 if ( Cmd_Argc() != 2 ) { 400 Com_Printf ("Usage: banUser <player name>\n"); 401 return; 402 } 403 404 cl = SV_GetPlayerByName(); 405 406 if (!cl) { 407 return; 408 } 409 410 if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { 411 SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); 412 return; 413 } 414 415 // look up the authorize server's IP 416 if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { 417 Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); 418 if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { 419 Com_Printf( "Couldn't resolve address\n" ); 420 return; 421 } 422 svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); 423 Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, 424 svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], 425 svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], 426 BigShort( svs.authorizeAddress.port ) ); 427 } 428 429 // otherwise send their ip to the authorize server 430 if ( svs.authorizeAddress.type != NA_BAD ) { 431 NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, 432 "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], 433 cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); 434 Com_Printf("%s was banned from coming back\n", cl->name); 435 } 436 } 437 438 /* 439 ================== 440 SV_BanNum_f 441 442 Ban a user from being able to play on this server through the auth 443 server 444 ================== 445 */ 446 static void SV_BanNum_f( void ) { 447 client_t *cl; 448 449 // make sure server is running 450 if ( !com_sv_running->integer ) { 451 Com_Printf( "Server is not running.\n" ); 452 return; 453 } 454 455 if ( Cmd_Argc() != 2 ) { 456 Com_Printf ("Usage: banClient <client number>\n"); 457 return; 458 } 459 460 cl = SV_GetPlayerByNum(); 461 if ( !cl ) { 462 return; 463 } 464 if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { 465 SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); 466 return; 467 } 468 469 // look up the authorize server's IP 470 if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { 471 Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); 472 if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { 473 Com_Printf( "Couldn't resolve address\n" ); 474 return; 475 } 476 svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); 477 Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, 478 svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], 479 svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], 480 BigShort( svs.authorizeAddress.port ) ); 481 } 482 483 // otherwise send their ip to the authorize server 484 if ( svs.authorizeAddress.type != NA_BAD ) { 485 NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, 486 "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], 487 cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); 488 Com_Printf("%s was banned from coming back\n", cl->name); 489 } 490 } 491 492 /* 493 ================== 494 SV_KickNum_f 495 496 Kick a user off of the server FIXME: move to game 497 ================== 498 */ 499 static void SV_KickNum_f( void ) { 500 client_t *cl; 501 502 // make sure server is running 503 if ( !com_sv_running->integer ) { 504 Com_Printf( "Server is not running.\n" ); 505 return; 506 } 507 508 if ( Cmd_Argc() != 2 ) { 509 Com_Printf ("Usage: kicknum <client number>\n"); 510 return; 511 } 512 513 cl = SV_GetPlayerByNum(); 514 if ( !cl ) { 515 return; 516 } 517 if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { 518 SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); 519 return; 520 } 521 522 SV_DropClient( cl, "was kicked" ); 523 cl->lastPacketTime = svs.time; // in case there is a funny zombie 524 } 525 526 /* 527 ================ 528 SV_Status_f 529 ================ 530 */ 531 static void SV_Status_f( void ) { 532 int i, j, l; 533 client_t *cl; 534 playerState_t *ps; 535 const char *s; 536 int ping; 537 538 // make sure server is running 539 if ( !com_sv_running->integer ) { 540 Com_Printf( "Server is not running.\n" ); 541 return; 542 } 543 544 Com_Printf ("map: %s\n", sv_mapname->string ); 545 546 Com_Printf ("num score ping name lastmsg address qport rate\n"); 547 Com_Printf ("--- ----- ---- --------------- ------- --------------------- ----- -----\n"); 548 for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) 549 { 550 if (!cl->state) 551 continue; 552 Com_Printf ("%3i ", i); 553 ps = SV_GameClientNum( i ); 554 Com_Printf ("%5i ", ps->persistant[PERS_SCORE]); 555 556 if (cl->state == CS_CONNECTED) 557 Com_Printf ("CNCT "); 558 else if (cl->state == CS_ZOMBIE) 559 Com_Printf ("ZMBI "); 560 else 561 { 562 ping = cl->ping < 9999 ? cl->ping : 9999; 563 Com_Printf ("%4i ", ping); 564 } 565 566 Com_Printf ("%s", cl->name); 567 // TTimo adding a ^7 to reset the color 568 // NOTE: colored names in status breaks the padding (WONTFIX) 569 Com_Printf ("^7"); 570 l = 16 - strlen(cl->name); 571 for (j=0 ; j<l ; j++) 572 Com_Printf (" "); 573 574 Com_Printf ("%7i ", svs.time - cl->lastPacketTime ); 575 576 s = NET_AdrToString( cl->netchan.remoteAddress ); 577 Com_Printf ("%s", s); 578 l = 22 - strlen(s); 579 for (j=0 ; j<l ; j++) 580 Com_Printf (" "); 581 582 Com_Printf ("%5i", cl->netchan.qport); 583 584 Com_Printf (" %5i", cl->rate); 585 586 Com_Printf ("\n"); 587 } 588 Com_Printf ("\n"); 589 } 590 591 /* 592 ================== 593 SV_ConSay_f 594 ================== 595 */ 596 static void SV_ConSay_f(void) { 597 char *p; 598 char text[1024]; 599 600 // make sure server is running 601 if ( !com_sv_running->integer ) { 602 Com_Printf( "Server is not running.\n" ); 603 return; 604 } 605 606 if ( Cmd_Argc () < 2 ) { 607 return; 608 } 609 610 strcpy (text, "console: "); 611 p = Cmd_Args(); 612 613 if ( *p == '"' ) { 614 p++; 615 p[strlen(p)-1] = 0; 616 } 617 618 strcat(text, p); 619 620 SV_SendServerCommand(NULL, "chat \"%s\n\"", text); 621 } 622 623 624 /* 625 ================== 626 SV_Heartbeat_f 627 628 Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer 629 ================== 630 */ 631 void SV_Heartbeat_f( void ) { 632 svs.nextHeartbeatTime = -9999999; 633 } 634 635 636 /* 637 =========== 638 SV_Serverinfo_f 639 640 Examine the serverinfo string 641 =========== 642 */ 643 static void SV_Serverinfo_f( void ) { 644 Com_Printf ("Server info settings:\n"); 645 Info_Print ( Cvar_InfoString( CVAR_SERVERINFO ) ); 646 } 647 648 649 /* 650 =========== 651 SV_Systeminfo_f 652 653 Examine or change the serverinfo string 654 =========== 655 */ 656 static void SV_Systeminfo_f( void ) { 657 Com_Printf ("System info settings:\n"); 658 Info_Print ( Cvar_InfoString( CVAR_SYSTEMINFO ) ); 659 } 660 661 662 /* 663 =========== 664 SV_DumpUser_f 665 666 Examine all a users info strings FIXME: move to game 667 =========== 668 */ 669 static void SV_DumpUser_f( void ) { 670 client_t *cl; 671 672 // make sure server is running 673 if ( !com_sv_running->integer ) { 674 Com_Printf( "Server is not running.\n" ); 675 return; 676 } 677 678 if ( Cmd_Argc() != 2 ) { 679 Com_Printf ("Usage: info <userid>\n"); 680 return; 681 } 682 683 cl = SV_GetPlayerByName(); 684 if ( !cl ) { 685 return; 686 } 687 688 Com_Printf( "userinfo\n" ); 689 Com_Printf( "--------\n" ); 690 Info_Print( cl->userinfo ); 691 } 692 693 694 /* 695 ================= 696 SV_KillServer 697 ================= 698 */ 699 static void SV_KillServer_f( void ) { 700 SV_Shutdown( "killserver" ); 701 } 702 703 //=========================================================== 704 705 /* 706 ================== 707 SV_AddOperatorCommands 708 ================== 709 */ 710 void SV_AddOperatorCommands( void ) { 711 static qboolean initialized; 712 713 if ( initialized ) { 714 return; 715 } 716 initialized = qtrue; 717 718 Cmd_AddCommand ("heartbeat", SV_Heartbeat_f); 719 Cmd_AddCommand ("kick", SV_Kick_f); 720 Cmd_AddCommand ("banUser", SV_Ban_f); 721 Cmd_AddCommand ("banClient", SV_BanNum_f); 722 Cmd_AddCommand ("clientkick", SV_KickNum_f); 723 Cmd_AddCommand ("status", SV_Status_f); 724 Cmd_AddCommand ("serverinfo", SV_Serverinfo_f); 725 Cmd_AddCommand ("systeminfo", SV_Systeminfo_f); 726 Cmd_AddCommand ("dumpuser", SV_DumpUser_f); 727 Cmd_AddCommand ("map_restart", SV_MapRestart_f); 728 Cmd_AddCommand ("sectorlist", SV_SectorList_f); 729 Cmd_AddCommand ("map", SV_Map_f); 730 #ifndef PRE_RELEASE_DEMO 731 Cmd_AddCommand ("devmap", SV_Map_f); 732 Cmd_AddCommand ("spmap", SV_Map_f); 733 Cmd_AddCommand ("spdevmap", SV_Map_f); 734 #endif 735 Cmd_AddCommand ("killserver", SV_KillServer_f); 736 if( com_dedicated->integer ) { 737 Cmd_AddCommand ("say", SV_ConSay_f); 738 } 739 } 740 741 /* 742 ================== 743 SV_RemoveOperatorCommands 744 ================== 745 */ 746 void SV_RemoveOperatorCommands( void ) { 747 #if 0 748 // removing these won't let the server start again 749 Cmd_RemoveCommand ("heartbeat"); 750 Cmd_RemoveCommand ("kick"); 751 Cmd_RemoveCommand ("banUser"); 752 Cmd_RemoveCommand ("banClient"); 753 Cmd_RemoveCommand ("status"); 754 Cmd_RemoveCommand ("serverinfo"); 755 Cmd_RemoveCommand ("systeminfo"); 756 Cmd_RemoveCommand ("dumpuser"); 757 Cmd_RemoveCommand ("map_restart"); 758 Cmd_RemoveCommand ("sectorlist"); 759 Cmd_RemoveCommand ("say"); 760 #endif 761 } 762