/*
Copyright (C) 1997-2001 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "server.h"
#include "mvd.h"

/*
===============================================================================

OPERATOR CONSOLE ONLY COMMANDS

These commands can only be entered from stdin or by a remote operator datagram
===============================================================================
*/

/*
====================
SV_SetMaster_f

Specify a list of master servers
====================
*/
void SV_SetMaster_f (void)
{
	int		i, slot;

	// only dedicated servers send heartbeats
	if (!dedicated->integer)
	{
		Com_Printf ("Only dedicated servers use masters.\n");
		return;
	}

	// make sure the server is listed public
	Cvar_Set ("public", "1");

	for (i=1 ; i<MAX_MASTERS ; i++)
		memset (&master_adr[i], 0, sizeof(master_adr[i]));

	slot = 1;		// slot 0 will always contain the id master
	for (i=1 ; i<Cmd_Argc() ; i++)
	{
		if (slot == MAX_MASTERS)
			break;

		if (!NET_StringToAdr (Cmd_Argv(i), &master_adr[i]))
		{
			Com_Printf ("Bad address: %s\n", Cmd_Argv(i));
			continue;
		}
		if (master_adr[slot].port == 0)
			master_adr[slot].port = BigShort (PORT_MASTER);

		Com_Printf ("Master server at %s\n", NET_AdrToString (&master_adr[slot]));

		Com_Printf ("Sending a ping.\n");

		Netchan_OutOfBandPrint (NS_SERVER, &master_adr[slot], "ping");

		slot++;
	}

	svs.last_heartbeat = -9999999;
}

static const char *SV_SetPlayer_g( const char *partial, int state ) {
    static int length;
    static int index;
    client_t *client;

	if( !svs.initialized ) {
		return NULL;
	}
    
    if( !state ) {
        length = strlen( partial );
        index = 0;
    }

	while( index < sv_maxclients->integer ) {
		client = &svs.clientpool[index++];
		if( !client->state ) {
			continue;
		}
		if( !strncmp( partial, client->name, length ) ) {
			return client->name;
		}
	}

    return NULL;
}


/*
==================
SV_SetPlayer

Sets sv_client and sv_player to the player with idnum Cmd_Argv(1)
==================
*/
qboolean SV_SetPlayer (void)
{
	client_t	*cl;
	int			i;
	int			idnum;
	char		*s;

	if (Cmd_Argc() < 2)
		return qfalse;

	s = Cmd_Argv(1);

	// numeric values are just slot numbers
	for( i = 0; s[i]; i++ ) {
		if( !Q_isdigit( s[i] ) ) {
			break;
		}
	} 
	if( !s[i] ) {
		idnum = atoi(s);
		if (idnum < 0 || idnum >= sv_maxclients->integer)
		{
			Com_Printf ("Bad client slot: %i\n", idnum);
			return qfalse;
		}

		sv_client = &svs.clientpool[idnum];
		sv_player = sv_client->edict;
		if (!sv_client->state)
		{
			Com_Printf ("Client %i is not active\n", idnum);
			return qfalse;
		}
		return qtrue;
	}

	// check for a name match
    FOR_EACH_CLIENT( cl ) {
		if (!strcmp(cl->name, s))
		{
			sv_client = cl;
			sv_player = sv_client->edict;
			return qtrue;
		}
	}

	Com_Printf ("Userid %s is not on the server\n", s);
	return qfalse;
}


/*
===============================================================================

SAVEGAME FILES

===============================================================================
*/

#define GAME_SEARCH_FLAGS	( FS_SEARCH_BYFILTER | FS_SEARCH_SAVEPATH | FS_PATH_GAME | FS_TYPE_REAL )

