//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//
 
#include "tier0/vprof.h"
#include "server.h"
#include "host_cmd.h"
#include "keys.h"
#include "screen.h"
#include "vengineserver_impl.h"
#include "host_saverestore.h"
#include "sv_filter.h"
#include "gl_matsysiface.h"
#include "pr_edict.h"
#include "world.h"
#include "checksum_engine.h"
#include "const.h"
#include "sv_main.h"
#include "host.h"
#include "demo.h"
#include "cdll_int.h"
#include "networkstringtableserver.h"
#include "networkstringtableclient.h"
#include "host_state.h"
#include "string_t.h"
#include "tier0/dbg.h"
#include "testscriptmgr.h"
#include "r_local.h"
#include "PlayerState.h"
#include "enginesingleuserfilter.h"
#include "profile.h"
#include "proto_version.h"
#include "protocol.h"
#include "cl_main.h"
#include "sv_steamauth.h"
#include "zone.h"
#include "datacache/idatacache.h"
#include "sys_dll.h"
#include "cmd.h"
#include "tier0/icommandline.h"
#include "filesystem.h"
#include "filesystem_engine.h"
#include "icliententitylist.h"
#include "icliententity.h"
#include "GameEventManager.h"
#include "hltvserver.h"
#if defined( REPLAY_ENABLED )
#include "replay_internal.h"
#include "replayserver.h"
#endif
#include "cdll_engine_int.h"
#include "cl_steamauth.h"
#ifndef SWDS
#include "vgui_baseui_interface.h"
#endif
#include "sound.h"
#include "voice.h"
#include "sv_rcon.h"
#if defined( _X360 )
#include "xbox/xbox_console.h"
#include "xbox/xbox_launch.h"
#endif
#include "filesystem/IQueuedLoader.h"
#include "sys.h"

#include "ixboxsystem.h"
extern IXboxSystem *g_pXboxSystem;

#include <sys/stat.h>
#include <stdio.h>
#ifdef POSIX
// sigh, microsoft put _ in front of its type defines for stat
#define _stat stat
#endif


// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define STEAM_PREFIX "STEAM_"

#define STATUS_COLUMN_LENGTH_LINEPREFIX	1
#define STATUS_COLUMN_LENGTH_USERID		6
#define STATUS_COLUMN_LENGTH_USERID_STR	"6"
#define STATUS_COLUMN_LENGTH_NAME		19
#define STATUS_COLUMN_LENGTH_STEAMID	19
#define STATUS_COLUMN_LENGTH_TIME		9
#define STATUS_COLUMN_LENGTH_PING		4
#define STATUS_COLUMN_LENGTH_PING_STR	"4"
#define STATUS_COLUMN_LENGTH_LOSS		4
#define STATUS_COLUMN_LENGTH_LOSS_STR	"4"
#define STATUS_COLUMN_LENGTH_STATE		6
#define STATUS_COLUMN_LENGTH_ADDR		21

#define KICKED_BY_CONSOLE "Kicked from server"

#ifndef SWDS
bool g_bInEditMode = false;
bool g_bInCommentaryMode = false;
#endif

static void host_name_changed_f( IConVar *var, const char *pOldValue, float flOldValue )
{
	Steam3Server().NotifyOfServerNameChange();
}

ConVar host_name( "hostname", "", 0, "Hostname for server.", host_name_changed_f );
ConVar host_map( "host_map", "", 0, "Current map name." );

void Host_VoiceRecordStop_f(void);
static void voiceconvar_file_changed_f( IConVar *pConVar, const char *pOldValue, float flOldValue )
{
#ifndef SWDS
	ConVarRef var( pConVar );
	if ( var.GetInt() == 0 )
	{
		// Force voice recording to stop if they turn off voice_inputfromfile or if sv_allow_voice_from_file is set to 0. 
		// Prevents an exploit where clients turn it on, start voice sending a long file, and then turn it off immediately.
		Host_VoiceRecordStop_f();
	}
#endif
}

ConVar voice_recordtofile("voice_recordtofile", "0", 0, "Record mic data and decompressed voice data into 'voice_micdata.wav' and 'voice_decompressed.wav'");
ConVar voice_inputfromfile("voice_inputfromfile", "0", 0, "Get voice input from 'voice_input.wav' rather than from the microphone.", &voiceconvar_file_changed_f );
ConVar sv_allow_voice_from_file( "sv_allow_voice_from_file", "1", FCVAR_REPLICATED, "Allow or disallow clients from using voice_inputfromfile on this server.", &voiceconvar_file_changed_f );

class CStatusLineBuilder
{
public:
	CStatusLineBuilder() { Reset(); }
	void Reset() { m_curPosition = 0; m_szLine[0] = '\0'; }
	void AddColumnText( const char *pszText, unsigned int columnWidth )
	{
		size_t len = strlen( m_szLine );

		if ( m_curPosition > len )
		{
			for ( size_t i = len; i < m_curPosition; i++ )
			{
				m_szLine[i] = ' ';
			}
			m_szLine[m_curPosition] = '\0';
		}
		else if ( len != 0 )
		{
			// There is always at least one space between columns.
			m_szLine[len] = ' ';
			m_szLine[len+1] = '\0';
		}

		V_strncat( m_szLine, pszText, sizeof( m_szLine ) );
		m_curPosition += columnWidth + 1;
	}

	void InsertEmptyColumn( unsigned int columnWidth )
	{
		m_curPosition += columnWidth + 1;
	}

	const char *GetLine() { return m_szLine; }

private:
	size_t m_curPosition;
	char m_szLine[512];
};

uint GetSteamAppID()
{
	static uint sunAppID = 0;
	static bool bHaveValidSteamInterface = false;
	
	if ( !bHaveValidSteamInterface )
	{
#ifndef SWDS
		if ( Steam3Client().SteamUtils() )
		{
			bHaveValidSteamInterface = true;
			sunAppID = Steam3Client().SteamUtils()->GetAppID();
		}
#endif
		if ( Steam3Server().SteamGameServerUtils() )
		{
			bHaveValidSteamInterface = true;
			sunAppID = Steam3Server().SteamGameServerUtils()->GetAppID();
		}

		if ( !sunAppID )
			sunAppID = 215;	// defaults to Source SDK Base (215) if no steam.inf can be found.
	}
	
	return sunAppID;
}

EUniverse GetSteamUniverse()
{
#ifndef SWDS
	if ( Steam3Client().SteamUtils() )
		return Steam3Client().SteamUtils()->GetConnectedUniverse();
#endif
	if ( Steam3Server().SteamGameServerUtils() )
		return Steam3Server().SteamGameServerUtils()->GetConnectedUniverse();

	return k_EUniverseInvalid;
}

// Globals
int	gHostSpawnCount = 0;

// If any quit handlers balk, then aborts quit sequence
bool EngineTool_CheckQuitHandlers();

#if defined( _X360 )
CON_COMMAND( quit_x360, "" )
{
	int launchFlags = LF_EXITFROMGAME;

	// allocate the full payload
	int nPayloadSize = XboxLaunch()->MaxPayloadSize();
	byte *pPayload = (byte *)stackalloc( nPayloadSize );
	V_memset( pPayload, 0, sizeof( nPayloadSize ) );

	// payload is at least the command line
	// any user data needed must be placed AFTER the command line
	const char *pCmdLine = CommandLine()->GetCmdLine();
	int nCmdLineLength = (int)strlen( pCmdLine ) + 1;
	V_memcpy( pPayload, pCmdLine, min( nPayloadSize, nCmdLineLength ) );

	// add any other data here to payload, after the command line
	// ...

	// storage device may have changed since previous launch
	XboxLaunch()->SetStorageID( XBX_GetStorageDeviceId() );

	// Close the storage devices
	g_pXboxSystem->CloseContainers();
	// persist the user id
	bool bInviteRestart = args.FindArg( "invite" );
	DWORD nUserID = ( bInviteRestart ) ? XBX_GetInvitedUserId() : XBX_GetPrimaryUserId();
	XboxLaunch()->SetUserID( nUserID );

	if ( args.FindArg( "restart" ) )
	{
		launchFlags |= LF_GAMERESTART;
	}
	
	// If we're relaunching due to invite
	if ( bInviteRestart )
	{
		launchFlags |= LF_INVITERESTART;
		XNKID nSessionID = XBX_GetInviteSessionId();
		XboxLaunch()->SetInviteSessionID( &nSessionID );
	}

	bool bLaunch = XboxLaunch()->SetLaunchData( pPayload, nPayloadSize, launchFlags );
	if ( bLaunch )
	{
		COM_TimestampedLog( "Launching: \"%s\" Flags: 0x%8.8x", pCmdLine, XboxLaunch()->GetLaunchFlags() );
		g_pMaterialSystem->PersistDisplay();
		XBX_DisconnectConsoleMonitor();
		XboxLaunch()->Launch();
	}
}
#endif

