Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

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