/*
=====================
SV_WipeSavegame

Delete save/<XXX>/
=====================
*/
void SV_WipeSavegame( const char *savename ) {
	char	name[MAX_QPATH];
	char	**list, **s;

	Com_DPrintf( "SV_WipeSaveGame( %s )\n", savename );

	Com_sprintf( name, sizeof( name ), "save/%s/server.ssv", savename );
	FS_RemoveFile( name );
	
	Com_sprintf( name, sizeof( name ), "save/%s/game.ssv", savename );
	FS_RemoveFile( name );

	Com_sprintf( name, sizeof( name ), "save/%s/*.sav", savename );
	if( ( list = FS_ListFiles( NULL, name, GAME_SEARCH_FLAGS, NULL ) ) != NULL ) {
		s = list;
		while( *s ) {
			FS_RemoveFile( *s );
			s++;
		}
		FS_FreeFileList( list );
	}

	Com_sprintf( name, sizeof( name ), "save/%s/*.sv2", savename );
	if( ( list = FS_ListFiles( NULL, name, GAME_SEARCH_FLAGS, NULL ) ) != NULL ) {
		s = list;
		while( *s ) {
			FS_RemoveFile( *s );
			s++;
		}
		FS_FreeFileList( list );
	}

}

/*
================
SV_CopySaveGame
================
*/
void SV_CopySaveGame( const char *src, const char *dst ) {
	char	name[MAX_QPATH], name2[MAX_QPATH];
	int		len;
	char	**list, **s;

	Com_DPrintf( "SV_CopySaveGame(%s, %s)\n", src, dst );

	SV_WipeSavegame( dst );

	// copy the savegame over
	Com_sprintf( name, sizeof( name ), "save/%s/server.ssv", src );
	Com_sprintf( name2, sizeof( name2 ), "save/%s/server.ssv", dst );
	FS_CopyFile( name, name2 );

	Com_sprintf( name, sizeof( name ), "save/%s/game.ssv", src );
	Com_sprintf( name2, sizeof( name2 ), "save/%s/game.ssv", dst );
	FS_CopyFile( name, name2 );

	Com_sprintf( name, sizeof( name ), "save/%s/", src );
	len = strlen( name );

	Com_sprintf( name, sizeof( name ), "save/%s/*.sav", src );
	if( ( list = FS_ListFiles( NULL, name, GAME_SEARCH_FLAGS, NULL ) ) != NULL ) {
		s = list;
		while( *s ) {
			Com_sprintf( name2, sizeof( name2 ), "save/%s/%s", dst, *s + len );
			FS_CopyFile( *s, name2 );
			s++;
		}
		FS_FreeFileList( list );
	}

	Com_sprintf( name, sizeof( name ), "save/%s/*.sv2", src );
	if( ( list = FS_ListFiles( NULL, name, GAME_SEARCH_FLAGS, NULL ) ) != NULL ) {
		s = list;
		while( *s ) {
			Com_sprintf( name2, sizeof( name2 ), "save/%s/%s", dst, *s + len );
			FS_CopyFile( *s, name2 );
			s++;
		}
		FS_FreeFileList( list );
	}
}


/*
==============
SV_WriteLevelFile

==============
*/
void SV_WriteLevelFile (void)
{
	char	name[MAX_OSPATH];
	fileHandle_t f;

	Com_DPrintf("SV_WriteLevelFile()\n");

	Com_sprintf (name, sizeof(name), "save/current/%s.sv2", sv.name);
	FS_FOpenFile( name, &f, FS_MODE_WRITE );
	if (!f)
	{
		Com_Printf ("Failed to open %s\n", name);
		return;
	}

	FS_Write (sv.configstrings, sizeof(sv.configstrings), f);
	CM_WritePortalState (&sv.cm, f);
	FS_FCloseFile (f);

	Com_sprintf (name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name);
	ge->WriteLevel (name);
}

/*
==============
SV_ReadLevelFile

==============
*/
void SV_ReadLevelFile (void)
{
	char	name[MAX_OSPATH];
	fileHandle_t f;

	Com_DPrintf("SV_ReadLevelFile()\n");

	Com_sprintf (name, sizeof(name), "save/current/%s.sv2", sv.name);
	
	FS_FOpenFile( name, &f, FS_MODE_READ|FS_TYPE_REAL );
	if( !f ) {
		Com_Printf ("Failed to open %s\n", name);
		return;
	}
	FS_Read( sv.configstrings, sizeof( sv.configstrings ), f );
	*( ( byte * )sv.configstrings + sizeof( sv.configstrings ) - 1 ) = 0;
	
	CM_ReadPortalState (&sv.cm, f);
	FS_FCloseFile (f);

	Com_sprintf (name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name);
	ge->ReadLevel (name);
}