/*
==================
Host_Quit_f
==================
*/
void Host_Quit_f( const CCommand &args )
{
#if !defined(SWDS)
	
	if ( args.FindArg( "prompt" ) )
	{
		// confirm they want to quit
		EngineVGui()->ConfirmQuit();
		return;
	}

	if ( !EngineTool_CheckQuitHandlers() )
	{
		return;
	}
#endif

	IGameEvent *event = g_GameEventManager.CreateEvent( "host_quit" );
	if ( event )
	{
		g_GameEventManager.FireEventClientSide( event );
	}

	HostState_Shutdown();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CON_COMMAND( _restart, "Shutdown and restart the engine." )
{
	/*
	// FIXME:  How to handle restarts?
#ifndef SWDS
	if ( !EngineTool_CheckQuitHandlers() )
	{
		return;
	}
#endif
	*/

	HostState_Restart();
}

#ifndef SWDS
//-----------------------------------------------------------------------------
// A console command to spew out driver information
//-----------------------------------------------------------------------------
void Host_LightCrosshair (void);

static ConCommand light_crosshair( "light_crosshair", Host_LightCrosshair, "Show texture color at crosshair", FCVAR_CHEAT );

void Host_LightCrosshair (void)
{
	Vector endPoint;
	Vector lightmapColor;

	// max_range * sqrt(3)
	VectorMA( MainViewOrigin(), COORD_EXTENT * 1.74f, MainViewForward(), endPoint );
	
	R_LightVec( MainViewOrigin(), endPoint, true, lightmapColor );
	int r = LinearToTexture( lightmapColor.x );
	int g = LinearToTexture( lightmapColor.y );
	int b = LinearToTexture( lightmapColor.z );

	ConMsg( "Luxel Value: %d %d %d\n", r, g, b );
}
#endif

/*
==================
Host_Status_PrintClient

Print client info to console 
==================
*/
void Host_Status_PrintClient( IClient *client, bool bShowAddress, void (*print) (const char *fmt, ...) )
{
	INetChannelInfo *nci = client->GetNetChannel();

	const char *state = "challenging";
	if ( client->IsActive() )
		state = "active";
	else if ( client->IsSpawned() )
		state = "spawning";
	else if ( client->IsConnected() )
		state = "connecting";

	CStatusLineBuilder builder;
	builder.AddColumnText( "#", STATUS_COLUMN_LENGTH_LINEPREFIX );
	builder.AddColumnText( va( "%" STATUS_COLUMN_LENGTH_USERID_STR "i", client->GetUserID() ), STATUS_COLUMN_LENGTH_USERID );
	builder.AddColumnText( va( "\"%s\"", client->GetClientName() ), STATUS_COLUMN_LENGTH_NAME );
	builder.AddColumnText( client->GetNetworkIDString(), STATUS_COLUMN_LENGTH_STEAMID );

	if ( nci != NULL )
	{
		builder.AddColumnText( COM_FormatSeconds( nci->GetTimeConnected() ), STATUS_COLUMN_LENGTH_TIME );
		builder.AddColumnText( va( "%" STATUS_COLUMN_LENGTH_PING_STR "i", (int)(1000.0f*nci->GetAvgLatency( FLOW_OUTGOING )) ), STATUS_COLUMN_LENGTH_PING );
		builder.AddColumnText( va( "%" STATUS_COLUMN_LENGTH_LOSS_STR "i", (int)(100.0f*nci->GetAvgLoss(FLOW_INCOMING)) ), STATUS_COLUMN_LENGTH_LOSS );
		builder.AddColumnText( state, STATUS_COLUMN_LENGTH_STATE );
		if ( bShowAddress )
			builder.AddColumnText( nci->GetAddress(), STATUS_COLUMN_LENGTH_ADDR );
	}
	else
	{
		builder.InsertEmptyColumn( STATUS_COLUMN_LENGTH_TIME );
		builder.InsertEmptyColumn( STATUS_COLUMN_LENGTH_PING );
		builder.InsertEmptyColumn( STATUS_COLUMN_LENGTH_LOSS );
		builder.AddColumnText( state, STATUS_COLUMN_LENGTH_STATE );
	}

	print( "%s\n", builder.GetLine() );
}

void Host_Client_Printf(const char *fmt, ...)
{
	va_list		argptr;
	char		string[1024];

	va_start (argptr,fmt);
	Q_vsnprintf (string, sizeof( string ), fmt,argptr);
	va_end (argptr);

	host_client->ClientPrintf( "%s", string );
}

#define LIMIT_PER_CLIENT_COMMAND_EXECUTION_ONCE_PER_INTERVAL(seconds) \
	{ \
		static float g_flLastTime__Limit[ABSOLUTE_PLAYER_LIMIT] = { 0.0f }; /* we don't have access to any of the three MAX_PLAYERS #define's here unfortunately */ \
		int playerindex = cmd_clientslot; \
		if ( playerindex >= 0 && playerindex < (ARRAYSIZE(g_flLastTime__Limit)) && realtime - g_flLastTime__Limit[playerindex] > (seconds) ) \
		{ \
			g_flLastTime__Limit[playerindex] = realtime; \
		} \
		else \
		{ \
			return; \
		} \
	}

//-----------------------------------------------------------------------------
// Host_Status_f
//-----------------------------------------------------------------------------
CON_COMMAND( status, "Display map and connection status." )
{
	IClient	*client;
	int j;
	void (*print) (const char *fmt, ...);

#if defined( _X360 )
	Vector org;
	QAngle ang;
	const char *pName;

	if ( cl.IsActive() )
	{
		pName = cl.m_szLevelNameShort;
		org = MainViewOrigin();
		VectorAngles( MainViewForward(), ang );
		IClientEntity *localPlayer = entitylist->GetClientEntity( cl.m_nPlayerSlot + 1 );
		if ( localPlayer )
		{
			org = localPlayer->GetAbsOrigin();
		}
	}
	else
	{
		pName = "";
		org.Init();
		ang.Init();
	}

	// send to vxconsole
	xMapInfo_t mapInfo;
	mapInfo.position[0] = org[0];
	mapInfo.position[1] = org[1];
	mapInfo.position[2] = org[2];
	mapInfo.angle[0]    = ang[0];
	mapInfo.angle[1]    = ang[1];
	mapInfo.angle[2]    = ang[2];
	mapInfo.build       = build_number();
	mapInfo.skill       = skill.GetInt();

	// generate the qualified path where .sav files are expected to be written
	char savePath[MAX_PATH];
	V_snprintf( savePath, sizeof( savePath ), "%s", saverestore->GetSaveDir() );
	V_StripTrailingSlash( savePath );
	g_pFileSystem->RelativePathToFullPath( savePath, "MOD", mapInfo.savePath, sizeof( mapInfo.savePath ) );
	V_FixSlashes( mapInfo.savePath );

	if ( pName[0] )
	{
		// generate the qualified path from where the map was loaded
		char mapPath[MAX_PATH];
		Q_snprintf( mapPath, sizeof( mapPath ), "maps/%s.360.bsp", pName );
		g_pFileSystem->GetLocalPath( mapPath, mapInfo.mapPath, sizeof( mapInfo.mapPath ) );
		Q_FixSlashes( mapInfo.mapPath );
	}
	else
	{
		mapInfo.mapPath[0] = '\0';
	}

	XBX_rMapInfo( &mapInfo );
#endif

	if ( cmd_source == src_command )
	{
		if ( !sv.IsActive() )
		{
			Cmd_ForwardToServer( args );
			return;
		}
		print = ConMsg;
	}
	else
	{
		print = Host_Client_Printf;

		// limit this to once per 5 seconds
		LIMIT_PER_CLIENT_COMMAND_EXECUTION_ONCE_PER_INTERVAL(5.0);
	}

	// ============================================================
	// Server status information.
	print( "hostname: %s\n", host_name.GetString() );

	const char *pchSecureReasonString = "";
	const char *pchUniverse = "";
	bool bGSSecure = Steam3Server().BSecure();
	if ( !bGSSecure && Steam3Server().BWantsSecure() )
	{
		if ( Steam3Server().BLoggedOn() )
		{
			pchSecureReasonString = " (secure mode enabled, connected to Steam3)";
		}
		else
		{
			pchSecureReasonString = " (secure mode enabled, disconnected from Steam3)";
		}
	}

	switch ( GetSteamUniverse() )
	{
		case k_EUniversePublic:
			pchUniverse = "";
			break;
		case k_EUniverseBeta:
			pchUniverse = " (beta)";
			break;
		case k_EUniverseInternal:
			pchUniverse = " (internal)";
			break;
		case k_EUniverseDev:
			pchUniverse = " (dev)";
			break;
		default:
			pchUniverse = " (unknown)";
			break;
	}
	

	print( "version : %s/%d %d %s%s%s\n", GetSteamInfIDVersionInfo().szVersionString,
		PROTOCOL_VERSION, build_number(), bGSSecure ? "secure" : "insecure", pchSecureReasonString, pchUniverse );

	if ( NET_IsMultiplayer() )
	{
		CUtlString sPublicIPInfo;
		if ( !Steam3Server().BLanOnly() )
		{
			uint32 unPublicIP = Steam3Server().GetPublicIP();
			if ( unPublicIP != 0 )
			{
				netadr_t addr;
				addr.SetIP( unPublicIP );
				sPublicIPInfo.Format("  (public ip: %s)", addr.ToString( true ) );
			}
		}
		print( "udp/ip  : %s:%i%s\n", net_local_adr.ToString(true), sv.GetUDPPort(), sPublicIPInfo.String() );

		if ( !Steam3Server().BLanOnly() )
		{
			if ( Steam3Server().BLoggedOn() )
				print( "steamid : %s (%llu)\n", Steam3Server().SteamGameServer()->GetSteamID().Render(), Steam3Server().SteamGameServer()->GetSteamID().ConvertToUint64() );
			else
				print( "steamid : not logged in\n" );
		}
	}

	// Check if this game uses server registration, then output status
	ConVarRef sv_registration_successful( "sv_registration_successful", true );
	if ( sv_registration_successful.IsValid() )
	{
		CUtlString sExtraInfo;
		ConVarRef sv_registration_message( "sv_registration_message", true );
		if ( sv_registration_message.IsValid() )
		{
			const char *msg = sv_registration_message.GetString();
			if ( msg && *msg )
			{
				sExtraInfo.Format("  (%s)", msg );
			}
		}

		if ( sv_registration_successful.GetBool() )
		{
			print( "account : logged in%s\n", sExtraInfo.String() );
		}
		else
		{
			print( "account : not logged in%s\n", sExtraInfo.String() );
		}
	}

	print( "map     : %s at: %d x, %d y, %d z\n", sv.GetMapName(), (int)MainViewOrigin()[0], (int)MainViewOrigin()[1], (int)MainViewOrigin()[2]);
	static ConVarRef sv_tags( "sv_tags" );
	print( "tags    : %s\n", sv_tags.GetString() );

	if ( hltv && hltv->IsActive() )
	{
		print( "sourcetv:  port %i, delay %.1fs\n", hltv->GetUDPPort(), hltv->GetDirector()->GetDelay() );
	}

#if defined( REPLAY_ENABLED )
	if ( replay && replay->IsActive() )
	{
		print( "replay  :  %s\n", replay->IsRecording() ? "recording" : "not recording" );
	}
#endif

	int players = sv.GetNumClients();
	int nBots = sv.GetNumFakeClients();
	int nHumans = players - nBots;

	print( "players : %i humans, %i bots (%i max)\n", nHumans, nBots, sv.GetMaxClients() );
	// ============================================================

	print( "edicts  : %d used of %d max\n", sv.num_edicts - sv.free_edicts, sv.max_edicts );

	if ( ( g_iServerGameDLLVersion >= 10 ) && serverGameDLL )
	{
		serverGameDLL->Status( print );
	}

	// Early exit for this server.
	if ( args.ArgC() == 2 )
	{
		if ( !Q_stricmp( args[1], "short" ) )
		{
			for ( j=0 ; j < sv.GetClientCount() ; j++ )
			{
				client = sv.GetClient( j );

				if ( !client->IsActive() )
					continue;

				print( "#%i - %s\n" , j + 1, client->GetClientName() );
			}
			return;
		}
	}

	// the header for the status rows
	// print( "# userid %-19s %-19s connected ping loss state%s\n", "name", "uniqueid", cmd_source == src_command ? "  adr" : "" );
	CStatusLineBuilder header;
	header.AddColumnText( "#", STATUS_COLUMN_LENGTH_LINEPREFIX );
	header.AddColumnText( "userid", STATUS_COLUMN_LENGTH_USERID );
	header.AddColumnText( "name", STATUS_COLUMN_LENGTH_NAME );
	header.AddColumnText( "uniqueid", STATUS_COLUMN_LENGTH_STEAMID );
	header.AddColumnText( "connected", STATUS_COLUMN_LENGTH_TIME );
	header.AddColumnText( "ping", STATUS_COLUMN_LENGTH_PING );
	header.AddColumnText( "loss", STATUS_COLUMN_LENGTH_LOSS );
	header.AddColumnText( "state", STATUS_COLUMN_LENGTH_STATE );
	if ( cmd_source == src_command )
	{
		header.AddColumnText( "adr", STATUS_COLUMN_LENGTH_ADDR );
	}

	print( "%s\n", header.GetLine() );

	for ( j=0 ; j < sv.GetClientCount() ; j++ )
	{
		client = sv.GetClient( j );

		if ( !client->IsConnected() )
			continue; // not connected yet, maybe challenging
		
		Host_Status_PrintClient( client, (cmd_source == src_command), print );
	}
}


//-----------------------------------------------------------------------------
// Host_Ping_f
//-----------------------------------------------------------------------------
CON_COMMAND( ping, "Display ping to server." )
{
	if ( cmd_source == src_command )
	{
		Cmd_ForwardToServer( args );
		return;
	}
	// limit this to once per 5 seconds
	LIMIT_PER_CLIENT_COMMAND_EXECUTION_ONCE_PER_INTERVAL(5.0);

	host_client->ClientPrintf( "Client ping times:\n" );

	for ( int i=0; i< sv.GetClientCount(); i++ )
	{
		IClient	*client = sv.GetClient(i);

		if ( !client->IsConnected() || client->IsFakeClient() )
			continue;

		host_client->ClientPrintf ("%4.0f ms : %s\n", 
			1000.0f * client->GetNetChannel()->GetAvgLatency( FLOW_OUTGOING ), client->GetClientName() );
	}
}

bool CL_HL2Demo_MapCheck( const char *name )
{
	if ( IsPC() && CL_IsHL2Demo() && !sv.IsDedicated() )
	{
		if (    !Q_stricmp( name, "d1_trainstation_01" ) || 
				!Q_stricmp( name, "d1_trainstation_02" ) || 
				!Q_stricmp( name, "d1_town_01" ) || 
				!Q_stricmp( name, "d1_town_01a" ) || 
				!Q_stricmp( name, "d1_town_02" ) || 
				!Q_stricmp( name, "d1_town_03" ) ||
				!Q_stricmp( name, "background01" ) ||
				!Q_stricmp( name, "background03" ) 
			)
		{
			return true;
		}
		return false;
	}

	return true;
}

bool CL_PortalDemo_MapCheck( const char *name )
{
	if ( IsPC() && CL_IsPortalDemo() && !sv.IsDedicated() )
	{
		if (    !Q_stricmp( name, "testchmb_a_00" ) || 
				!Q_stricmp( name, "testchmb_a_01" ) || 
				!Q_stricmp( name, "testchmb_a_02" ) || 
				!Q_stricmp( name, "testchmb_a_03" ) || 
				!Q_stricmp( name, "testchmb_a_04" ) || 
				!Q_stricmp( name, "testchmb_a_05" ) ||
				!Q_stricmp( name, "testchmb_a_06" ) ||
				!Q_stricmp( name, "background1" ) 
			)
		{
			return true;
		}
		return false;
	}

	return true;
}

int _Host_Map_f_CompletionFunc( char const *cmdname, char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] );

