sv_init.c (18747B)
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 SV_SetConfigstring 28 29 =============== 30 */ 31 void SV_SetConfigstring (int index, const char *val) { 32 int len, i; 33 int maxChunkSize = MAX_STRING_CHARS - 24; 34 client_t *client; 35 36 if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { 37 Com_Error (ERR_DROP, "SV_SetConfigstring: bad index %i\n", index); 38 } 39 40 if ( !val ) { 41 val = ""; 42 } 43 44 // don't bother broadcasting an update if no change 45 if ( !strcmp( val, sv.configstrings[ index ] ) ) { 46 return; 47 } 48 49 // change the string in sv 50 Z_Free( sv.configstrings[index] ); 51 sv.configstrings[index] = CopyString( val ); 52 53 // send it to all the clients if we aren't 54 // spawning a new server 55 if ( sv.state == SS_GAME || sv.restarting ) { 56 57 // send the data to all relevent clients 58 for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) { 59 if ( client->state < CS_PRIMED ) { 60 continue; 61 } 62 // do not always send server info to all clients 63 if ( index == CS_SERVERINFO && client->gentity && (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) { 64 continue; 65 } 66 67 len = strlen( val ); 68 if( len >= maxChunkSize ) { 69 int sent = 0; 70 int remaining = len; 71 char *cmd; 72 char buf[MAX_STRING_CHARS]; 73 74 while (remaining > 0 ) { 75 if ( sent == 0 ) { 76 cmd = "bcs0"; 77 } 78 else if( remaining < maxChunkSize ) { 79 cmd = "bcs2"; 80 } 81 else { 82 cmd = "bcs1"; 83 } 84 Q_strncpyz( buf, &val[sent], maxChunkSize ); 85 86 SV_SendServerCommand( client, "%s %i \"%s\"\n", cmd, index, buf ); 87 88 sent += (maxChunkSize - 1); 89 remaining -= (maxChunkSize - 1); 90 } 91 } else { 92 // standard cs, just send it 93 SV_SendServerCommand( client, "cs %i \"%s\"\n", index, val ); 94 } 95 } 96 } 97 } 98 99 100 101 /* 102 =============== 103 SV_GetConfigstring 104 105 =============== 106 */ 107 void SV_GetConfigstring( int index, char *buffer, int bufferSize ) { 108 if ( bufferSize < 1 ) { 109 Com_Error( ERR_DROP, "SV_GetConfigstring: bufferSize == %i", bufferSize ); 110 } 111 if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { 112 Com_Error (ERR_DROP, "SV_GetConfigstring: bad index %i\n", index); 113 } 114 if ( !sv.configstrings[index] ) { 115 buffer[0] = 0; 116 return; 117 } 118 119 Q_strncpyz( buffer, sv.configstrings[index], bufferSize ); 120 } 121 122 123 /* 124 =============== 125 SV_SetUserinfo 126 127 =============== 128 */ 129 void SV_SetUserinfo( int index, const char *val ) { 130 if ( index < 0 || index >= sv_maxclients->integer ) { 131 Com_Error (ERR_DROP, "SV_SetUserinfo: bad index %i\n", index); 132 } 133 134 if ( !val ) { 135 val = ""; 136 } 137 138 Q_strncpyz( svs.clients[index].userinfo, val, sizeof( svs.clients[ index ].userinfo ) ); 139 Q_strncpyz( svs.clients[index].name, Info_ValueForKey( val, "name" ), sizeof(svs.clients[index].name) ); 140 } 141 142 143 144 /* 145 =============== 146 SV_GetUserinfo 147 148 =============== 149 */ 150 void SV_GetUserinfo( int index, char *buffer, int bufferSize ) { 151 if ( bufferSize < 1 ) { 152 Com_Error( ERR_DROP, "SV_GetUserinfo: bufferSize == %i", bufferSize ); 153 } 154 if ( index < 0 || index >= sv_maxclients->integer ) { 155 Com_Error (ERR_DROP, "SV_GetUserinfo: bad index %i\n", index); 156 } 157 Q_strncpyz( buffer, svs.clients[ index ].userinfo, bufferSize ); 158 } 159 160 161 /* 162 ================ 163 SV_CreateBaseline 164 165 Entity baselines are used to compress non-delta messages 166 to the clients -- only the fields that differ from the 167 baseline will be transmitted 168 ================ 169 */ 170 void SV_CreateBaseline( void ) { 171 sharedEntity_t *svent; 172 int entnum; 173 174 for ( entnum = 1; entnum < sv.num_entities ; entnum++ ) { 175 svent = SV_GentityNum(entnum); 176 if (!svent->r.linked) { 177 continue; 178 } 179 svent->s.number = entnum; 180 181 // 182 // take current state as baseline 183 // 184 sv.svEntities[entnum].baseline = svent->s; 185 } 186 } 187 188 189 /* 190 =============== 191 SV_BoundMaxClients 192 193 =============== 194 */ 195 void SV_BoundMaxClients( int minimum ) { 196 // get the current maxclients value 197 Cvar_Get( "sv_maxclients", "8", 0 ); 198 199 sv_maxclients->modified = qfalse; 200 201 if ( sv_maxclients->integer < minimum ) { 202 Cvar_Set( "sv_maxclients", va("%i", minimum) ); 203 } else if ( sv_maxclients->integer > MAX_CLIENTS ) { 204 Cvar_Set( "sv_maxclients", va("%i", MAX_CLIENTS) ); 205 } 206 } 207 208 209 /* 210 =============== 211 SV_Startup 212 213 Called when a host starts a map when it wasn't running 214 one before. Successive map or map_restart commands will 215 NOT cause this to be called, unless the game is exited to 216 the menu system first. 217 =============== 218 */ 219 void SV_Startup( void ) { 220 if ( svs.initialized ) { 221 Com_Error( ERR_FATAL, "SV_Startup: svs.initialized" ); 222 } 223 SV_BoundMaxClients( 1 ); 224 225 svs.clients = Z_Malloc (sizeof(client_t) * sv_maxclients->integer ); 226 if ( com_dedicated->integer ) { 227 svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; 228 } else { 229 // we don't need nearly as many when playing locally 230 svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; 231 } 232 svs.initialized = qtrue; 233 234 Cvar_Set( "sv_running", "1" ); 235 } 236 237 238 /* 239 ================== 240 SV_ChangeMaxClients 241 ================== 242 */ 243 void SV_ChangeMaxClients( void ) { 244 int oldMaxClients; 245 int i; 246 client_t *oldClients; 247 int count; 248 249 // get the highest client number in use 250 count = 0; 251 for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { 252 if ( svs.clients[i].state >= CS_CONNECTED ) { 253 if (i > count) 254 count = i; 255 } 256 } 257 count++; 258 259 oldMaxClients = sv_maxclients->integer; 260 // never go below the highest client number in use 261 SV_BoundMaxClients( count ); 262 // if still the same 263 if ( sv_maxclients->integer == oldMaxClients ) { 264 return; 265 } 266 267 oldClients = Hunk_AllocateTempMemory( count * sizeof(client_t) ); 268 // copy the clients to hunk memory 269 for ( i = 0 ; i < count ; i++ ) { 270 if ( svs.clients[i].state >= CS_CONNECTED ) { 271 oldClients[i] = svs.clients[i]; 272 } 273 else { 274 Com_Memset(&oldClients[i], 0, sizeof(client_t)); 275 } 276 } 277 278 // free old clients arrays 279 Z_Free( svs.clients ); 280 281 // allocate new clients 282 svs.clients = Z_Malloc ( sv_maxclients->integer * sizeof(client_t) ); 283 Com_Memset( svs.clients, 0, sv_maxclients->integer * sizeof(client_t) ); 284 285 // copy the clients over 286 for ( i = 0 ; i < count ; i++ ) { 287 if ( oldClients[i].state >= CS_CONNECTED ) { 288 svs.clients[i] = oldClients[i]; 289 } 290 } 291 292 // free the old clients on the hunk 293 Hunk_FreeTempMemory( oldClients ); 294 295 // allocate new snapshot entities 296 if ( com_dedicated->integer ) { 297 svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; 298 } else { 299 // we don't need nearly as many when playing locally 300 svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; 301 } 302 } 303 304 /* 305 ================ 306 SV_ClearServer 307 ================ 308 */ 309 void SV_ClearServer(void) { 310 int i; 311 312 for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { 313 if ( sv.configstrings[i] ) { 314 Z_Free( sv.configstrings[i] ); 315 } 316 } 317 Com_Memset (&sv, 0, sizeof(sv)); 318 } 319 320 /* 321 ================ 322 SV_TouchCGame 323 324 touch the cgame.vm so that a pure client can load it if it's in a seperate pk3 325 ================ 326 */ 327 void SV_TouchCGame(void) { 328 fileHandle_t f; 329 char filename[MAX_QPATH]; 330 331 Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", "cgame" ); 332 FS_FOpenFileRead( filename, &f, qfalse ); 333 if ( f ) { 334 FS_FCloseFile( f ); 335 } 336 } 337 338 /* 339 ================ 340 SV_SpawnServer 341 342 Change the server to a new map, taking all connected 343 clients along with it. 344 This is NOT called for map_restart 345 ================ 346 */ 347 void SV_SpawnServer( char *server, qboolean killBots ) { 348 int i; 349 int checksum; 350 qboolean isBot; 351 char systemInfo[16384]; 352 const char *p; 353 354 // shut down the existing game if it is running 355 SV_ShutdownGameProgs(); 356 357 Com_Printf ("------ Server Initialization ------\n"); 358 Com_Printf ("Server: %s\n",server); 359 360 // if not running a dedicated server CL_MapLoading will connect the client to the server 361 // also print some status stuff 362 CL_MapLoading(); 363 364 // make sure all the client stuff is unloaded 365 CL_ShutdownAll(); 366 367 // clear the whole hunk because we're (re)loading the server 368 Hunk_Clear(); 369 370 // clear collision map data 371 CM_ClearMap(); 372 373 // init client structures and svs.numSnapshotEntities 374 if ( !Cvar_VariableValue("sv_running") ) { 375 SV_Startup(); 376 } else { 377 // check for maxclients change 378 if ( sv_maxclients->modified ) { 379 SV_ChangeMaxClients(); 380 } 381 } 382 383 // clear pak references 384 FS_ClearPakReferences(0); 385 386 // allocate the snapshot entities on the hunk 387 svs.snapshotEntities = Hunk_Alloc( sizeof(entityState_t)*svs.numSnapshotEntities, h_high ); 388 svs.nextSnapshotEntities = 0; 389 390 // toggle the server bit so clients can detect that a 391 // server has changed 392 svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; 393 394 // set nextmap to the same map, but it may be overriden 395 // by the game startup or another console command 396 Cvar_Set( "nextmap", "map_restart 0"); 397 // Cvar_Set( "nextmap", va("map %s", server) ); 398 399 // wipe the entire per-level structure 400 SV_ClearServer(); 401 for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { 402 sv.configstrings[i] = CopyString(""); 403 } 404 405 // make sure we are not paused 406 Cvar_Set("cl_paused", "0"); 407 408 // get a new checksum feed and restart the file system 409 srand(Com_Milliseconds()); 410 sv.checksumFeed = ( ((int) rand() << 16) ^ rand() ) ^ Com_Milliseconds(); 411 FS_Restart( sv.checksumFeed ); 412 413 CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum ); 414 415 // set serverinfo visible name 416 Cvar_Set( "mapname", server ); 417 418 Cvar_Set( "sv_mapChecksum", va("%i",checksum) ); 419 420 // serverid should be different each time 421 sv.serverId = com_frameTime; 422 sv.restartedServerId = sv.serverId; // I suppose the init here is just to be safe 423 sv.checksumFeedServerId = sv.serverId; 424 Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); 425 426 // clear physics interaction links 427 SV_ClearWorld (); 428 429 // media configstring setting should be done during 430 // the loading stage, so connected clients don't have 431 // to load during actual gameplay 432 sv.state = SS_LOADING; 433 434 // load and spawn all other entities 435 SV_InitGameProgs(); 436 437 // don't allow a map_restart if game is modified 438 sv_gametype->modified = qfalse; 439 440 // run a few frames to allow everything to settle 441 for ( i = 0 ;i < 3 ; i++ ) { 442 VM_Call( gvm, GAME_RUN_FRAME, svs.time ); 443 SV_BotFrame( svs.time ); 444 svs.time += 100; 445 } 446 447 // create a baseline for more efficient communications 448 SV_CreateBaseline (); 449 450 for (i=0 ; i<sv_maxclients->integer ; i++) { 451 // send the new gamestate to all connected clients 452 if (svs.clients[i].state >= CS_CONNECTED) { 453 char *denied; 454 455 if ( svs.clients[i].netchan.remoteAddress.type == NA_BOT ) { 456 if ( killBots ) { 457 SV_DropClient( &svs.clients[i], "" ); 458 continue; 459 } 460 isBot = qtrue; 461 } 462 else { 463 isBot = qfalse; 464 } 465 466 // connect the client again 467 denied = VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) ); // firstTime = qfalse 468 if ( denied ) { 469 // this generally shouldn't happen, because the client 470 // was connected before the level change 471 SV_DropClient( &svs.clients[i], denied ); 472 } else { 473 if( !isBot ) { 474 // when we get the next packet from a connected client, 475 // the new gamestate will be sent 476 svs.clients[i].state = CS_CONNECTED; 477 } 478 else { 479 client_t *client; 480 sharedEntity_t *ent; 481 482 client = &svs.clients[i]; 483 client->state = CS_ACTIVE; 484 ent = SV_GentityNum( i ); 485 ent->s.number = i; 486 client->gentity = ent; 487 488 client->deltaMessage = -1; 489 client->nextSnapshotTime = svs.time; // generate a snapshot immediately 490 491 VM_Call( gvm, GAME_CLIENT_BEGIN, i ); 492 } 493 } 494 } 495 } 496 497 // run another frame to allow things to look at all the players 498 VM_Call( gvm, GAME_RUN_FRAME, svs.time ); 499 SV_BotFrame( svs.time ); 500 svs.time += 100; 501 502 if ( sv_pure->integer ) { 503 // the server sends these to the clients so they will only 504 // load pk3s also loaded at the server 505 p = FS_LoadedPakChecksums(); 506 Cvar_Set( "sv_paks", p ); 507 if (strlen(p) == 0) { 508 Com_Printf( "WARNING: sv_pure set but no PK3 files loaded\n" ); 509 } 510 p = FS_LoadedPakNames(); 511 Cvar_Set( "sv_pakNames", p ); 512 513 // if a dedicated pure server we need to touch the cgame because it could be in a 514 // seperate pk3 file and the client will need to load the latest cgame.qvm 515 if ( com_dedicated->integer ) { 516 SV_TouchCGame(); 517 } 518 } 519 else { 520 Cvar_Set( "sv_paks", "" ); 521 Cvar_Set( "sv_pakNames", "" ); 522 } 523 // the server sends these to the clients so they can figure 524 // out which pk3s should be auto-downloaded 525 p = FS_ReferencedPakChecksums(); 526 Cvar_Set( "sv_referencedPaks", p ); 527 p = FS_ReferencedPakNames(); 528 Cvar_Set( "sv_referencedPakNames", p ); 529 530 // save systeminfo and serverinfo strings 531 Q_strncpyz( systemInfo, Cvar_InfoString_Big( CVAR_SYSTEMINFO ), sizeof( systemInfo ) ); 532 cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; 533 SV_SetConfigstring( CS_SYSTEMINFO, systemInfo ); 534 535 SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); 536 cvar_modifiedFlags &= ~CVAR_SERVERINFO; 537 538 // any media configstring setting now should issue a warning 539 // and any configstring changes should be reliably transmitted 540 // to all clients 541 sv.state = SS_GAME; 542 543 // send a heartbeat now so the master will get up to date info 544 SV_Heartbeat_f(); 545 546 Hunk_SetMark(); 547 548 Com_Printf ("-----------------------------------\n"); 549 } 550 551 /* 552 =============== 553 SV_Init 554 555 Only called at main exe startup, not for each game 556 =============== 557 */ 558 void SV_BotInitBotLib(void); 559 560 void SV_Init (void) { 561 SV_AddOperatorCommands (); 562 563 // serverinfo vars 564 Cvar_Get ("dmflags", "0", CVAR_SERVERINFO); 565 Cvar_Get ("fraglimit", "20", CVAR_SERVERINFO); 566 Cvar_Get ("timelimit", "0", CVAR_SERVERINFO); 567 sv_gametype = Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_LATCH ); 568 Cvar_Get ("sv_keywords", "", CVAR_SERVERINFO); 569 Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM); 570 sv_mapname = Cvar_Get ("mapname", "nomap", CVAR_SERVERINFO | CVAR_ROM); 571 sv_privateClients = Cvar_Get ("sv_privateClients", "0", CVAR_SERVERINFO); 572 sv_hostname = Cvar_Get ("sv_hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE ); 573 sv_maxclients = Cvar_Get ("sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH); 574 575 sv_maxRate = Cvar_Get ("sv_maxRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); 576 sv_minPing = Cvar_Get ("sv_minPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); 577 sv_maxPing = Cvar_Get ("sv_maxPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); 578 sv_floodProtect = Cvar_Get ("sv_floodProtect", "1", CVAR_ARCHIVE | CVAR_SERVERINFO ); 579 580 // systeminfo 581 Cvar_Get ("sv_cheats", "1", CVAR_SYSTEMINFO | CVAR_ROM ); 582 sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM ); 583 #ifndef DLL_ONLY // bk010216 - for DLL-only servers 584 sv_pure = Cvar_Get ("sv_pure", "1", CVAR_SYSTEMINFO ); 585 #else 586 sv_pure = Cvar_Get ("sv_pure", "0", CVAR_SYSTEMINFO | CVAR_INIT | CVAR_ROM ); 587 #endif 588 Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM ); 589 Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); 590 Cvar_Get ("sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM ); 591 Cvar_Get ("sv_referencedPakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); 592 593 // server vars 594 sv_rconPassword = Cvar_Get ("rconPassword", "", CVAR_TEMP ); 595 sv_privatePassword = Cvar_Get ("sv_privatePassword", "", CVAR_TEMP ); 596 sv_fps = Cvar_Get ("sv_fps", "20", CVAR_TEMP ); 597 sv_timeout = Cvar_Get ("sv_timeout", "200", CVAR_TEMP ); 598 sv_zombietime = Cvar_Get ("sv_zombietime", "2", CVAR_TEMP ); 599 Cvar_Get ("nextmap", "", CVAR_TEMP ); 600 601 sv_allowDownload = Cvar_Get ("sv_allowDownload", "0", CVAR_SERVERINFO); 602 sv_master[0] = Cvar_Get ("sv_master1", MASTER_SERVER_NAME, 0 ); 603 sv_master[1] = Cvar_Get ("sv_master2", "", CVAR_ARCHIVE ); 604 sv_master[2] = Cvar_Get ("sv_master3", "", CVAR_ARCHIVE ); 605 sv_master[3] = Cvar_Get ("sv_master4", "", CVAR_ARCHIVE ); 606 sv_master[4] = Cvar_Get ("sv_master5", "", CVAR_ARCHIVE ); 607 sv_reconnectlimit = Cvar_Get ("sv_reconnectlimit", "3", 0); 608 sv_showloss = Cvar_Get ("sv_showloss", "0", 0); 609 sv_padPackets = Cvar_Get ("sv_padPackets", "0", 0); 610 sv_killserver = Cvar_Get ("sv_killserver", "0", 0); 611 sv_mapChecksum = Cvar_Get ("sv_mapChecksum", "", CVAR_ROM); 612 sv_lanForceRate = Cvar_Get ("sv_lanForceRate", "1", CVAR_ARCHIVE ); 613 sv_strictAuth = Cvar_Get ("sv_strictAuth", "1", CVAR_ARCHIVE ); 614 615 // initialize bot cvars so they are listed and can be set before loading the botlib 616 SV_BotInitCvars(); 617 618 // init the botlib here because we need the pre-compiler in the UI 619 SV_BotInitBotLib(); 620 } 621 622 623 /* 624 ================== 625 SV_FinalMessage 626 627 Used by SV_Shutdown to send a final message to all 628 connected clients before the server goes down. The messages are sent immediately, 629 not just stuck on the outgoing message list, because the server is going 630 to totally exit after returning from this function. 631 ================== 632 */ 633 void SV_FinalMessage( char *message ) { 634 int i, j; 635 client_t *cl; 636 637 // send it twice, ignoring rate 638 for ( j = 0 ; j < 2 ; j++ ) { 639 for (i=0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) { 640 if (cl->state >= CS_CONNECTED) { 641 // don't send a disconnect to a local client 642 if ( cl->netchan.remoteAddress.type != NA_LOOPBACK ) { 643 SV_SendServerCommand( cl, "print \"%s\"", message ); 644 SV_SendServerCommand( cl, "disconnect" ); 645 } 646 // force a snapshot to be sent 647 cl->nextSnapshotTime = -1; 648 SV_SendClientSnapshot( cl ); 649 } 650 } 651 } 652 } 653 654 655 /* 656 ================ 657 SV_Shutdown 658 659 Called when each game quits, 660 before Sys_Quit or Sys_Error 661 ================ 662 */ 663 void SV_Shutdown( char *finalmsg ) { 664 if ( !com_sv_running || !com_sv_running->integer ) { 665 return; 666 } 667 668 Com_Printf( "----- Server Shutdown -----\n" ); 669 670 if ( svs.clients && !com_errorEntered ) { 671 SV_FinalMessage( finalmsg ); 672 } 673 674 SV_RemoveOperatorCommands(); 675 SV_MasterShutdown(); 676 SV_ShutdownGameProgs(); 677 678 // free current level 679 SV_ClearServer(); 680 681 // free server static data 682 if ( svs.clients ) { 683 Z_Free( svs.clients ); 684 } 685 Com_Memset( &svs, 0, sizeof( svs ) ); 686 687 Cvar_Set( "sv_running", "0" ); 688 Cvar_Set("ui_singlePlayerActive", "0"); 689 690 Com_Printf( "---------------------------\n" ); 691 692 // disconnect any local clients 693 CL_Disconnect( qfalse ); 694 } 695