/*
==============
SV_WriteServerFile

==============
*/
void SV_WriteServerFile (qboolean autosave)
{
	fileHandle_t f;
	cvar_t	*var;
	char	name[MAX_OSPATH], string[128];
	char	comment[MAX_QPATH]; // increased from 32 to avoid Com_sprintf warnings
	time_t	aclock;
	struct tm	*newtime;

	Com_DPrintf( "SV_WriteServerFile( %i )\n", autosave );

	FS_FOpenFile( "save/current/server.ssv", &f, FS_MODE_WRITE );
	if( !f ) {
		Com_WPrintf( "Couldn't write save/current/server.ssv\n" );
		return;
	}
	// write the comment field
	memset (comment, 0, sizeof(comment));

	if (!autosave)
	{
#ifdef _WIN32_WCE
		struct tm	foo;

		memset( &foo, 0, sizeof( foo ) );
		newtime = &foo;
#else
		time (&aclock);
		newtime = localtime (&aclock);
#endif
		Com_sprintf (comment,sizeof(comment), "%2i:%i%i %2i/%2i  ", newtime->tm_hour
			, newtime->tm_min/10, newtime->tm_min%10,
			newtime->tm_mon+1, newtime->tm_mday);
		strncat (comment, sv.configstrings[CS_NAME], sizeof(comment)-1-strlen(comment) );
	}
	else
	{	// autosaved
		Com_sprintf( comment, sizeof( comment ), "ENTERING %s", sv.configstrings[CS_NAME] );
	}

	// write 32 bytes instead of 64 for compatibility
	FS_Write (comment, 32, f);

	// write the mapcmd
	FS_Write (svs.mapcmd, sizeof(svs.mapcmd), f);

	// write all CVAR_LATCH cvars
	// these will be things like coop, skill, deathmatch, etc
	for (var = cvar_vars ; var ; var=var->next)
	{
		if (!(var->flags & CVAR_LATCH))
			continue;
		if (strlen(var->name) >= sizeof(name)-1
			|| strlen(var->string) >= sizeof(string)-1)
		{
			Com_Printf ("Cvar too long: %s = %s\n", var->name, var->string);
			continue;
		}
		memset (name, 0, sizeof(name));
		memset (string, 0, sizeof(string));
		strcpy (name, var->name);
		strcpy (string, var->string);
		FS_Write (name, sizeof(name), f);
		FS_Write (string, sizeof(string), f);
	}

	FS_FCloseFile (f);

	// write game state
	Com_sprintf (name, sizeof(name), "%s/save/current/game.ssv", FS_Gamedir());
	ge->WriteGame (name, autosave);
}

/*
==============
SV_ReadServerFile

==============
*/
void SV_ReadServerFile (void)
{
	fileHandle_t f;
	char	name[MAX_OSPATH], string[128];
	char	comment[32];
	char	mapcmd[MAX_TOKEN_CHARS];

	Com_DPrintf("SV_ReadServerFile()\n");

	Com_sprintf (name, sizeof(name), "save/current/server.ssv");
	FS_FOpenFile( name, &f, FS_MODE_READ|FS_TYPE_REAL|FS_PATH_GAME );
	if (!f)
	{
		Com_Printf ("Couldn't read %s\n", name);
		return;
	}
	// read the comment field
	FS_Read (comment, sizeof(comment), f);

	// read the mapcmd
	FS_Read (mapcmd, sizeof(mapcmd), f);

	// read all CVAR_LATCH cvars
	// these will be things like coop, skill, deathmatch, etc
	while (1)
	{
		if (!FS_Read (name, sizeof(name), f))
			break;
		FS_Read (string, sizeof(string), f);
		Com_DPrintf ("Set %s = %s\n", name, string);
		Cvar_Set (name, string);
	}

	FS_FCloseFile (f);

	// start a new game fresh with new cvars
	SV_InitGame ( 0 );

	strcpy (svs.mapcmd, mapcmd);

	// read game state
	Com_sprintf (name, sizeof(name), "%s/save/current/game.ssv", FS_Gamedir());
	ge->ReadGame (name);

}