// Note, leaves name alone if no match possible
static bool Host_Map_Helper_FuzzyName( const CCommand &args, char *name, size_t bufsize )
{
	char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ];
	CUtlString argv0;
	argv0 = args.Arg( 0 );
	argv0 += " ";

	if ( _Host_Map_f_CompletionFunc( argv0, args.ArgS(), commands ) > 0 )
	{
		Q_strncpy( name, &commands[ 0 ][ argv0.Length() ], bufsize );
		return true;
	}
	return false;
}

void Host_Map_Helper( const CCommand &args, bool bEditmode, bool bBackground, bool bCommentary )
{
	if ( cmd_source != src_command )
		return;
	if (args.ArgC() < 2)
	{
		Warning("No map specified\n");
		return;
	}

	const char *pszReason = NULL;
	if ( ( g_iServerGameDLLVersion >= 10 ) && !serverGameDLL->IsManualMapChangeOkay( &pszReason ) )
	{
		if ( pszReason && pszReason[0] )
		{
			Warning( "%s\n", pszReason );
		}
		return;
	}

	char szMapName[ MAX_QPATH ] = { 0 };
	V_strncpy( szMapName, args[ 1 ], sizeof( szMapName ) );

	// Call find map, proceed for any value besides NotFound
	IVEngineServer::eFindMapResult eResult = g_pVEngineServer->FindMap( szMapName, sizeof( szMapName ) );
	if ( eResult == IVEngineServer::eFindMap_NotFound )
	{
		Warning( "map load failed: %s not found or invalid\n", args[ 1 ] );
		return;
	}

	COM_TimestampedLog( "*** Map Load: %s", szMapName );

	// There is a precision issue here, as described Bruce Dawson's blog.
	// In our case, we don't care because we're looking for anything on the order of second precision, which 
	// covers runtime up to around 4 months.
	static ConVarRef dev_loadtime_map_start( "dev_loadtime_map_start" );
	dev_loadtime_map_start.SetValue( (float)Plat_FloatTime() );

	// If I was in edit mode reload config file
	// to overwrite WC edit key bindings
#if !defined(SWDS)
	if ( !bEditmode )
	{
		if ( g_bInEditMode )
		{
			// Re-read config from disk
			Host_ReadConfiguration();
			g_bInEditMode = false;
		}
	}
	else
	{
		g_bInEditMode = true;
	}

	g_bInCommentaryMode = bCommentary;
#endif

	if ( !CL_HL2Demo_MapCheck( szMapName ) )
	{
		Warning( "map load failed: %s not found or invalid\n", szMapName );
		return;	
	}

	if ( !CL_PortalDemo_MapCheck( szMapName ) )
	{
		Warning( "map load failed: %s not found or invalid\n", szMapName );
		return;	
	}

#if defined( REPLAY_ENABLED )
	// If we're recording the game, finalize the replay so players can download it.
	if ( g_pReplay && g_pReplay->IsRecording() )
	{
		g_pReplay->SV_EndRecordingSession();
	}
#endif

	// Stop demo loop
	cl.demonum = -1;

	Host_Disconnect( false );	// stop old game

	HostState_NewGame( szMapName, false, bBackground );

	if (args.ArgC() == 10)
	{
		if (Q_stricmp(args[2], "setpos") == 0
			&& Q_stricmp(args[6], "setang") == 0) 
		{
			Vector newpos;
			newpos.x = atof( args[3] );
			newpos.y = atof( args[4] );
			newpos.z = atof( args[5] );

			QAngle newangle;
			newangle.x = atof( args[7] );
			newangle.y = atof( args[8] );
			newangle.z = atof( args[9] );
			
			HostState_SetSpawnPoint(newpos, newangle);
		}
	}
}

