Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

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