//=========================================================


/*
==================
SV_DemoMap_f

Puts the server in demo mode on a specific map/cinematic
==================
*/
void SV_DemoMap_f( void ) {
	if( Cmd_Argc() != 2 ) {
		Com_Printf( "Usage: %s <mapname>\n", Cmd_Argv( 0 ) );
		return;
	}
	SV_Map( ATR_DEMO, Cmd_Argv( 1 ), qfalse );
}

/*
==================
SV_GameMap_f

Saves the state of the map just being exited and goes to a new map.

If the initial character of the map string is '*', the next map is
in a new unit, so the current savegame directory is cleared of
map files.

Example:

*inter.cin+jail

Clears the archived maps, plays the inter.cin cinematic, then
goes to map jail.bsp.
==================
*/
void SV_GameMap_f (void)
{
	char		*map;
	int			i;
	client_t	*cl;
	qboolean	*savedInuse;
	char		expanded[MAX_QPATH];

	if (Cmd_Argc() != 2)
	{
		Com_Printf( "Usage: %s <mapname>\n", Cmd_Argv( 0 ) );
		return;
	}

	Com_DPrintf("SV_GameMap(%s)\n", Cmd_Argv(1));

	FS_CreatePath (va("%s/save/current/", FS_Gamedir()));

	// check for clearing the current savegame
	map = Cmd_Argv(1);
	if (map[0] == '*')
	{
		// wipe all the *.sav files
		SV_WipeSavegame ("current");
	}
	else
	{
		// if not a pcx, demo, or cinematic, check to make sure the level exists
		if( !strchr( map, '.' ) && !strchr( map, '$' ) ) {
			Com_sprintf( expanded, sizeof( expanded ), "maps/%s.bsp", map );
			if( FS_LoadFile( expanded, NULL ) == -1 ) {
				Com_Printf( "Can't find %s\n", expanded );
				return;
			}
		}

		// save the map just exited
		if (sv.state == ss_game)
		{
			// clear all the client inuse flags before saving so that
			// when the level is re-entered, the clients will spawn
			// at spawn points instead of occupying body shells
			savedInuse = SV_Malloc(sv_maxclients->integer * sizeof(qboolean));
			for (i=0,cl=svs.clientpool ; i<sv_maxclients->integer; i++,cl++)
			{
				savedInuse[i] = cl->edict->inuse;
				cl->edict->inuse = qfalse;
			}

			SV_WriteLevelFile ();

			// we must restore these for clients to transfer over correctly
			for (i=0,cl=svs.clientpool ; i<sv_maxclients->integer; i++,cl++)
				cl->edict->inuse = savedInuse[i];
			Z_Free (savedInuse);
		}
	}

	// start up the next map
	SV_Map (ATR_NONE, Cmd_Argv(1), qfalse );

	// archive server state
	Q_strncpyz( svs.mapcmd, Cmd_Argv( 1 ), sizeof( svs.mapcmd ) );

	// copy off the level to the autosave slot
	if (!dedicated->integer)
	{
		SV_WriteServerFile (qtrue);
		SV_CopySaveGame ("current", "save0");
	}
}

/*
==================
SV_Map_f

Goes directly to a given map without any savegame archiving.
For development work
==================
*/
void SV_Map_f( void ) {
	char	*map;
	char	expanded[MAX_QPATH];

	if( Cmd_Argc() != 2 ) {
		Com_Printf( "Usage: %s <mapname>\n", Cmd_Argv( 0 ) );
		return;
	}

	// if not a pcx, demo, or cinematic, check to make sure the level exists
	map = Cmd_Argv( 1 );
	if( !strchr( map, '.' ) ) {
		Com_sprintf( expanded, sizeof( expanded ), "maps/%s.bsp", map );
		if( FS_LoadFile( expanded, NULL ) == -1 ) {
			Com_Printf( "Can't find %s\n", expanded );
			return;
		}
	}

	sv.state = ss_dead;		// don't save current level when changing
	SV_WipeSavegame( "current" );
	SV_GameMap_f();
}