/*
======================
Host_Map_f

handle a 
map <servername>
command from the console.  Active clients are kicked off.
======================
*/
void Host_Map_f( const CCommand &args )
{
	Host_Map_Helper( args, false, false, false );
}


//-----------------------------------------------------------------------------
// handle a map_edit <servername> command from the console. 
// Active clients are kicked off.
// UNDONE: protect this from use if not in dev. mode
//-----------------------------------------------------------------------------
#ifndef SWDS
CON_COMMAND( map_edit, "" )
{
	Host_Map_Helper( args, true, false, false );
}
#endif


//-----------------------------------------------------------------------------
// Purpose: Runs a map as the background
//-----------------------------------------------------------------------------
void Host_Map_Background_f( const CCommand &args )
{
	Host_Map_Helper( args, false, true, false );
}


//-----------------------------------------------------------------------------
// Purpose: Runs a map in commentary mode
//-----------------------------------------------------------------------------
void Host_Map_Commentary_f( const CCommand &args )
{
	Host_Map_Helper( args, false, false, true );
}


//-----------------------------------------------------------------------------
// Restarts the current server for a dead player
//-----------------------------------------------------------------------------
CON_COMMAND( restart, "Restart the game on the same level (add setpos to jump to current view position on restart)." )
{
	if ( 
#if !defined(SWDS)
		demoplayer->IsPlayingBack() || 
#endif
		!sv.IsActive() )
		return;

	if ( sv.IsMultiplayer() )
		return;

	if ( cmd_source != src_command )
		return;

	bool bRememberLocation = ( args.ArgC() == 2 && !Q_stricmp( args[1], "setpos" ) );

	Host_Disconnect(false);	// stop old game

	if ( !CL_HL2Demo_MapCheck( sv.GetMapName() ) )
	{
		Warning( "map load failed: %s not found or invalid\n", sv.GetMapName() );
		return;	
	}

	if ( !CL_PortalDemo_MapCheck( sv.GetMapName() ) )
	{
		Warning( "map load failed: %s not found or invalid\n", sv.GetMapName() );
		return;	
	}

	HostState_NewGame( sv.GetMapName(), bRememberLocation, false );
}