static const char *SV_Map_g( const char *partial, int state ) {
	return Com_FileNameGenerator( "maps", ".bsp", partial, qtrue, state );
}

/*
=====================================================================

  SAVEGAMES

=====================================================================
*/


/*
==============
SV_Loadgame_f

==============
*/
void SV_Loadgame_f (void)
{
	char	name[MAX_QPATH];
	char	*dir;

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("USAGE: loadgame <directory>\n");
		return;
	}

	Com_Printf ("Loading game...\n");

	dir = Cmd_Argv(1);
	if (strstr (dir, "..") || strstr (dir, "/") || strstr (dir, "\\") )
	{
		Com_Printf ("Bad savedir.\n");
	}

	// make sure the server.ssv file exists
	Com_sprintf (name, sizeof(name), "save/%s/server.ssv", Cmd_Argv(1));
	if( FS_LoadFileEx( name, NULL, FS_TYPE_REAL|FS_PATH_GAME ) == -1 ) {
		Com_Printf ("No such savegame: %s\n", name);
		return;
	}

	SV_CopySaveGame (Cmd_Argv(1), "current");

	SV_ReadServerFile ();

	// go to the map
	sv.state = ss_dead;		// don't save current level when changing
	SV_Map (ATR_NONE, svs.mapcmd, qtrue);
}



/*
==============
SV_Savegame_f

==============
*/
void SV_Savegame_f (void)
{
	char	*dir;

	if (sv.state != ss_game)
	{
		Com_Printf ("You must be in a game to save.\n");
		return;
	}

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("USAGE: savegame <directory>\n");
		return;
	}

	if (Cvar_VariableValue("deathmatch"))
	{
		Com_Printf ("Can't savegame in a deathmatch\n");
		return;
	}

	if (!strcmp (Cmd_Argv(1), "current"))
	{
		Com_Printf ("Can't save to 'current'\n");
		return;
	}

	if (sv_maxclients->integer == 1 && svs.clientpool[0].edict && svs.clientpool[0].edict->client && svs.clientpool[0].edict->client->ps.stats[STAT_HEALTH] <= 0)
	{
		Com_Printf ("\nCan't savegame while dead!\n");
		return;
	}

	dir = Cmd_Argv(1);
	if (strstr (dir, "..") || strstr (dir, "/") || strstr (dir, "\\") )
	{
		Com_Printf ("Bad savedir.\n");
	}

	Com_Printf ("Saving game...\n");

	// archive current level, including all client edicts.
	// when the level is reloaded, they will be shells awaiting
	// a connecting client
	SV_WriteLevelFile ();

	// save server state
	SV_WriteServerFile (qfalse);

	// copy it off
	SV_CopySaveGame ("current", dir);

	Com_Printf ("Done.\n");
}

//===============================================================

/*
==================
SV_Kick_f

Kick a user off of the server
==================
*/
void SV_Kick_f( void ) {
	if( !svs.initialized ) {
		Com_Printf( "No server running.\n" );
		return;
	}

	if( Cmd_Argc() != 2 ) {
		Com_Printf( "Usage: %s <userid>\n", Cmd_Argv( 0 ) );
		return;
	}

	if( !SV_SetPlayer() )
		return;

	SV_DropClient( sv_client, "was kicked" );
	sv_client->lastmessage = svs.realtime;	// min case there is a funny zombie

	sv_client = NULL;
	sv_player = NULL;
}


/*
================
SV_Status_f
================
*/
static void SV_Status_f( void ) {
	int			size;
	client_t	*client;

	if( !svs.initialized ) {
		Com_Printf( "No server running.\n" );
		return;
	}

	Com_Printf( "map              : %s\n", sv.name );
	if( sv.state == ss_broadcast ) {
		if( mvd.demoplayback ) {
			size = mvd.demofileSize + mvd.demofileFrameOffset;
			Com_Printf( "demo             : %s\n", mvd.demopath );
			Com_Printf( "size             : %.1f KB\n", size * 0.001f );
			Com_Printf( "position         : %d%%", mvd.demofilePercent );
			if( mvd_pause->integer ) {
				Com_Printf( " (paused)" );
			}
			Com_Printf( "\n" );
		} else if( mvd.netchan ) {
			Com_Printf( "remote           : %s\n", NET_AdrToString( &mvd.netchan->remote_address ) );
		}
	}
	Com_Printf( "\n" );

	if( Cmd_Argc() > 1 ) {
		Com_Printf( "num name             version\n" );
		Com_Printf( "--- ---------------- ----------------------------------------\n" );
		
    FOR_EACH_CLIENT( client ) {
			if( client->state < cs_connected )
				continue;
			Com_Printf( "%3i ", client->clientNum );
			Com_Printf( "%-16.16s ", client->name );
			if( client->versionString ) {
				Com_Printf( "%s\n", client->versionString );
			} else {
				Com_Printf( "<unknown>\n" );
			}
		}
		Com_Printf( "\n" );
		return;
	}

	Com_Printf( "num score ping name             lastmsg address                rate proto\n" );
	Com_Printf( "--- ----- ---- ---------------- ------- --------------------- ----- -----\n" );
    FOR_EACH_CLIENT( client ) {
		Com_Printf( "%3i ", client->clientNum );
		if( sv.state == ss_broadcast || client->protocol == PROTOCOL_VERSION_MVD ) {
			Com_Printf( "      " );
		} else {
			Com_Printf( "%5i ", client->edict->client->ps.stats[STAT_FRAGS] );
		}

		switch( client->state ) {
		case cs_zombie:
			Com_Printf( "ZMBI " );
			break;
		case cs_connected:
			Com_Printf( "CNCT " );
			break;
		case cs_primed:
			Com_Printf( "PRIM " );
			break;
		case cs_ready:
			Com_Printf( "SPWN " );
			break;
		default:
			Com_Printf( "%4i ", client->ping < 9999 ? client->ping : 9999 );
			break;
		}

		Com_Printf( "%-16.16s ", client->name );
		Com_Printf( "%7u ", svs.realtime - client->lastmessage );
		Com_Printf( "%-21s ", NET_AdrToString( &client->netchan->remote_address ) );
        if( NET_IsLocalAddress( &client->netchan->remote_address ) ||
            ( sv_lan_force_rate->integer &&
            NET_IsLANAddress( &client->netchan->remote_address ) ) )
        {
    		Com_Printf( "  inf " );
        } else {
    		Com_Printf( "%5i ", client->rate );
        }
		Com_Printf( "%2i/%d ", client->protocol, client->netchan->type );
		Com_Printf( "\n" );
	}

	Com_Printf( "\n" );
}

/*
==================
SV_ConSay_f
==================
*/
void SV_ConSay_f( void ) {
	client_t *client;
	char *s;

	if( !svs.initialized ) {
		Com_Printf( "No server running\n" );
		return;
	}

	if( Cmd_Argc() < 2 ) {
		Com_Printf( "Usage: %s <text>\n", Cmd_Argv( 0 ) );
		return;
	}

	s = Cmd_RawArgs();
    FOR_EACH_CLIENT( client ) {
		if( client->state != cs_spawned )
			continue;
		SV_ClientPrintf( client, PRINT_CHAT, "console: %s\n", s );
	}

	Com_Printf( "console: %s\n", s );
}


/*
==================
SV_Heartbeat_f
==================
*/
static void SV_Heartbeat_f( void ) {
	svs.last_heartbeat = -9999999;
}


/*
===========
SV_Serverinfo_f

  Examine or change the serverinfo string
===========
*/
static void SV_Serverinfo_f( void ) {
	Com_Printf( "Server info settings:\n" );
	Info_Print( Cvar_Serverinfo() );
}


/*
===========
SV_DumpUser_f

Examine all a users info strings
===========
*/
static void SV_DumpUser_f( void ) {
	if( !svs.initialized ) {
		Com_Printf( "No server running\n" );
		return;
	}

	if( Cmd_Argc() != 2 ) {
		Com_Printf( "Usage: %s <userid>\n", Cmd_Argv( 0 ) );
		return;
	}

	if( !SV_SetPlayer() )
		return;

	Com_Printf( "userinfo\n" );
	Com_Printf( "--------\n" );
	Info_Print( sv_client->userinfo );
	if( sv_client->versionString ) {
		Com_Printf( "version              %s\n", sv_client->versionString );
	}

	sv_client = NULL;
	sv_player = NULL;

}