//-----------------------------------------------------------------------------
// Restarts the current server for a dead player
//-----------------------------------------------------------------------------
CON_COMMAND( reload, "Reload the most recent saved game (add setpos to jump to current view position on reload).")
{
#ifndef SWDS
	const char *pSaveName;
	char name[MAX_OSPATH];
#endif

	if ( 
#if !defined(SWDS)
		demoplayer->IsPlayingBack() || 
#endif
		!sv.IsActive() )
		return;

	if ( sv.IsMultiplayer() )
		return;

	if (cmd_source != src_command)
		return;

	bool remember_location = false;
	if ( args.ArgC() == 2 && 
		!Q_stricmp( args[1], "setpos" ) )
	{
		remember_location = true;
	}

	// See if there is a most recently saved game
	// Restart that game if there is
	// Otherwise, restart the starting game map
#ifndef SWDS
	pSaveName = saverestore->FindRecentSave( name, sizeof( name ) );

	// Put up loading plaque
  	SCR_BeginLoadingPlaque();

	Host_Disconnect( false );	// stop old game

	if ( pSaveName && saverestore->SaveFileExists( pSaveName ) )
	{
		HostState_LoadGame( pSaveName, remember_location );
	}
	else
#endif
	{
		if ( !CL_HL2Demo_MapCheck( host_map.GetString() ) )
		{
			Warning( "map load failed: %s not found or invalid\n", host_map.GetString() );
			return;	
		}

		if ( !CL_PortalDemo_MapCheck( host_map.GetString() ) )
		{
			Warning( "map load failed: %s not found or invalid\n", host_map.GetString() );
			return;	
		} 

		HostState_NewGame( host_map.GetString(), remember_location, false );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Goes to a new map, taking all clients along
// Output : void Host_Changelevel_f
//-----------------------------------------------------------------------------
void Host_Changelevel_f( const CCommand &args )
{
	if ( args.ArgC() < 2 )
	{
		ConMsg( "changelevel <levelname> : continue game on a new level\n" );
		return;
	}

	if ( !sv.IsActive() )
	{
		ConMsg( "Can't changelevel, not running server\n" );
		return;
	}

	char szName[MAX_PATH] = { 0 };
	V_strncpy( szName, args[1], sizeof( szName ) );

	// Call find map to attempt to resolve fuzzy/non-canonical map names
	IVEngineServer::eFindMapResult eResult = g_pVEngineServer->FindMap( szName, sizeof( szName ) );
	if ( eResult == IVEngineServer::eFindMap_NotFound )
	{
		// Warn, but but proceed even if the map is not found, such that we hit the proper server_levelchange_failed
		// codepath and event later on.
		Warning( "Failed to find map %s\n", args[ 1 ] );
	}

	if ( !CL_HL2Demo_MapCheck(szName) )
	{
		Warning( "changelevel failed: %s not found\n", szName );
		return;
	}

	if ( !CL_PortalDemo_MapCheck(szName) )
	{
		Warning( "changelevel failed: %s not found\n", szName );
		return;
	}

	const char *pszReason = NULL;
	if ( ( g_iServerGameDLLVersion >= 10 ) && !serverGameDLL->IsManualMapChangeOkay( &pszReason ) )
	{
		if ( pszReason && pszReason[0] )
		{
			Warning( "%s", pszReason );
		}
		return;
	}

	HostState_ChangeLevelMP( szName, args[2] );
}

//-----------------------------------------------------------------------------
// Purpose: Changing levels within a unit, uses save/restore
//-----------------------------------------------------------------------------
void Host_Changelevel2_f( const CCommand &args )
{
	if ( args.ArgC() < 2 )
	{
		ConMsg ("changelevel2 <levelname> : continue game on a new level in the unit\n");
		return;
	}

	if ( !sv.IsActive() || sv.IsMultiplayer() )
	{
		ConMsg( "Can't changelevel2, not in a single-player map\n" );
		return;
	}

	char szName[MAX_PATH] = { 0 };
	V_strncpy( szName, args[1], sizeof( szName ) );
	IVEngineServer::eFindMapResult eResult = g_pVEngineServer->FindMap( szName, sizeof( szName ) );
	if ( eResult == IVEngineServer::eFindMap_NotFound )
	{
		if ( !CL_IsHL2Demo() || (CL_IsHL2Demo() && !(!Q_stricmp( szName, "d1_trainstation_03" ) || !Q_stricmp( szName, "d1_town_02a" ))) )	
		{
			Warning( "changelevel2 failed: %s not found\n", szName );
			return;
		}
	}

#if !defined(SWDS)
	// needs to be before CL_HL2Demo_MapCheck() check as d1_trainstation_03 isn't a valid map
	if ( IsPC() && CL_IsHL2Demo() && !sv.IsDedicated() && !Q_stricmp( szName, "d1_trainstation_03" ) ) 
	{
		void CL_DemoTransitionFromTrainstation();
		CL_DemoTransitionFromTrainstation();
		return; 
	}

	// needs to be before CL_HL2Demo_MapCheck() check as d1_trainstation_03 isn't a valid map
	if ( IsPC() && CL_IsHL2Demo() && !sv.IsDedicated() && !Q_stricmp( szName, "d1_town_02a" ) && !Q_stricmp( args[2], "d1_town_02_02a" )) 
	{
		void CL_DemoTransitionFromRavenholm();
		CL_DemoTransitionFromRavenholm();
		return; 
	}

	if ( IsPC() && CL_IsPortalDemo() && !sv.IsDedicated() && !Q_stricmp( szName, "testchmb_a_07" ) ) 
	{
		void CL_DemoTransitionFromTestChmb();
		CL_DemoTransitionFromTestChmb();
		return; 
	}

#endif

	// allow a level transition to d1_trainstation_03 so the Host_Changelevel() can act on it
	if ( !CL_HL2Demo_MapCheck( szName ) ) 
	{
		Warning( "changelevel failed: %s not found\n", szName );
		return;	
	}

	HostState_ChangeLevelSP( szName, args[2] );
}


//-----------------------------------------------------------------------------
// Purpose: Shut down client connection and any server
//-----------------------------------------------------------------------------
void Host_Disconnect( bool bShowMainMenu, const char *pszReason )
{
	if ( IsX360() )
	{
		g_pQueuedLoader->EndMapLoading( false );
	}

#ifndef SWDS
	if ( !sv.IsDedicated() )
	{
		cl.Disconnect( pszReason, bShowMainMenu );
	}
#endif
	Host_AllowQueuedMaterialSystem( false );
	HostState_GameShutdown();
}

void Disconnect()
{
	cl.demonum = -1;
	Host_Disconnect(true);

#if defined( REPLAY_ENABLED )
	// Finalize the recording replay on the server, if is recording.
	// NOTE: We don't want this in Host_Disconnect() as that would be called more
	// than necessary.
	if ( g_pReplay && g_pReplay->IsReplayEnabled() && sv.IsDedicated() )
	{
		g_pReplay->SV_EndRecordingSession();
	}
#endif
}

//-----------------------------------------------------------------------------
// Kill the client and any local server.
//-----------------------------------------------------------------------------
CON_COMMAND( disconnect, "Disconnect game from server." )
{
#if !defined( SWDS )
	// Just run the regular Disconnect function if we're not the client or the client didn't handle it for us
	if( !g_ClientDLL || !g_ClientDLL->DisconnectAttempt() )
	{
		Disconnect();
	}
#else
	Disconnect();
#endif
}

#ifdef _WIN32
// manually pull in the GetEnvironmentVariableA defn so we don't need to include windows.h
extern "C"
{
DWORD __declspec(dllimport) __stdcall GetEnvironmentVariableA( const char *, char *, DWORD );
}
#endif // _WIN32

CON_COMMAND( version, "Print version info string." )
{
	ConMsg( "Build Label:          %8d   # Uniquely identifies each build\n", GetSteamInfIDVersionInfo().ServerVersion );
	ConMsg( "Network PatchVersion: %8s   # Determines client and server compatibility\n", GetSteamInfIDVersionInfo().szVersionString );
	ConMsg( "Protocol version:     %8d   # High level network protocol version\n", PROTOCOL_VERSION );

	if ( sv.IsDedicated() || serverGameDLL )
	{
		ConMsg( "Server version:       %8i\n", GetSteamInfIDVersionInfo().ServerVersion );
		ConMsg( "Server AppID:         %8i\n", GetSteamInfIDVersionInfo().ServerAppID );
	}
	if ( !sv.IsDedicated() )
	{
		ConMsg( "Client version:       %8i\n", GetSteamInfIDVersionInfo().ClientVersion );
		ConMsg( "Client AppID:         %8i\n", GetSteamInfIDVersionInfo().AppID );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CON_COMMAND( pause, "Toggle the server pause state." )
{
#ifndef SWDS
	if ( !sv.IsDedicated() )
	{
		if ( !cl.m_szLevelFileName[ 0 ] )
			return;
	}
#endif

	if ( cmd_source == src_command )
	{
		Cmd_ForwardToServer( args );
		return;
	}

	if ( !sv.IsPausable() )
		return;

	// toggle paused state
	sv.SetPaused( !sv.IsPaused() );
	
	// send text messaage who paused the game
	sv.BroadcastPrintf( "%s %s the game\n", host_client->GetClientName(), sv.IsPaused() ? "paused" : "unpaused" );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CON_COMMAND( setpause, "Set the pause state of the server." )
{
#ifndef SWDS
	if ( !cl.m_szLevelFileName[ 0 ] )
		return;
#endif

	if ( cmd_source == src_command )
	{
		Cmd_ForwardToServer( args );
		return;
	}

	sv.SetPaused( true );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CON_COMMAND( unpause, "Unpause the game." )
{
#ifndef SWDS
	if ( !cl.m_szLevelFileName[ 0 ] )
		return;
#endif

	if ( cmd_source == src_command )
	{
		Cmd_ForwardToServer( args );
		return;
	}
	
	sv.SetPaused( false );
}

// No non-testing use for this at the moment, though server mods in public will expose similar functionality
#if defined( STAGING_ONLY ) || defined( _DEBUG )
//-----------------------------------------------------------------------------
// Purpose: Send a string command to a client by userid
//-----------------------------------------------------------------------------
CON_COMMAND( clientcmd, "Send a clientside command to a player by userid" )
{
	if ( args.ArgC() <= 2 )
	{
		ConMsg( "Usage:  clientcmd < userid > { command string }\n" );
		return;
	}

	// Args
	int userid = Q_atoi( args[1] );
	int messageArgStart = 2;

	// Concatenate other arguments into string
	CUtlString commandString;

	commandString.SetLength( Q_strlen( args.ArgS() ) );
	commandString.Set( args[ messageArgStart ] );
	for ( int i = messageArgStart + 1; i < args.ArgC(); i++ )
	{
		commandString.Append( " " );
		commandString.Append( args[i] );
	}

	// find client
	IClient *client = NULL;
	for ( int i = 0; i < sv.GetClientCount(); i++ )
	{
		IClient *searchclient = sv.GetClient( i );

		if ( !searchclient->IsConnected() )
			continue;

		if ( userid != -1 && searchclient->GetUserID() == userid )
		{
			client = searchclient;
			break;
		}
	}

	if ( !client )
	{
		ConMsg( "userid \"%d\" not found\n", userid );
		return;
	}

	NET_StringCmd cmdMsg( commandString ) ;
	client->SendNetMsg( cmdMsg, true );
}
#endif // defined( STAGING_ONLY ) || defined( _DEBUG )

//-----------------------------------------------------------------------------
// Kicks a user off of the server using their userid or uniqueid
//-----------------------------------------------------------------------------
CON_COMMAND( kickid, "Kick a player by userid or uniqueid, with a message." )
{
	char		*who = NULL;
	const char	*pszArg1 = NULL, *pszMessage = NULL;
	IClient		*client = NULL;
	int			iSearchIndex = -1;
	char		szSearchString[128];
	int			argsStartNum = 1;
	bool		bSteamID = false;
	int			i = 0;

	if ( args.ArgC() <= 1 )
	{
		ConMsg( "Usage:  kickid < userid | uniqueid > { message }\n" );
		return;
	}

	// get the first argument
	pszArg1 = args[1];

	// if the first letter is a character then
	// we're searching for a uniqueid ( e.g. STEAM_ )
	if ( *pszArg1 < '0' || *pszArg1 > '9' )
	{
		// SteamID (need to reassemble it)
		if ( !Q_strnicmp( pszArg1, STEAM_PREFIX, strlen( STEAM_PREFIX ) ) && Q_strstr( args[2], ":" ) )
		{
			Q_snprintf( szSearchString, sizeof( szSearchString ), "%s:%s:%s", pszArg1, args[3], args[5] );
			argsStartNum = 5;
			bSteamID = true;
		}
		// some other ID (e.g. "UNKNOWN", "STEAM_ID_PENDING", "STEAM_ID_LAN")
		// NOTE: assumed to be one argument
		else
		{
			Q_snprintf( szSearchString, sizeof( szSearchString ), "%s", pszArg1 );
		}
	}
	// this is a userid
	else
	{
		iSearchIndex = Q_atoi( pszArg1 );
	}

	// check for a message
	if ( args.ArgC() > argsStartNum )
	{
		int j;
		int dataLen = 0;

		pszMessage = args.ArgS();
		for ( j = 1; j <= argsStartNum; j++ )
		{
			dataLen += Q_strlen( args[j] ) + 1; // +1 for the space between args
		}

		if ( bSteamID )
		{
			dataLen -= 5; // SteamIDs don't have spaces between the args[) values
		}

		if ( dataLen > Q_strlen( pszMessage ) ) // saftey check
		{
			pszMessage = NULL;
		}
		else
		{
			pszMessage += dataLen;
		}
	}

	// find this client
	for ( i = 0; i < sv.GetClientCount(); i++ )
	{
		client = sv.GetClient( i );

		if ( !client->IsConnected() )
			continue;

#if defined( REPLAY_ENABLED )
		if ( client->IsReplay() )
			continue;
#endif

		if ( client->IsHLTV() )
			continue;

		// searching by UserID
		if ( iSearchIndex != -1 )
		{
			if ( client->GetUserID() == iSearchIndex )
			{
				// found!
				break;
			}
		}
		// searching by UniqueID
		else	
		{
			if ( Q_stricmp( client->GetNetworkIDString(), szSearchString ) == 0 ) 
			{
				// found!
				break;
			}
		}
	}

	// now kick them
	if ( i < sv.GetClientCount() )
	{
		if ( cmd_source != src_command )
		{
			who = host_client->m_Name;
		}

		// can't kick yourself!
		if ( cmd_source != src_command && host_client == client && !sv.IsDedicated() )
		{
			return;
		}

		if ( iSearchIndex != -1 || !client->IsFakeClient() )
		{
			if ( who == NULL )
			{
				if ( pszMessage )
				{
					client->Disconnect( "%s", pszMessage );
				}
				else
				{
					client->Disconnect( KICKED_BY_CONSOLE );
				}
			}
			else
			{
				if ( pszMessage )
				{
					client->Disconnect( "Kicked by %s : %s", who, pszMessage );
				}
				else
				{
					client->Disconnect( "Kicked by %s", who );
				}
			}
		}
	}
	else
	{
		if ( iSearchIndex != -1 )
		{
			ConMsg( "userid \"%d\" not found\n", iSearchIndex );
		}
		else
		{
			ConMsg( "uniqueid \"%s\" not found\n", szSearchString );			
		}
	}
}

/*
==================
Host_Kick_f

Kicks a user off of the server using their name
==================
*/
CON_COMMAND( kick, "Kick a player by name." )
{
	char		*who = NULL;
	char		*pszName = NULL;
	IClient		*client = NULL;
	int			i = 0;
	char		name[64];

	if ( args.ArgC() <= 1 )
	{
		ConMsg( "Usage:  kick < name >\n" );
		return;
	}

	// copy the name to a local buffer
	memset( name, 0, sizeof(name) );
	Q_strncpy( name, args.ArgS(), sizeof(name) );
	pszName = name;

	// safety check
	if ( pszName && pszName[0] != 0 )
	{
		//HACK-HACK
		// check for the name surrounded by quotes (comes in this way from rcon)
		int len = Q_strlen( pszName ) - 1; // (minus one since we start at 0)
		if ( pszName[0] == '"' && pszName[len] == '"' )
		{
			// get rid of the quotes at the beginning and end
			pszName[len] = 0;
			pszName++;
		}

		for ( i = 0; i < sv.GetClientCount(); i++ )
		{
			client = sv.GetClient(i);

			if ( !client->IsConnected() )
				continue;

#if defined( REPLAY_ENABLED )
			if ( client->IsReplay() )
				continue;
#endif

			if ( client->IsHLTV() )
				continue;

			// found!
			if ( Q_strcasecmp( client->GetClientName(), pszName ) == 0 ) 
				break;
		}

		// now kick them
		if ( i < sv.GetClientCount() )
		{
			if ( cmd_source != src_command )
			{
				who = host_client->m_Name;
			}

			// can't kick yourself!
			if ( cmd_source != src_command && host_client == client && !sv.IsDedicated() )
				return;

			if ( who )
			{
				client->Disconnect( "Kicked by %s", who );
			}
			else
			{
				client->Disconnect( KICKED_BY_CONSOLE );
			}
		}
		else
		{
			ConMsg( "name \"%s\" not found\n", pszName );
		}
	}
}

//-----------------------------------------------------------------------------
// Kicks all users off of the server
//-----------------------------------------------------------------------------
CON_COMMAND( kickall, "Kicks everybody connected with a message." )
{
	char		*who = NULL;
	IClient		*client = NULL;
	int			i = 0;
	char		szMessage[128];

	// copy the message to a local buffer
	memset( szMessage, 0, sizeof(szMessage) );
	V_strcpy_safe( szMessage, args.ArgS() );

	if ( cmd_source != src_command )
	{
		who = host_client->m_Name;
	}

	for ( i = 0; i < sv.GetClientCount(); i++ )
	{
		client = sv.GetClient(i);

		if ( !client->IsConnected() )
			continue;

		// can't kick yourself!
		if ( cmd_source != src_command && host_client == client && !sv.IsDedicated() )
			continue;

#if defined( REPLAY_ENABLED )
		if ( client->IsReplay() )
			continue;
#endif

		if ( client->IsHLTV() )
			continue;

		if ( who )
		{
			if ( szMessage[0] )
			{
				client->Disconnect( "Kicked by %s : %s", who, szMessage );
			}
			else
			{
				client->Disconnect( "Kicked by %s", who );
			}
		}
		else
		{
			if ( szMessage[0] )
			{
				client->Disconnect( "%s", szMessage );
			}
			else
			{
				client->Disconnect( KICKED_BY_CONSOLE );
			}
		}
	}
}

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

DEBUGGING TOOLS

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


//-----------------------------------------------------------------------------
// Dump memory stats
//-----------------------------------------------------------------------------
CON_COMMAND( memory, "Print memory stats." )
{
#if !defined(NO_MALLOC_OVERRIDE)
	ConMsg( "Heap Used:\n" );
	int nTotal = MemAlloc_GetSize( 0 );
	if (nTotal == -1)
	{
		ConMsg( "Corrupted!\n" );
	}
	else
	{
		ConMsg( "%5.2f MB (%d bytes)\n", nTotal/(1024.0f*1024.0f), nTotal );
	}
#endif

#ifdef VPROF_ENABLED
	ConMsg("\nVideo Memory Used:\n");
	CVProfile *pProf = &g_VProfCurrentProfile;
	int prefixLen = strlen( "TexGroup_Global_" );
	float total = 0.0f;
	for ( int i=0; i < pProf->GetNumCounters(); i++ )
	{
		if ( pProf->GetCounterGroup( i ) == COUNTER_GROUP_TEXTURE_GLOBAL )
		{
			float value = pProf->GetCounterValue( i ) * (1.0f/(1024.0f*1024.0f) );
			total += value;
			const char *pName = pProf->GetCounterName( i );
			if ( !Q_strnicmp( pName, "TexGroup_Global_", prefixLen ) )
			{
				pName += prefixLen;
			}
			ConMsg( "%5.2f MB: %s\n", value, pName );
		}
	}
	ConMsg("------------------\n");
	ConMsg( "%5.2f MB: total\n", total );
#endif

	ConMsg( "\nHunk Memory Used:\n" );
	Hunk_Print();
}

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

DEMO LOOP CONTROL

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


#ifndef SWDS

//MOTODO move all demo commands to demoplayer


//-----------------------------------------------------------------------------
// Purpose: Gets number of valid demo names
// Output : int
//-----------------------------------------------------------------------------
int Host_GetNumDemos()
{
	int c = 0;
#ifndef SWDS
	for ( int i = 0; i < MAX_DEMOS; ++i )
	{
		const char *demoname = cl.demos[ i ].Get();
		if ( !demoname[ 0 ] )
			break;

		++c;
	}
#endif
	return c;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void Host_PrintDemoList()
{
	int count = Host_GetNumDemos();

	int next = cl.demonum;
	if ( next >= count || next < 0 )
	{
		next = 0;
	}

#ifndef SWDS
	for ( int i = 0; i < MAX_DEMOS; ++i )
	{
		const char *demoname = cl.demos[ i ].Get();
		if ( !demoname[ 0 ] )
			break;

		bool isnextdemo = next == i ? true : false;

		DevMsg( "%3s % 2i : %20s\n", isnextdemo ? "-->" : "   ", i, cl.demos[ i ].Get() );
	}
#endif

	if ( !count )
	{
		DevMsg( "No demos in list, use startdemos <demoname> <demoname2> to specify\n" );
	}
}


#ifndef SWDS
//-----------------------------------------------------------------------------
//
// Con commands related to demos, not available on dedicated servers
//
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Purpose: Specify list of demos for the "demos" command
//-----------------------------------------------------------------------------
CON_COMMAND( startdemos, "Play demos in demo sequence." )
{
	int	c = args.ArgC() - 1;
	if (c > MAX_DEMOS)
	{
		Msg ("Max %i demos in demoloop\n", MAX_DEMOS);
		c = MAX_DEMOS;
	}
	Msg ("%i demo(s) in loop\n", c);

	for ( int i=1 ; i<c+1 ; i++ )
	{
		cl.demos[i-1] = args[i];
	}

	cl.demonum = 0;

	Host_PrintDemoList();

	if ( !sv.IsActive() && !demoplayer->IsPlayingBack() )
	{
		CL_NextDemo ();
	}
	else
	{
		cl.demonum = -1;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Return to looping demos, optional resume demo index
//-----------------------------------------------------------------------------
CON_COMMAND( demos, "Demo demo file sequence." )
{
	int oldn = cl.demonum;
	cl.demonum = -1;
	Host_Disconnect(false);
	cl.demonum = oldn;

	if (cl.demonum == -1)
		cl.demonum = 0;

	if ( args.ArgC() == 2 )
	{
		int numdemos = Host_GetNumDemos();
		if ( numdemos >= 1 )
		{
			cl.demonum = clamp( Q_atoi( args[1] ), 0, numdemos - 1 );
			DevMsg( "Jumping to %s\n", cl.demos[ cl.demonum ].Get() );
		}
	}

	Host_PrintDemoList();

	CL_NextDemo ();
}

//-----------------------------------------------------------------------------
// Purpose: Stop current demo
//-----------------------------------------------------------------------------
CON_COMMAND_F( stopdemo, "Stop playing back a demo.", FCVAR_DONTRECORD )
{
	if ( !demoplayer->IsPlayingBack() )
		return;
	
	Host_Disconnect (true);
}

//-----------------------------------------------------------------------------
// Purpose: Skip to next demo
//-----------------------------------------------------------------------------
CON_COMMAND( nextdemo, "Play next demo in sequence." )
{
	if ( args.ArgC() == 2 )
	{
		int numdemos = Host_GetNumDemos();
		if ( numdemos >= 1 )
		{
			cl.demonum = clamp( Q_atoi( args[1] ), 0, numdemos - 1 );
			DevMsg( "Jumping to %s\n", cl.demos[ cl.demonum ].Get() );
		}
	}
	Host_EndGame( false, "Moving to next demo..." );
}

//-----------------------------------------------------------------------------
// Purpose: Print out the current demo play order
//-----------------------------------------------------------------------------
CON_COMMAND( demolist, "Print demo sequence list." )
{
	Host_PrintDemoList();
}


//-----------------------------------------------------------------------------
// Purpose: Host_Soundfade_f
//-----------------------------------------------------------------------------
CON_COMMAND_F( soundfade, "Fade client volume.", FCVAR_SERVER_CAN_EXECUTE )
{
	float percent;
	float inTime, holdTime, outTime;

	if (args.ArgC() != 3 && args.ArgC() != 5)
	{
		Msg("soundfade <percent> <hold> [<out> <int>]\n");
		return;
	}

	percent = clamp( (float) atof(args[1]), 0.0f, 100.0f );
	
	holdTime = max( 0., atof(args[2]) );

	inTime = 0.0f;
	outTime = 0.0f;
	if (args.ArgC() == 5)
	{
		outTime = max( 0., atof(args[3]) );
		inTime = max( 0., atof( args[4]) );
	}

	S_SoundFade( percent, holdTime, outTime, inTime );
}

#endif // !SWDS

#endif


//-----------------------------------------------------------------------------
// Shutdown the server
//-----------------------------------------------------------------------------
CON_COMMAND( killserver, "Shutdown the server." )
{
	Host_Disconnect(true);
	
	if ( !sv.IsDedicated() )
	{
		// close network sockets
		NET_SetMutiplayer( false );
	}
}

#if !defined(SWDS)
void Host_VoiceRecordStart_f(void)
{
#ifdef VOICE_VOX_ENABLE
	ConVarRef voice_vox( "voice_vox" );
	if ( voice_vox.IsValid() && voice_vox.GetBool() )
		return;
#endif // VOICE_VOX_ENABLE

	if ( cl.IsActive() )
	{
		const char *pUncompressedFile = NULL;
		const char *pDecompressedFile = NULL;
		const char *pInputFile = NULL;
		
		if (voice_recordtofile.GetInt())
		{
			pUncompressedFile = "voice_micdata.wav";
			pDecompressedFile = "voice_decompressed.wav";
		}

		if (voice_inputfromfile.GetInt())
		{
			pInputFile = "voice_input.wav";
		}
		if ( !sv_allow_voice_from_file.GetBool() )
		{
			pInputFile = NULL;
		}
#if !defined( NO_VOICE )
		if (Voice_RecordStart(pUncompressedFile, pDecompressedFile, pInputFile))
		{
		}
#endif
	}
}


void Host_VoiceRecordStop_f(void)
{
#ifdef VOICE_VOX_ENABLE
	ConVarRef voice_vox( "voice_vox" );
	if ( voice_vox.IsValid() && voice_vox.GetBool() )
		return;
#endif // VOICE_VOX_ENABLE

	if ( cl.IsActive() )
	{
#if !defined( NO_VOICE )
		if (Voice_IsRecording())
		{
			CL_SendVoicePacket( g_bUsingSteamVoice ? false : true );
			Voice_UserDesiresStop();
		}
#endif
	}
}

#ifdef VOICE_VOX_ENABLE
void Host_VoiceToggle_f( const CCommand &args )
{
	if ( cl.IsActive() )
	{
#if !defined( NO_VOICE )	
		bool bToggle = false;

		if ( args.ArgC() == 2 && V_strcasecmp( args[1], "on" ) == 0 )
		{
			bToggle = true;
		}

		if ( Voice_IsRecording() && bToggle == false )
		{
			CL_SendVoicePacket( g_bUsingSteamVoice ? false : true );
			Voice_UserDesiresStop();
		}
		else if ( !Voice_IsRecording() && bToggle == true )
		{
			const char *pUncompressedFile = NULL;
			const char *pDecompressedFile = NULL;
			const char *pInputFile = NULL;

			if (voice_recordtofile.GetInt())
			{
				pUncompressedFile = "voice_micdata.wav";
				pDecompressedFile = "voice_decompressed.wav";
			}

			if (voice_inputfromfile.GetInt())
			{
				pInputFile = "voice_input.wav";
			}
			if ( !sv_allow_voice_from_file.GetBool() )
			{
				pInputFile = NULL;
			}

			Voice_RecordStart( pUncompressedFile, pDecompressedFile, pInputFile );
		}
#endif // NO_VOICE
	}
}
#endif // VOICE_VOX_ENABLE
	
#endif // SWDS

//-----------------------------------------------------------------------------
// Purpose: Wrapper for modelloader->Print() function call
//-----------------------------------------------------------------------------
CON_COMMAND( listmodels, "List loaded models." )
{
	modelloader->Print();
}

/*
==================
Host_IncrementCVar
==================
*/
CON_COMMAND_F( incrementvar, "Increment specified convar value.", FCVAR_DONTRECORD )
{
	if( args.ArgC() != 5 )
	{
		Warning( "Usage: incrementvar varName minValue maxValue delta\n" );
		return;
	}

	const char *varName = args[ 1 ];
	if( !varName )
	{
		ConDMsg( "Host_IncrementCVar_f without a varname\n" );
		return;
	}

	ConVar *var = ( ConVar * )g_pCVar->FindVar( varName );
	if( !var )
	{
		ConDMsg( "cvar \"%s\" not found\n", varName );
		return;
	}

	float currentValue = var->GetFloat();
	float startValue = atof( args[ 2 ] );
	float endValue = atof( args[ 3 ] );
	float delta = atof( args[ 4 ] );
	float newValue = currentValue + delta;
	if( newValue > endValue )
	{
		newValue = startValue;
	}
	else if ( newValue < startValue )
	{
		newValue = endValue;
	}

	// Conver incrementvar command to direct sets to avoid any problems with state in a demo loop.
	Cbuf_AddText( va("%s %f", varName, newValue) );

	ConDMsg( "%s = %f\n", var->GetName(), newValue );
}


//-----------------------------------------------------------------------------
// Host_MultiplyCVar_f
//-----------------------------------------------------------------------------
CON_COMMAND_F( multvar, "Multiply specified convar value.", FCVAR_DONTRECORD )
{
	if (( args.ArgC() != 5 ))
	{
		Warning( "Usage: multvar varName minValue maxValue factor\n" );
		return;
	}

	const char *varName = args[ 1 ];
	if( !varName )
	{
		ConDMsg( "multvar without a varname\n" );
		return;
	}

	ConVar *var = ( ConVar * )g_pCVar->FindVar( varName );
	if( !var )
	{
		ConDMsg( "cvar \"%s\" not found\n", varName );
		return;
	}

	float currentValue = var->GetFloat();
	float startValue = atof( args[ 2 ] );
	float endValue = atof( args[ 3 ] );
	float factor = atof( args[ 4 ] );
	float newValue = currentValue * factor;
	if( newValue > endValue )
	{
		newValue = endValue;
	}
	else if ( newValue < startValue )
	{
		newValue = startValue;
	}

	// Conver incrementvar command to direct sets to avoid any problems with state in a demo loop.
	Cbuf_AddText( va("%s %f", varName, newValue) );

	ConDMsg( "%s = %f\n", var->GetName(), newValue );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CON_COMMAND( dumpstringtables, "Print string tables to console." )
{
	SV_PrintStringTables();
#ifndef SWDS
	CL_PrintStringTables();
#endif
}

// Register shared commands
ConCommand quit("quit", Host_Quit_f, "Exit the engine.");
static ConCommand cmd_exit("exit", Host_Quit_f, "Exit the engine.");

#ifndef SWDS
#ifdef VOICE_OVER_IP
static ConCommand startvoicerecord("+voicerecord", Host_VoiceRecordStart_f);
static ConCommand endvoicerecord("-voicerecord", Host_VoiceRecordStop_f);
#ifdef VOICE_VOX_ENABLE
static ConCommand togglevoicerecord("voicerecord_toggle", Host_VoiceToggle_f);
#endif // VOICE_VOX_ENABLE
#endif // VOICE_OVER_IP

#endif // SWDS


#if defined( STAGING_ONLY )

// From Kyle: For the GC we added this so we could call it over and
//  over until we got the crash reporter fixed.

// Visual studio optimizes this away unless we disable optimizations.
#pragma optimize( "", off )

class PureCallBase
{
public:
	virtual void PureFunction() = 0;

	PureCallBase()
	{
		NonPureFunction();
	}

	void NonPureFunction()
	{
		PureFunction();
	}
};
 
class PureCallDerived : public PureCallBase
{
public:
	void PureFunction() OVERRIDE
	{
	}
};

//-----------------------------------------------------------------------------
// Purpose: Force various crashes. useful for testing minidumps.
//  crash : Write 0 to address 0.
//  crash sys_error : Call Sys_Error().
//  crash hang : Hang.
//  crash purecall : Call virtual function in ctor.
//-----------------------------------------------------------------------------
CON_COMMAND( crash, "[ sys_error | hang | purecall | segfault | minidump ]: Cause the engine to crash." )
{ 
	if ( cmd_source != src_command )
		return;

	CUtlString cmd( ( args.ArgC() > 1 ) ? args[ 1 ] : "" );

	if ( cmd == "hang" )
	{
		// Hang. Useful to test watchdog code.
		Msg( "Hanging... Watchdog time: %d.\n ", Plat_GetWatchdogTime() );
		for ( ;; )
		{
			Msg( "%d ", Plat_MSTime() );
			ThreadSleep( 5000 );
		}
	}
	else if ( cmd == "purecall" )
	{
		Msg( "Instantiating PureCallDerived_derived...\n" );
		PureCallDerived derived;
	}
	else if ( cmd == "sys_error" )
	{
		Msg( "Calling Sys_Error...\n" );
		Sys_Error( "%s: Sys_Error()!!!", __FUNCTION__ );
	}
	else if ( cmd == "minidump" )
	{
		Msg( "Forcing minidump. build_number: %d.\n", build_number() );
		SteamAPI_WriteMiniDump( 0, NULL, build_number() );
	}
	else
	{
		Msg( "Segfault...\n" );
		char *p = 0;
		*p = 0;
	}
}

#pragma optimize( "", on )

#endif // STAGING_ONLY

CON_COMMAND_F( flush, "Flush unlocked cache memory.", FCVAR_CHEAT )
{
#if !defined( SWDS )
	g_ClientDLL->InvalidateMdlCache();
#endif // SWDS
	serverGameDLL->InvalidateMdlCache();
	g_pDataCache->Flush( true );
}

CON_COMMAND_F( flush_locked, "Flush unlocked and locked cache memory.", FCVAR_CHEAT )
{
#if !defined( SWDS )
	g_ClientDLL->InvalidateMdlCache();
#endif // SWDS
	serverGameDLL->InvalidateMdlCache();
	g_pDataCache->Flush( false );
}

CON_COMMAND( cache_print, "cache_print [section]\nPrint out contents of cache memory." )
{
	const char *pszSection = NULL;
	if ( args.ArgC() == 2 )
	{
		pszSection = args[ 1 ];
	}
	g_pDataCache->OutputReport( DC_DETAIL_REPORT, pszSection );
}

CON_COMMAND( cache_print_lru, "cache_print_lru [section]\nPrint out contents of cache memory." )
{
	const char *pszSection = NULL;
	if ( args.ArgC() == 2 )
	{
		pszSection = args[ 1 ];
	}
	g_pDataCache->OutputReport( DC_DETAIL_REPORT_LRU, pszSection );
}

CON_COMMAND( cache_print_summary, "cache_print_summary [section]\nPrint out a summary contents of cache memory." )
{
	const char *pszSection = NULL;
	if ( args.ArgC() == 2 )
	{
		pszSection = args[ 1 ];
	}
	g_pDataCache->OutputReport( DC_SUMMARY_REPORT, pszSection );
}

CON_COMMAND( sv_dump_edicts, "Display a list of edicts allocated on the server." )
{
	if ( !sv.IsActive() )
		return;

	CUtlMap<CUtlString, int> classNameCountMap;
	classNameCountMap.SetLessFunc( UtlStringLessFunc );

	Msg( "\nCurrent server edicts:\n");
	for ( int i = 0; i < sv.num_edicts; ++i )
	{
		CUtlMap<CUtlString, int>::IndexType_t index = classNameCountMap.Find( sv.edicts[ i ].GetClassName() );
		if ( index == classNameCountMap.InvalidIndex() )
		{
			index = classNameCountMap.Insert( sv.edicts[ i ].GetClassName(), 0 );
		}

		classNameCountMap[ index ]++;
	}

	Msg( "Count Classname\n");
	FOR_EACH_MAP( classNameCountMap, i )
	{
		Msg("%5d %s\n", classNameCountMap[ i ], classNameCountMap.Key(i).String() );
	}
	Msg( "NumEdicts: %d\n", sv.num_edicts );
	Msg( "FreeEdicts: %d\n\n", sv.free_edicts );
}

// make valve_ds only?
CON_COMMAND_F( memory_list, "dump memory list (linux only)", FCVAR_CHEAT )
{
	DumpMemoryLog( 128 * 1024 );
}

// make valve_ds only?
CON_COMMAND_F( memory_status, "show memory stats (linux only)", FCVAR_CHEAT )
{
	DumpMemorySummary();
}

// make valve_ds only?
CON_COMMAND_F( memory_mark, "snapshot current allocation status", FCVAR_CHEAT )
{
	SetMemoryMark();
}
// make valve_ds only?
CON_COMMAND_F( memory_diff, "show memory stats relative to snapshot", FCVAR_CHEAT )
{
	DumpChangedMemory( 64 * 1024 );
}

//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
CON_COMMAND( namelockid, "Prevent name changes for this userID." )
{
	if ( args.ArgC() <= 2 )
	{
		ConMsg( "Usage:  namelockid < userid > < 0 | 1 >\n" );
		return;
	}

	CBaseClient	*pClient = NULL;

	int iIndex = Q_atoi( args[1] );
	if ( iIndex > 0 )
	{
		for ( int i = 0; i < sv.GetClientCount(); i++ )
		{
			pClient = static_cast< CBaseClient* >( sv.GetClient( i ) );

			if ( !pClient->IsConnected() )
				continue;

#if defined( REPLAY_ENABLED )
			if ( pClient->IsReplay() )
				continue;
#endif

			if ( pClient->IsHLTV() )
				continue;

			if ( pClient->GetUserID() == iIndex )
				break;

			pClient = NULL;
		}
	}

	if ( pClient )
	{
		pClient->SetPlayerNameLocked( ( Q_atoi( args[2] ) == 0 ) ? false : true );
	}
	else
	{
		ConMsg( "Player id \"%d\" not found.\n", iIndex );
	}
}

#if defined( STAGING_ONLY ) || defined( _DEBUG )
CON_COMMAND( fs_find, "Run virtual filesystem find" )
{
	if ( args.ArgC() != 3 )
	{
		ConMsg( "Usage:  fs_find wildcard pathid\n" );
		return;
	}

	const char *pWildcard = args.Arg(1);
	const char *pPathID = args.Arg(2);

	FileFindHandle_t findhandle;
	const char *pFile = NULL;
	size_t matches = 0;
	for ( pFile = g_pFullFileSystem->FindFirstEx( pWildcard, pPathID, &findhandle );
	      pFile;
	      pFile = g_pFullFileSystem->FindNext( findhandle ) )
	{
		ConMsg( "%s\n", pFile );
		matches++;
	}

	ConMsg( "  %u matching files/directories\n", matches );
}
#endif // defined( STAGING_ONLY ) || defined( _DEBUG )