/*
==================
SV_Stuff_f

Stuff raw command string to the client.
==================
*/
void SV_Stuff_f( void ) {
	if( !svs.initialized ) {
		Com_Printf( "No server running.\n" );
		return;
	}

	if( Cmd_Argc() < 3 ) {
		Com_Printf( "Usage: %s <userid> <text>\n", Cmd_Argv( 0 ) );
		return;
	}

	if( !SV_SetPlayer() )
		return;

	MSG_WriteByte( svc_stufftext );
	MSG_WriteString( Cmd_RawArgsFrom( 2 ) );
	SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );

	sv_client = NULL;
	sv_player = NULL;
}

/*
==================
SV_Stuff_f

Stuff raw command string to the client.
==================
*/
void SV_Stuffall_f( void ) {
	client_t *client;

	if( !svs.initialized ) {
		Com_Printf( "No server running.\n" );
		return;
	}

	if( Cmd_Argc() < 2 ) {
		Com_Printf( "Usage: %s <text>\n", Cmd_Argv( 0 ) );
		return;
	}

	MSG_WriteByte( svc_stufftext );
	MSG_WriteString( Cmd_RawArgsFrom( 1 ) );

    FOR_EACH_CLIENT( client ) {
		if( client->state < cs_connected )
			continue;
		SV_ClientAddMessage( client, MSG_RELIABLE );
	}

	SZ_Clear( &msg_write );

}


/*
===============
SV_KillServer_f

Kick everyone off, possibly in preparation for a new game

===============
*/
void SV_KillServer_f( void ) {
	if( !svs.initialized ) {
		Com_Printf( "No server running\n" );
		return;
	}

	SV_Shutdown( "Server was killed.\n", KILL_DROP );
}

/*
===============
SV_ServerCommand_f

Let the game dll handle a command
===============
*/
void SV_ServerCommand_f( void ) {
	if( !ge ) {
		Com_Printf( "No game loaded.\n" );
		return;
	}

	ge->ServerCommand();
}

static void SV_Client_m( char *buffer, int bufferSize ) {
	if( !sv_client ) {
		Q_strncpyz( buffer, "unknown", bufferSize );
		return;
	}

	Q_strncpyz( buffer, sv_client->name, bufferSize );
}

//===========================================================

/*
==================
SV_InitOperatorCommands
==================
*/
void SV_InitOperatorCommands (void)
{
	Cmd_AddCommand ("heartbeat", SV_Heartbeat_f);
	Cmd_AddCommandEx ("kick", SV_Kick_f, SV_SetPlayer_g);
	Cmd_AddCommand ("status", SV_Status_f);
	Cmd_AddCommand ("serverinfo", SV_Serverinfo_f);
	Cmd_AddCommandEx ("dumpuser", SV_DumpUser_f, SV_SetPlayer_g);
	Cmd_AddCommandEx ("stuff", SV_Stuff_f, SV_SetPlayer_g);
	Cmd_AddCommand ("stuffall", SV_Stuffall_f);

	Cmd_AddCommandEx ("map", SV_Map_f, SV_Map_g);
	Cmd_AddCommand ("demomap", SV_DemoMap_f);
	Cmd_AddCommandEx ("gamemap", SV_GameMap_f, SV_Map_g);
	Cmd_AddCommand ("setmaster", SV_SetMaster_f);

	if ( dedicated->integer )
		Cmd_AddCommand ("say", SV_ConSay_f);

	Cmd_AddCommand ("save", SV_Savegame_f);
	Cmd_AddCommand ("load", SV_Loadgame_f);

	Cmd_AddCommand ("killserver", SV_KillServer_f);

	Cmd_AddCommand ("sv", SV_ServerCommand_f);

	Cmd_AddMacro ("sv_client", SV_Client_m);

}
 

