//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/cl_main.c                                              $
// $Revision:: 4                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 8/10/01 6:00 PM                                                $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// client main loop
//

#include <ctype.h>
#include "client.h"

#ifdef GAMESPY
#include "nonport.h"
#include "goaceng.h"
#endif

#ifdef SIN_ARCADE
#include "arcade_comm.h"
#endif

cvar_t	*freelook;

cvar_t	*adr0;
cvar_t	*adr1;
cvar_t	*adr2;
cvar_t	*adr3;
cvar_t	*adr4;
cvar_t	*adr5;
cvar_t	*adr6;
cvar_t	*adr7;
cvar_t	*adr8;

cvar_t	*cl_stereo_separation;
cvar_t	*cl_stereo;

cvar_t	*rcon_client_password;
cvar_t	*rcon_address;

cvar_t	*cl_noskins;
cvar_t	*cl_autoskins;
cvar_t	*cl_footsteps;
cvar_t	*cl_timeout;
cvar_t	*cl_predict;
cvar_t	*cl_maxfps;
cvar_t	*cl_gun;

#ifdef SPLIT_NETWORK_FRAME
cvar_t	*cl_async;
cvar_t	*net_maxfps;
cvar_t	*r_maxfps;
#endif	// SPLIT_NETWORK_FRAME

cvar_t	*cl_sleep; /* FS */
cvar_t	*cl_skiplogon; /* FS */
cvar_t	*cl_autorepeat_allkeys; /* FS: So I can autorepeat whatever I want, hoss. */

cvar_t	*cl_add_particles;
cvar_t	*cl_add_lights;
cvar_t	*cl_add_entities;
cvar_t	*cl_add_blend;
cvar_t	*cl_add_triangles;
cvar_t	*cl_add_detailmodels;
cvar_t	*cl_add_bulletholes;
cvar_t	*cl_add_glows;
cvar_t	*precache;
cvar_t	*cl_forcecinematic;
int		cl_total=0;
int		cl_current=0;

cvar_t	*cl_shownet;
cvar_t	*cl_showmiss;
cvar_t	*cl_showclamp;

cvar_t	*cl_paused;
cvar_t	*cl_timedemo;

cvar_t	*lookspring;
cvar_t	*lookstrafe;
cvar_t	*sensitivity;

cvar_t	*m_pitch;
cvar_t	*m_yaw;
cvar_t	*m_forward;
cvar_t	*m_side;

cvar_t	*cl_lightlevel;

//
// userinfo
//
cvar_t	*info_password;
cvar_t	*name;
cvar_t	*skin;
cvar_t	*rate;
cvar_t	*msg;
cvar_t	*hand;

cvar_t	*cl_speechdelay;
cvar_t	*cl_nospeech;
cvar_t	*cl_oldnoclip;
cvar_t	*cl_oldladders;
cvar_t	*zoomscale;
cvar_t	*cl_dumpdemo;
cvar_t	*cl_document;
cvar_t	*model;
// 2015 code
cvar_t	*bikeskin;
cvar_t	*bikemodel;

//***
cvar_t	*dt0;
cvar_t	*dt1;
cvar_t	*dt2;
cvar_t	*dt3;
cvar_t	*dt4;
cvar_t	*dt5;
cvar_t	*dtprint;
//***

/* Knightmare- whether to adjust fov for wide aspect ratio */
cvar_t	*cl_widescreen_fov;

static cvar_t	*cl_master_server_ip;
static cvar_t	*cl_master_server_port;
static cvar_t	*cl_master_server_queries;
static cvar_t	*cl_master_server_timeout;
static cvar_t	*cl_master_server_retries;
static cvar_t	*cl_master_server_optout;

client_static_t	cls;
client_state_t	cl;

centity_t		cl_entities[MAX_EDICTS];

entity_state_t	cl_parse_entities[MAX_PARSE_ENTITIES];

extern	cvar_t *allow_download;
extern	cvar_t *allow_download_players;
extern	cvar_t *allow_download_models;
extern	cvar_t *allow_download_sounds;
extern	cvar_t *allow_download_maps;

#ifdef GAMESPY
static	GServerList serverlist = NULL;
static void GameSpy_Async_Think(void);
static void ListCallBack(GServerList lst, int msg, void *instance, void *param1, void *param2);
static void CL_Gspystop_f (void);
       void CL_PingNetServers_f (void);
#else
void CL_ServerListThink (void) {}
void CL_PingNetServers_f (void) {}
void InitGamespy (void) {}
void CL_RefreshServerList (void) {}
#endif

#ifdef __DJGPP__
void Sys_Memory_Stats_f (void); /* FS: Added */
#endif

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


/*
====================
CL_WriteDemoMessage

Dumps the current net message, prefixed by the length
====================
*/
void CL_WriteDemoMessage (void)
{
	int		len, swlen;

	// the first eight bytes are just packet sequencing stuff
	len = net_message.cursize-8;
	swlen = LittleLong(len);
	fwrite (&swlen, 4, 1, cls.demofile);
	fwrite (net_message.data+8,	len, 1, cls.demofile);
}


/*
====================
CL_Stop_f

stop recording a demo
====================
*/
void CL_Stop_f (void)
{
	int		len;

	if (!cls.demorecording)
	{
		Com_Printf ("Not recording a demo.\n");
		return;
	}

// finish up
	len = -1;
	fwrite (&len, 4, 1, cls.demofile);
	fclose (cls.demofile);
	cls.demofile = NULL;
	cls.demorecording = false;
	Com_Printf ("Stopped demo.\n");
}

/*
=====================
CL_RecordConsole
=====================
*/

static void CL_RecordConsole(console3d_t *p, sizebuf_t *buf)
{
	console_state_t	nullconstate;
	console_state_t clcon;
	char	buffer[MAX_BUFFER_LENGTH], buffer2[MAX_BUFFER_LENGTH];
	int		l,x,i,j;
	char	*line;
	int		start=0;
	int		end=0;

	memset(&clcon, 0, sizeof(clcon));
	memset(&nullconstate, 0, sizeof(nullconstate));

	clcon.number = p->console_number;
	strcpy(clcon.console_name, p->console_name);
	clcon.name_update_time = -1;

	if (p->scrollcon)
		clcon.spawnflags |= 2;
	if (p->menucon)
		clcon.spawnflags |= 4;

	clcon.create_time = -1;
	clcon.layout_update_time = -1;
	if (p->menu_file[0])
		clcon.menufile_update_time = -1;
	clcon.red   = p->r;
	clcon.green = p->g;
	clcon.blue  = p->b;
	clcon.alpha = p->a;
	strcpy(clcon.layout,p->LayoutPtr);
	clcon.virtual_width = p->virtual_width;
	clcon.virtual_height = p->virtual_height;
	clcon.fraction = p->fraction;
	clcon.rows = p->rows;
	clcon.cols = p->columns;
	clcon.linepos = p->key_linepos;
	clcon.cmdline[0] = 0;
	clcon.menu_level = p->current_menu_level;
	clcon.sel_menu_item = p->sel_menu_item;
	clcon.focus = p->focus;
	clcon.consoleactive = p->active;
	clcon.menuactive = p->drawmenu;

	// Store the current state of the buffer.
	memset(buffer,0,sizeof(buffer));
	memset(buffer2,0,sizeof(buffer2));

	// We only need to save the last 1024 characters of the console.
	buffer[p->con.linewidth] = 0;
	for (l = p->con.current - p->con.totallines + 1 ; l <= p->con.current ; l++)
	{
		line = p->con.text + (l%p->con.totallines)*p->con.linewidth;
		strncpy (buffer, line, p->con.linewidth);

		for (x=p->con.linewidth-1 ; x>=0 ; x--)
		{
			if (buffer[x] == ' ')
				buffer[x] = 0;
			else
				break;
		}

		buffer[x+1] = '\n';

		if (buffer[0])
		{
			char *str = &buffer[0];
			char *bufptr = &buffer2[0];
			while (*str)
			{
				if ( (end+1)%MAX_BUFFER_LENGTH == (start) )
				{
					start = (start + 1)%MAX_BUFFER_LENGTH;
				}
				bufptr[end]=*str;
				end = (end+1) % MAX_BUFFER_LENGTH;
				str++;
			}
		}
	}

	// Re-order buffer so it is linear
	for (i=start,j=0;i<end;i=i+1%MAX_BUFFER_LENGTH,j++)
		buffer[j] = buffer2[i];

	MSG_WriteByte (buf, svc_spawnconbaseline);
	MSG_WriteDeltaConsoleForDemo (&nullconstate, &clcon, buf, true, buffer2);
}

static void CL_RecordSurface(csurface_t *surf, sizebuf_t *buf)
{
	char temp[256];

	surface_state_t nullsurfstate;
	surface_state_t clsurf;

	memset(&clsurf, 0, sizeof(clsurf));
	memset(&nullsurfstate, 0, sizeof(nullsurfstate));

	nullsurfstate.magnitude = 1;

	strcpy(temp,surf->groupname);
	clsurf.name = &temp[0];
	clsurf.number = surf->groupnumber;
	clsurf.trans_state = surf->trans_state;
	clsurf.trans_mag = surf->trans_mag;
	clsurf.trans_angle = surf->trans_angle;
	clsurf.translucence = surf->translucence;
	clsurf.magnitude = surf->magnitude;
	clsurf.frequency = surf->frequency;
	clsurf.damage_frame = surf->animation_frame;

	MSG_WriteByte (buf, svc_spawnsurfbaseline);
	MSG_WriteDeltaSurface (&nullsurfstate, &clsurf, buf, true);
}

/*
====================
CL_Record_f

record <demoname>

Begins recording a demo from the current position
====================
*/
void CL_Record_f (void)
{
	char	name[MAX_OSPATH];
	char	buf_data[MAX_MSGLEN];
	sizebuf_t	buf;
	int		i;
	int		len;
	entity_state_t	*ent;
	entity_state_t	nullstate;
	console3d_t *p;

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("record <demoname>\n");
		return;
	}

	if (cls.demorecording)
	{
		Com_Printf ("Already recording.\n");
		return;
	}

	if (cls.state != ca_active)
	{
		Com_Printf ("You must be in a level to record.\n");
		return;
	}

	//
	// open the demo file
	//
	Com_sprintf (name, sizeof(name), "%s/demos/%s.dm2", FS_Gamedir(), Cmd_Argv(1));

	Com_Printf ("recording to %s.\n", name);
	FS_CreatePath (name);
	cls.demofile = fopen (name, "wb");
	if (!cls.demofile)
	{
		Com_Printf ("ERROR: couldn't open.\n");
		return;
	}
	cls.demorecording = true;

	// don't start saving messages until a non-delta compressed message is received
	cls.demowaiting = true;

	//
	// write out messages to hold the startup information
	//
	SZ_Init (&buf,(byte *) buf_data, sizeof(buf_data));

	// send the serverdata
	MSG_WriteByte (&buf, svc_serverdata);
	MSG_WriteLong (&buf, PROTOCOL_VERSION);
	MSG_WriteLong (&buf, 0x10000 + cl.servercount);
	MSG_WriteByte (&buf, 1);	// demos are allways attract loops
	MSG_WriteString (&buf, cl.gamedir);
	MSG_WriteShort (&buf, cl.playernum);

	MSG_WriteString (&buf, cl.configstrings[CS_NAME]);

	// configstrings
	for (i=0 ; i<MAX_CONFIGSTRINGS ; i++)
	{
		if (cl.configstrings[i][0])
		{
			if (buf.cursize + strlen (cl.configstrings[i]) + 128 > buf.maxsize)
			{	// write it out
				len = LittleLong (buf.cursize);
				fwrite (&len, 4, 1, cls.demofile);
				fwrite (buf.data, buf.cursize, 1, cls.demofile);
				buf.cursize = 0;
			}

			MSG_WriteByte (&buf, svc_configstring);
			MSG_WriteShort (&buf, i);
			MSG_WriteString (&buf, cl.configstrings[i]);
		}
	}

	MSG_WriteByte (&buf, svc_stufftext);
	MSG_WriteString (&buf, "loadmap\n");

	// baselines
	memset(&nullstate, 0, sizeof(nullstate));

	// Set up the default baseline values
	nullstate.scale = 1.0;
	nullstate.alpha = 1.0;
	nullstate.renderfx = RF_FRAMELERP;

	for (i=0; i<MAX_EDICTS ; i++)
	{
		ent = &cl_entities[i].baseline;
		
		if (!ent->modelindex && !ent->sound && !ent->effects)
			continue;

		if (buf.cursize + 128 > buf.maxsize)
		{	// write it out
			len = LittleLong (buf.cursize);
			fwrite (&len, 4, 1, cls.demofile);
			fwrite (buf.data, buf.cursize, 1, cls.demofile);
			buf.cursize = 0;
		}

		MSG_WriteByte (&buf, svc_spawnbaseline);
		MSG_WriteDeltaEntity (&nullstate, &cl_entities[i].baseline, &buf, true, true);
	}

	// Console baselines
	for (p=active_consoles ; p ; p=p->next)
	{
		if (p->console_number)
		{
			// write out the message up to this point because
			// consoles eat up a lot of space.
			len = LittleLong (buf.cursize);
			fwrite (&len, 4, 1, cls.demofile);
			fwrite (buf.data, buf.cursize, 1, cls.demofile);
			buf.cursize = 0;
			CL_RecordConsole(p,&buf);
		}
	}

	// Surface Baselines
	{
		csurface_t *surf;
		surf = CM_Surfinfos();
		for (i=0;i<CM_NumSurfinfos();i++)
		{
			if (!isdigit(surf[i].groupname[0]) || ( surf[i].flags & SURF_DAMAGE ))
			{
				if (buf.cursize + 64 > buf.maxsize)
				{
					len = LittleLong (buf.cursize);
					fwrite (&len, 4, 1, cls.demofile);
					fwrite (buf.data, buf.cursize, 1, cls.demofile);
					buf.cursize = 0;
				}
				CL_RecordSurface(&surf[i],&buf);
			}
		}
	}

	MSG_WriteByte (&buf, svc_stufftext);
	MSG_WriteString (&buf, "precache\n");

	// write it to the demo file

	len = LittleLong (buf.cursize);
	fwrite (&len, 4, 1, cls.demofile);
	fwrite (buf.data, buf.cursize, 1, cls.demofile);

	// the rest of the demo file will be individual frames
}


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

/*
===================
Cmd_ForwardToServer

adds the current command line as a clc_stringcmd to the client message.
things like godmode, noclip, etc, are commands directed to the server,
so when they are typed in at the console, they will need to be forwarded.
===================
*/
void Cmd_ForwardToServer (void)
{
	const char  *cmd;

	cmd = Cmd_Argv(0);
	if (cls.state <= ca_connected || *cmd == '-' || *cmd == '+')
	{
		Com_Printf ("Unknown command \"%s\"\n", cmd);
		return;
	}

	MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
	SZ_Print (&cls.netchan.message, cmd);
	if (Cmd_Argc() > 1)
	{
		SZ_Print (&cls.netchan.message, " ");
		SZ_Print (&cls.netchan.message, Cmd_Args());
	}

	cls.forcePacket = true;
}

/*
void CL_Setenv_f(void)
{
	int argc = Cmd_Argc();

	if ( argc > 2 )
	{
		char buffer[1000];
		int i;

		strcpy( buffer, Cmd_Argv(1) );
		strcat( buffer, "=" );

		for ( i = 2; i < argc; i++ )
		{
			strcat( buffer, Cmd_Argv( i ) );
			strcat( buffer, " " );
		}

		putenv( buffer );
	}
	else if ( argc == 2 )
	{
		char *env = getenv( Cmd_Argv(1) );

		if ( env )
		{
			Com_Printf( "%s=%s\n", Cmd_Argv(1), env );
		}
		else
		{
			Com_Printf( "%s undefined\n", Cmd_Argv(1));
		}
	}
}
*/

/*
===========
CL_Remark_f
===========
*/
static void CL_Remark_f ( void )
{
	// Does nothing
	return;
}

/*
==================
CL_ForwardToServer_f
==================
*/
static void CL_ForwardToServer_f (void)
{
	if (cls.state != ca_connected && cls.state != ca_active)
	{
		Com_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0));
		return;
	}

	// don't forward the first argument
	if (Cmd_Argc() > 1)
	{
		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
		SZ_Print (&cls.netchan.message, Cmd_Args());
		cls.forcePacket = true;
	}
}


/*
==================
CL_Pause_f
==================
*/
void CL_Pause_f (void)
{
	// never pause in multiplayer
	if (Cvar_VariableValue ("maxclients") > 1 || !Com_ServerState ())
	{
		Cvar_SetValue ("paused", 0);
		return;
	}

	Cvar_SetValue ("paused", !cl_paused->value);
}

/*
==================
CL_Quit_f
==================
*/
void CL_Quit_f (void)
{
	CL_Disconnect ();
	Com_Quit ();
}

/*
================
CL_Drop

Called after an ERR_DROP was thrown
================
*/
void CL_Drop (void)
{
	SCR_EndLoadingPlaque ();
	cls.intermission_active = false;

	if (cls.state == ca_uninitialized)
		return;
	if (cls.state == ca_disconnected)
		return;

	CL_Disconnect ();

	// drop loading plaque unless this is the initial game start
	if (cls.disable_servercount != -1)
		SCR_EndLoadingPlaque ();	// get rid of loading plaque
}


/*
=======================
CL_SendConnectPacket

We have gotten a challenge from the server, so try and
connect.
======================
*/
static void CL_SendConnectPacket (void)
{
	netadr_t	adr;
	int		port;

	if (!NET_StringToAdr (cls.servername, &adr))
	{
		Com_Printf ("Bad server address\n");
		cls.connect_time = 0;
		return;
	}
	if (adr.port == 0)
		adr.port = BigShort (PORT_SERVER);

	port = Cvar_VariableValue ("qport");
	userinfo_modified = false;

	Netchan_OutOfBandPrint (NS_CLIENT, adr, "connect %i %i %i \"%s\"\n",
		PROTOCOL_VERSION, port, cls.challenge, Cvar_Userinfo() );
}

/*
=================
CL_CheckForResend

Resend a connect message if the last one has timed out
=================
*/
static void CL_CheckForResend (void)
{
	netadr_t	adr;

	// if the local server is running and we aren't
	// then connect
	if (cls.state == ca_disconnected && Com_ServerState() )
	{
		cls.state = ca_connecting;
		strncpy (cls.servername, "localhost", sizeof(cls.servername)-1);
		// we don't need a challenge on the localhost
		CL_SendConnectPacket ();
		return;
//		cls.connect_time = -99999;	// CL_CheckForResend() will fire immediately
	}

	// resend if we haven't gotten a reply yet
	if (cls.state != ca_connecting)
		return;

	if (cls.realtime - cls.connect_time < 3000)
		return;

	if (!NET_StringToAdr (cls.servername, &adr))
	{
		Com_Printf ("Bad server address\n");
		cls.state = ca_disconnected;
		return;
	}
	if (adr.port == 0)
		adr.port = BigShort (PORT_SERVER);

	cls.connect_time = cls.realtime;	// for retransmit requests

	Com_Printf ("Connecting to %s...\n", cls.servername);

	Netchan_OutOfBandPrint (NS_CLIENT, adr, "getchallenge\n");
}


/*
================
CL_Connect_f

================
*/
static void CL_Connect_f (void)
{
	const char	*server;

	if (Cmd_Argc() != 2)
	{
		Com_Printf ("usage: connect <server>\n");
		return;
	}

	if (Com_ServerState ())
	{	// if running a local server, kill it and reissue
		SV_Shutdown ("Server quit\n", false);
	}
	else
	{
		CL_Disconnect ();
	}

	server = Cmd_Argv (1);

	NET_Config (true);		// allow remote

	CL_Disconnect ();

	cls.state = ca_connecting;
	strncpy (cls.servername, server, sizeof(cls.servername)-1);
	cls.connect_time = -99999;	// CL_CheckForResend() will fire immediately
}


/*
=====================
CL_Rcon_f

  Send the rest of the command line over as
  an unconnected command.
=====================
*/
static void CL_Rcon_f (void)
{
	char	message[1024];
	int		i;
	netadr_t	to;

	if (!rcon_client_password->string)
	{
		Com_Printf ("You must set 'rcon_password' before\n"
					"issuing an rcon command.\n");
		return;
	}

	message[0] = (char)255;
	message[1] = (char)255;
	message[2] = (char)255;
	message[3] = (char)255;
	message[4] = 0;

	NET_Config (true); // allow remote

	strcat (message, "rcon ");

	strcat (message, rcon_client_password->string);
	strcat (message, " ");

	for (i=1 ; i<Cmd_Argc() ; i++)
	{
		strcat (message, Cmd_Argv(i));
		strcat (message, " ");
	}

	if (cls.state >= ca_connected)
		to = cls.netchan.remote_address;
	else
	{
		if (!strlen(rcon_address->string))
		{
			Com_Printf ("You must either be connected,\n"
						"or set the 'rcon_address' cvar\n"
						"to issue rcon commands\n");

			return;
		}
		NET_StringToAdr (rcon_address->string, &to);
		if (to.port == 0)
			to.port = BigShort (PORT_SERVER);
	}

	NET_SendPacket (NS_CLIENT, (int)strlen(message)+1, message, to);
}


/*
=====================
CL_ClearState

=====================
*/
void CL_ClearState (void)
{
	int		i;
	entity_state_t	*ent;

	S_StopAllSounds ();
	CL_ClearEffects ();
	CL_ClearTEnts ();

// wipe the entire cl structure
	memset(&cl, 0, sizeof(cl));
	memset(&cl_entities, 0, sizeof(cl_entities));

	// Set up the default baseline values
	for (i=0; i<MAX_EDICTS ; i++)
	{
		ent = &cl_entities[i].baseline;
		ent->scale = 1.0;
		ent->alpha = 1.0;
		ent->renderfx = RF_FRAMELERP;
	}

	SZ_Clear (&cls.netchan.message);

}

/*
=====================
CL_Disconnect

Goes from a connected state to full screen console state
Sends a disconnect message to the server
This is also called on Com_Error, so it shouldn't cause any errors
=====================
*/
void CL_Disconnect (void)
{
	byte	final[32];

	if (cls.state == ca_disconnected)
		return;

	if (cl.timedemo_start) /* FS: Was cl_timedemo->value. */
	{
		int	time;

		time = Sys_Milliseconds () - cl.timedemo_start;
		if (time > 0)
			Com_Printf ("%i frames, %3.1f seconds: %3.1f fps\n", cl.timedemo_frames,
			time/1000.0, cl.timedemo_frames*1000.0 / time);
	}

	VectorClear (cl.refdef.blend);
	re.CinematicSetPalette(NULL);

	CONSOLE_Exit(cls.mainconsole);

	cls.connect_time = 0;

	SCR_StopCinematic ();

	if (cls.demorecording)
		CL_Stop_f ();

	// send a disconnect message to the server
	final[0] = clc_stringcmd;
	strcpy ((char *)final+1, "disconnect");
	Netchan_Transmit (&cls.netchan, (int)strlen((char *)final), final);
	Netchan_Transmit (&cls.netchan, (int)strlen((char *)final), final);
	Netchan_Transmit (&cls.netchan, (int)strlen((char *)final), final);

	CL_ClearState ();

	// stop download
	if (cls.download) {
		fclose(cls.download);
		cls.download = NULL;
	}

	cls.state = ca_disconnected;
}

void CL_Disconnect_f (void)
{
	Com_Error (ERR_DROP, "Disconnected from server");
}


/*
====================
CL_Packet_f

packet <destination> <contents>

Contents allows \n escape character
====================
*/
#if 0 /* this is dangerous to leave in */
static void CL_Packet_f (void)
{
	char	send[2048];
	int		i, l;
	const char	*in;
	char *out;
	netadr_t	adr;

	if (Cmd_Argc() != 3)
	{
		Com_Printf ("packet <destination> <contents>\n");
		return;
	}

	NET_Config (true);		// allow remote

	if (!NET_StringToAdr (Cmd_Argv(1), &adr))
	{
		Com_Printf ("Bad address\n");
		return;
	}
	if (!adr.port)
		adr.port = BigShort (PORT_SERVER);

	in = Cmd_Argv(2);
	out = send+4;
	send[0] = send[1] = send[2] = send[3] = (char)0xff;

	l = (int)strlen (in);
	for (i=0 ; i<l ; i++)
	{
		if (in[i] == '\\' && in[i+1] == 'n')
		{
			*out++ = '\n';
			i++;
		}
		else
			*out++ = in[i];
	}
	*out = 0;

	NET_SendPacket (NS_CLIENT, out-send, send, adr);
}
#endif

/*
=================
CL_Changing_f

Just sent as a hint to the client that they should
drop to full console
=================
*/
static void CL_Changing_f (void)
{
	//if we are downloading, we don't change!  This so we don't suddenly stop downloading a map
	if (cls.download)
		return;

	SCR_BeginLoadingPlaque ();
	cls.state = ca_connected;	// not active anymore, but not disconnected
	Com_Printf ("\nChanging map...\n");
}


/*
=================
CL_Reconnect_f

The server is changing levels
=================
*/
static void CL_Reconnect_f (void)
{
   //if we are downloading, we don't change!  This so we don't suddenly stop downloading a map
	if (cls.download)
		return;

	S_StopAllSounds ();
	if (cls.state == ca_connected) {
		Com_Printf ("reconnecting...\n");
		cls.state = ca_connected;
		MSG_WriteChar (&cls.netchan.message, clc_stringcmd);
		MSG_WriteString (&cls.netchan.message, "new");
		cls.forcePacket = true;
		return;
	}

	if (*cls.servername) {
		if (cls.state >= ca_connected) {
			CL_Disconnect();
			cls.connect_time = cls.realtime - 1500;
		} else
			cls.connect_time = -99999; // fire immediately

		cls.state = ca_connecting;
		Com_Printf ("reconnecting...\n");
	}
}

/*
=================
CL_ParseStatusMessage

Handle a reply from a ping
=================
*/
static void CL_ParseStatusMessage (void)
{
	const char	*s;

	s = MSG_ReadString(&net_message);

	Com_Printf ("%s\n", s);

	MENU_AddToServerList (net_from, s);
}


/*
=================
CL_PingServers_f
=================
*/
void CL_PingServers_f (void)
{
	int			i;
	netadr_t	adr;
	char		name[32];
	const char		*adrstring;
	cvar_t		*noudp;
	cvar_t		*noipx;

	NET_Config (true);		// allow remote

	// send a broadcast packet
	Com_Printf ("pinging broadcast...\n");

	noudp = Cvar_Get ("noudp", "0", CVAR_NOSET);
	if (!noudp->value)
	{
		adr.type = NA_BROADCAST;
		adr.port = BigShort(PORT_SERVER);
		Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION));
	}
	noipx = Cvar_Get ("noipx", "0", CVAR_NOSET);
	if (!noipx->value)
	{
		adr.type = NA_BROADCAST_IPX;
		adr.port = BigShort(PORT_SERVER);
		Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION));
	}
	// send a packet to each address book entry
	for (i=0 ; i<16 ; i++)
	{
		Com_sprintf (name, sizeof(name), "adr%i", i);
		adrstring = Cvar_VariableString (name);
		if (!adrstring || !adrstring[0])
			continue;

		Com_Printf ("pinging %s...\n", adrstring);
		if (!NET_StringToAdr (adrstring, &adr))
		{
			Com_Printf ("Bad address: %s\n", adrstring);
			continue;
		}
		if (!adr.port)
			adr.port = BigShort(PORT_SERVER);
		Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION));
	}
}


/*
=================
CL_Skins_f

Load or download any custom player skins and models
=================
*/
static void CL_Skins_f (void)
{
	int		i;

	for (i=0 ; i<MAX_CLIENTS ; i++)
	{
		if (!cl.configstrings[CS_PLAYERSKINS+i][0])
			continue;
		Com_Printf ("client %i: %s\n", i, cl.configstrings[CS_PLAYERSKINS+i]);
		if(cl.configstrings[CS_BIKESKINS+i][0])
			Com_Printf ("client %i: %s\n", i, cl.configstrings[CS_BIKESKINS+i]);
		SCR_UpdateScreen ();
		Sys_SendKeyEvents ();	// pump message loop
		CL_ParseClientinfo (i);
		if(cl.configstrings[CS_BIKESKINS+i][0])
			CL_ParseBikeinfo (i);
	}
}


/*
=================
CL_ConnectionlessPacket

Responses to broadcasts, etc
=================
*/
static void CL_ConnectionlessPacket (void)
{
	const char	*s;
	const char	*c;

	MSG_BeginReading (&net_message);
	MSG_ReadLong (&net_message);	// skip the -1

	s = MSG_ReadStringLine (&net_message);

	Cmd_TokenizeString (s, false);

	c = Cmd_Argv(0);

	Com_Printf ("%s: %s\n", NET_AdrToString (net_from), c);

	// server connection
	if (!strcmp(c, "client_connect"))
	{
		if (cls.state == ca_connected)
		{
			Com_Printf ("Dup connect received.  Ignored.\n");
			return;
		}
		Netchan_Setup (NS_CLIENT, &cls.netchan, net_from, cls.quakePort);
		MSG_WriteChar (&cls.netchan.message, clc_stringcmd);
		MSG_WriteString (&cls.netchan.message, "new");	
		cls.state = ca_connected;
		cls.forcePacket = true;
		return;
	}

	// server responding to a status broadcast
	if (!strcmp(c, "info"))
	{
		CL_ParseStatusMessage ();
		return;
	}

	// remote command from gui front end
	if (!strcmp(c, "cmd"))
	{
		if (!NET_IsLocalAddress(net_from))
		{
			Com_Printf ("Command packet from remote host.  Ignored.\n");
			return;
		}
		Sys_AppActivate ();
		s = MSG_ReadString (&net_message);
		Cbuf_AddText (s);
		Cbuf_AddText ("\n");
		return;
	}
	// print command from somewhere
	if (!strcmp(c, "print"))
	{
		s = MSG_ReadString (&net_message);
		Com_Printf ("%s", s);
		return;
	}

	// ping from somewhere
	if (!strcmp(c, "ping"))
	{
		Netchan_OutOfBandPrint (NS_CLIENT, net_from, "ack");
		return;
	}

	// challenge from the server we are connecting to
	if (!strcmp(c, "challenge"))
	{
		cls.challenge = atoi(Cmd_Argv(1));
		CL_SendConnectPacket ();
		return;
	}

	// echo request from server
	if (!strcmp(c, "echo"))
	{
		Netchan_OutOfBandPrint (NS_CLIENT, net_from, "%s", Cmd_Argv(1) );
		return;
	}

	Com_Printf ("Unknown command.\n");
}

/*
=================
CL_DumpPackets

A vain attempt to help bad TCP stacks that cause problems
when they overflow
=================
*/
#if 0
static void CL_DumpPackets (void)
{
	while (NET_GetPacket (NS_CLIENT, &net_from, &net_message))
	{
		Com_Printf ("dumnping a packet\n");
	}
}
#endif

/*
=================
CL_ReadPackets
=================
*/
void CL_ReadPackets (void)
{
	while (NET_GetPacket (NS_CLIENT, &net_from, &net_message))
	{
//		Com_Printf ("packet\n");
		//
		// remote command packet
		//
		if (*(int *)net_message.data == -1)
		{
			CL_ConnectionlessPacket ();
			continue;
		}

		if (cls.state == ca_disconnected || cls.state == ca_connecting)
			continue;		// dump it if not connected

		if (net_message.cursize < 8)
		{
			Com_Printf ("%s: Runt packet\n",NET_AdrToString(net_from));
			continue;
		}

		//
		// packet from server
		//
		if (!NET_CompareAdr (net_from, cls.netchan.remote_address))
		{
			Com_DPrintf (DEVELOPER_MSG_NET, "%s:sequenced packet without connection\n", NET_AdrToString(net_from));
			continue;
		}
		if (!Netchan_Process(&cls.netchan, &net_message))
			continue;		// wasn't accepted for some reason
		CL_ParseServerMessage ();
	}

	//
	// check timeout
	//
	if (cls.state >= ca_connected && !Com_ServerState() &&
	    cls.realtime - cls.netchan.last_received > cl_timeout->value*1000)
	{
		if (++cl.timeoutcount > 5)	// timeoutcount saves debugger
		{
			Com_Printf ("\nServer connection timed out.\n");
			CL_Disconnect ();
			return;
		}
	}
	else
		cl.timeoutcount = 0;
}


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

/*
==============
CL_Userinfo_f
==============
*/
static void CL_Userinfo_f (void)
{
	Com_Printf ("User info settings:\n");
	Info_Print (Cvar_Userinfo());
}

static void CL_Snd_Shutdown_f (void) /* FS */
{
#ifdef __MSDOS__
	S_StopAllSounds(); /* FS: Clear GUS Buffer */
#endif
	S_Shutdown ();
	Com_Printf("\nSound shutdown.\n");
}

/*
=================
CL_Snd_Restart_f

Restart the sound subsystem so it can pick up
new parameters and flush all sounds
=================
*/
void CL_Snd_Restart_f (void)
{
#ifdef __MSDOS__
	S_StopAllSounds(); /* FS: Clear GUS Buffer */
#endif
	S_Shutdown ();
	S_Init ();
	CL_RegisterSounds( false );
}

int precache_check; // for autodownload of precache items
int precache_spawncount;
int precache_tex;
int precache_model_skin;

byte *precache_model; // used for skin checking in alias models

#define PLAYER_MULT 5

//FIXME this needs to be changed for SIN
#define TEXTURE_CNT (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
void CL_LoadMap_f (void);

static int precache_skin = false;
static int precache_damskin = false;
static int precache_icon = false;

void CL_RequestNextDownload (void)
{
	char model		[MAX_QPATH];
	char skin		[MAX_QPATH];
	char damskin	[MAX_QPATH];
	char icon		[MAX_QPATH];
	char scratch	[MAX_QPATH];
	char string		[1024];

	// MAP
	if ( precache_check == CS_MODELS ) 
	{
		precache_check = CS_PLAYERSKINS;
		// check for map file
		if ( allow_download_maps->value )
		{
			if ( !CL_CheckOrDownloadFile( cl.configstrings[CS_MODELS+1] ) )
				return; // started a download
		}
	}

	// SKINS
	if ( allow_download_players->value )
	{
		if ( precache_check >= CS_PLAYERSKINS && precache_check < CS_PLAYERSKINS+MAX_CLIENTS )
		{
			while ( precache_check < CS_PLAYERSKINS+MAX_CLIENTS &&
					cl.configstrings[precache_check][0])
			{
				const char *token;

				strncpy( string, cl.configstrings[precache_check], sizeof(string)-1 );

				// Name
				token = strtok( string, "\\" );

				// Model
				token = strtok( NULL, "\\" );
				COM_StripExtension( token, scratch );
				Com_sprintf( model, sizeof(model), "models/%s", scratch );

				// Skin
				token = strtok( NULL, "\\" );
				COM_StripExtension( token, scratch );
				Com_sprintf( skin, sizeof(skin), "%s/%s.tga",model, scratch );

				// Damage skin
				COM_StripExtension( skin, scratch );
				Com_sprintf( damskin, sizeof(damskin), "%s_dam.tga", scratch );

				// Icon
				Com_sprintf( icon, sizeof(icon), "%s_ico.tga", scratch );

				if ( !precache_skin )
				{
					if ( !CL_CheckOrDownloadFile( skin ) )
					{
						precache_skin = true;
						return; // started a download
					}
					precache_skin = true;
				}

				if ( !precache_damskin )
				{
					if ( !CL_CheckOrDownloadFile( damskin ) )
					{
						precache_damskin = true;
						return; // started a download
					}
					precache_damskin = true;
				}

				if ( !precache_icon )
				{
					if ( !CL_CheckOrDownloadFile( icon ) )
					{
						precache_icon = true;
						return; // started a download
					}
					precache_icon = true;
				}

				precache_check++;
				precache_skin = false;
				precache_damskin = false;
				precache_icon = false;
			}
		}
	}

	// Setup the rest of the client
	CL_RegisterSounds( true );
	CL_LoadMap_f();
	CL_PrepRefresh();
	cls.forcePacket = true;
}

static void CL_CalculateLoadBar( void )
{
	int i;

	cl_total = 5;

	for (i=1 ; i<MAX_MODELS && cl.configstrings[CS_MODELS+i][0] ; i++)
		cl_total++;
	for (i=1 ; i<MAX_IMAGES && cl.configstrings[CS_IMAGES+i][0] ; i++)
		cl_total++;

	cl_current = cl_total;
	cl_total   *= 2;
}

/*
=================
CL_Precache_f

The server will send this command right
before allowing the client into the server
=================
*/
static void CL_Precache_f (void)
{
	int i;

	if (cls.state != ca_connected)
	{
		return;
	}

	// Setup the intermission layout map for defaults
	CONSOLE_SetupDefaultIntermissionLayoutFile( cl.configstrings[CS_MODELS+1] );

	cl_total = 5;

	for (i=1 ; i<MAX_MODELS && cl.configstrings[CS_MODELS+i][0] ; i++)
		cl_total++;
	for (i=1 ; i<MAX_IMAGES && cl.configstrings[CS_IMAGES+i][0] ; i++)
		cl_total++;

	cl_total += 2 * MAX_SOUNDS;

	cl_current = cl_total;
	cl_total   *= 2;
}

#ifdef SIN_ARCADE
/*
=================
CL_Arcade_PlayerDead_f
=================
*/
void CL_Arcade_PlayerDead_f(void)
{
	if (!ARCADE_ComWriteByte(0x03))
		Com_Error( ERR_DROP, "Could not send lost player life to communications port" );
}

/*
=================
CL_Arcade_GameOver_f
=================
*/
void CL_Arcade_GameOver_f(void)
{
	if (!ARCADE_ComWriteByte(0x06))
		Com_Error( ERR_DROP, "Could not send end of game to communications port" );
}

/*
=================
CL_Arcade_PutByte_f
=================
*/
void CL_Arcade_PutByte_f(void)
{
	if (Cmd_Argc() < 2)
	{
		Com_Printf( "Usage: arcade_putbyte [value]\n" );
		return;
	}

	if (!ARCADE_ComWriteByte((byte) atoi(Cmd_Argv(1))))
		Com_Error(ERR_DROP, "Could not send data to communications port");
}

void CL_Arcade_CheckFirst_f(void)
{
	if (cl.frame.playerstate.stats[STAT_FIRSTPLACE] && !ARCADE_ComWriteByte(0x5))
		Com_Error(ERR_DROP, "Could not send data to communications port");
}
#endif

/*
=================
CL_LoadMap_f

The server will send this command after configstrings, so we can load the 
client side map. This needs to be done before we get the surface baselines
=================
*/
void CL_LoadMap_f (void)
{
	unsigned map_checksum;

	if (cls.state != ca_connected)
	{
		return;
	}

	CM_LoadMap (cl.configstrings[CS_MODELS+1], true, &map_checksum);

	if (map_checksum != atoi(cl.configstrings[CS_MAPCHECKSUM]))
		Com_Error (ERR_DROP, "Local map version differs from server.\n");

	cl.map_sequence_number = map_sequence_number;
	Com_DPrintf(DEVELOPER_MSG_NET, "Updating client sequence number to %i\n", map_sequence_number );

	MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
	MSG_WriteString (&cls.netchan.message, va("baselines %i 0\n", precache_spawncount ) );
}

static void CL_PreLoadMap_f( void )
{
	int i;

	if (cls.state != ca_connected)
	{
		return;
	}

	// Hack for demos to work
	if ( Cmd_Argc() < 2 )
	{
		unsigned map_checksum;

		CL_RegisterSounds( true );
		CM_LoadMap (cl.configstrings[CS_MODELS+1], true, &map_checksum);

		if (map_checksum != atoi(cl.configstrings[CS_MAPCHECKSUM]))
			Com_Error (ERR_DROP, "Local map version differs from server.\n");

		cl.map_sequence_number = map_sequence_number;
		Com_DPrintf(DEVELOPER_MSG_NET, "Updating client sequence number to %i\n", map_sequence_number );

		CL_PrepRefresh();
		return;
	}

	precache_check = CS_MODELS;
	precache_spawncount = atoi(Cmd_Argv(1));
	precache_model = 0;
	precache_model_skin = 0;

	// Setup the intermission layout map for defaults
	CONSOLE_SetupDefaultIntermissionLayoutFile( cl.configstrings[CS_MODELS+1] );

	cl_total = 5;

	for (i=1 ; i<MAX_MODELS && cl.configstrings[CS_MODELS+i][0] ; i++)
		cl_total++;
	for (i=1 ; i<MAX_IMAGES && cl.configstrings[CS_IMAGES+i][0] ; i++)
		cl_total++;

	cl_total += 2 * MAX_SOUNDS;

	cl_current	= cl_total;
	cl_total	*= 2;

	CL_RequestNextDownload();
}

/*
=================
CL_PrecacheDone_f

The server will send this command after the precache is done.  We will
load up a menu that will allow the user to press any key to enter the server.
=================
*/
static void CL_PrecacheDone_f (void)
{
	if (cls.state != ca_connected)
	{
		return;
	}

	precache_spawncount = atoi( Cmd_Argv( 1 ) );

#ifndef SIN_ARCADE
	if ( !developer->value && !scr_skip_loading->intValue ) /* FS */
	{
		CL_Pause_f();
		MENU_AnyKeyToContinue_f();
	}
	else
	{
		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
		MSG_WriteString (&cls.netchan.message, va("begin %i\n", precache_spawncount ) );
	}
#else
	CL_Begin_f();
	CONSOLE_ClearIntermissionData_f();
	CONSOLE_BeginLevel_f();
	if (cls.key_dest == key_console)
	{
		Con_ToggleConsole_f();
	}
#endif
}

/*
=================
CL_Begin_f

Sends a begin command to the server
=================
*/
void CL_Begin_f (void)
{
	if (cls.state != ca_connected)
	{
		return;
	}

	MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
	MSG_WriteString (&cls.netchan.message, va("begin %i\n", precache_spawncount ) );
}

static void CL_ArchiveRendererCVARS (void) /* FS: Don't loose our CVAR_ARCHIVE goodies */
{
	/* OpenGL */
	Cvar_Get("gl_particle_min_size", "2", CVAR_ARCHIVE);
	Cvar_Get("gl_particle_max_size", "40", CVAR_ARCHIVE);
	Cvar_Get("gl_particle_size", "40", CVAR_ARCHIVE);
	Cvar_Get("gl_particle_att_a", "0.01", CVAR_ARCHIVE);
	Cvar_Get("gl_particle_att_b", "0.0", CVAR_ARCHIVE);
	Cvar_Get("gl_particle_att_c", "0.01", CVAR_ARCHIVE);
	Cvar_Get("gl_modulate", "1", CVAR_ARCHIVE);
	Cvar_Get("gl_mode", "0", CVAR_ARCHIVE);
	Cvar_Get("gl_shadows", "0", CVAR_ARCHIVE);
	Cvar_Get("gl_shadows_stencil", "1", CVAR_ARCHIVE);
	Cvar_Get("gl_ztrick", "0", CVAR_ARCHIVE);
	Cvar_Get("gl_polyblend", "1", CVAR_ARCHIVE);
	Cvar_Get("gl_dosdriver", "gl.dxe", CVAR_ARCHIVE);
	Cvar_Get("gl_driver", "opengl32", CVAR_ARCHIVE);
	Cvar_Get("gl_texturemode", "GL_LINEAR_MIPMAP_NEAREST", CVAR_ARCHIVE);
	Cvar_Get("gl_texturealphamode", "default", CVAR_ARCHIVE);
	Cvar_Get("gl_texturesolidmode", "default", CVAR_ARCHIVE);
	Cvar_Get("gl_vertex_arrays", "0", CVAR_ARCHIVE);
	Cvar_Get("gl_skintexturemode", "GL_LINEAR_MIPMAP_NEAREST", CVAR_ARCHIVE);
	Cvar_Get("gl_ext_swapinterval", "1", CVAR_ARCHIVE);
	Cvar_Get("gl_ext_palettedtexture", "2", CVAR_ARCHIVE);
#ifdef __DJGPP__
	Cvar_Get("gl_ext_multitexture", "0", CVAR_ARCHIVE); /* FS: About a ~10fps loss on average with this on.  So let's set it to be off by default */
#else
	Cvar_Get("gl_ext_multitexture", "1", CVAR_ARCHIVE);
#endif
#ifdef __DJGPP__
	Cvar_Get("gl_ext_pointparameters", "0", CVAR_ARCHIVE); /* FS: Hardlocks when it's time to draw a particle in fxMesa 3*/
#else
	Cvar_Get("gl_ext_pointparameters", "1", CVAR_ARCHIVE);
#endif
	Cvar_Get("gl_ext_compiled_vertex_array", "1", CVAR_ARCHIVE);
	Cvar_Get("gl_swapinterval", "1", CVAR_ARCHIVE);
	Cvar_Get("gl_3dlabs_broken", "1", CVAR_ARCHIVE);
	Cvar_Get("gl_ignorehwgamma", "0", CVAR_ARCHIVE);
	Cvar_Get("gl_displayrefresh", "0", CVAR_ARCHIVE);

	/* Software */
	Cvar_Get("sw_allow_modex", "1", CVAR_ARCHIVE);
	Cvar_Get("sw_stipplealpha", "0", CVAR_ARCHIVE);
	Cvar_Get("sw_mode", "0", CVAR_ARCHIVE);
	Cvar_Get("r_8bpp", "0", CVAR_ARCHIVE);
	Cvar_Get("r_colorlighting", "0", CVAR_ARCHIVE);
	Cvar_Get("r_monolighting", "0", CVAR_ARCHIVE);

	/* Common */
	Cvar_Get("r_condist", "100000", CVAR_ARCHIVE);
	Cvar_Get("r_conoffset", "0.1", CVAR_ARCHIVE);
	Cvar_Get("r_conthresh", "100000", CVAR_ARCHIVE);
	Cvar_Get("r_detaildist", "450000", CVAR_ARCHIVE);
	Cvar_Get("r_detailthresh", "100000", CVAR_ARCHIVE);
	Cvar_Get("r_dynamic", "1", CVAR_ARCHIVE);
}

/*
=================
CL_InitLocal
=================
*/
static void CL_InitLocal (void)
{
	int i;

	cls.state = ca_disconnected;
	cls.realtime = Sys_Milliseconds ();

	CL_InitInput ();

	adr0 = Cvar_Get( "adr0", "", CVAR_ARCHIVE );
	adr1 = Cvar_Get( "adr1", "", CVAR_ARCHIVE );
	adr2 = Cvar_Get( "adr2", "", CVAR_ARCHIVE );
	adr3 = Cvar_Get( "adr3", "", CVAR_ARCHIVE );
	adr4 = Cvar_Get( "adr4", "", CVAR_ARCHIVE );
	adr5 = Cvar_Get( "adr5", "", CVAR_ARCHIVE );
	adr6 = Cvar_Get( "adr6", "", CVAR_ARCHIVE );
	adr7 = Cvar_Get( "adr7", "", CVAR_ARCHIVE );
	adr8 = Cvar_Get( "adr8", "", CVAR_ARCHIVE );

//
// register our variables
//
	cl_stereo_separation = Cvar_Get( "cl_stereo_separation", "0.4", CVAR_ARCHIVE );
	cl_stereo = Cvar_Get( "cl_stereo", "0", 0 );

	cl_add_blend = Cvar_Get ("cl_blend", "1", CVAR_ARCHIVE);
	cl_add_lights = Cvar_Get ("cl_lights", "1", CVAR_ARCHIVE);
	cl_add_particles = Cvar_Get ("cl_particles", "1", CVAR_ARCHIVE);
	cl_add_bulletholes = Cvar_Get ("cl_bulletholes", "1", CVAR_ARCHIVE);
	cl_add_glows = Cvar_Get ("cl_glows", "1", CVAR_ARCHIVE);
	cl_add_entities = Cvar_Get ("cl_entities", "1", 0);
	cl_gun = Cvar_Get ("cl_gun", "1", 0);
	cl_footsteps = Cvar_Get ("cl_footsteps", "1", 0);
	cl_noskins = Cvar_Get ("cl_noskins", "0", 0);
	cl_autoskins = Cvar_Get ("cl_autoskins", "0", 0);
	cl_predict = Cvar_Get ("cl_predict", "1", 0);
	cl_maxfps = Cvar_Get ("cl_maxfps", "90", CVAR_ARCHIVE); /* FS:  was 1000.  changed default to 90 to avoid 125hz bug. */

#ifdef SPLIT_NETWORK_FRAME
	cl_async = Cvar_Get ("cl_async", "1", CVAR_ARCHIVE);
	net_maxfps = Cvar_Get ("net_maxfps", "60", CVAR_ARCHIVE);
	r_maxfps = Cvar_Get ("r_maxfps", "125", CVAR_ARCHIVE);	
#endif /* SPLIT_NETWORK_FRAME */

	cl_sleep = Cvar_Get ("cl_sleep", "1", CVAR_ARCHIVE); /* FS */
	/* Knightmare- whether to adjust fov for wide aspect ratio */
	cl_widescreen_fov = Cvar_Get ("cl_widescreen_fov", "1", CVAR_ARCHIVE);
	cl_widescreen_fov->description = "Automatically adjusts FOV to aspect ratio.";

	cl_skiplogon = Cvar_Get ("cl_skiplogon", "0", CVAR_ARCHIVE);
	cl_skiplogon->description = "Skips the logon screen in the main menu.";

#ifdef GAMESPY /* FS: */
	cl_master_server_ip = Cvar_Get("cl_master_server_ip", CL_MASTER_ADDR, CVAR_ARCHIVE);
	cl_master_server_ip->description = "Master server IP to use with the gamespy browser.";
	cl_master_server_port = Cvar_Get("cl_master_server_port", CL_MASTER_PORT, CVAR_ARCHIVE);
	cl_master_server_port->description = "Master server port to use with the gamespy browser.";
	cl_master_server_queries = Cvar_Get("cl_master_server_queries", "10", CVAR_ARCHIVE);
	cl_master_server_queries->description = "Maximum number of query (ping) requests to use per loop with the gamespy browser.";
	cl_master_server_timeout = Cvar_Get("cl_master_server_timeout", "3000", CVAR_ARCHIVE);
	cl_master_server_timeout->description = "Timeout (in milliseconds) to give up on pinging a server.";
	cl_master_server_retries = Cvar_Get("cl_master_server_retries", "20", CVAR_ARCHIVE);
	cl_master_server_retries->description = "Number of retries to attempt for receiving the server list.  Formula is 50ms + 10ms for each retry.";
	cl_master_server_optout = Cvar_Get("cl_master_server_optout", "0", CVAR_ARCHIVE);
	cl_master_server_optout->description = "Opt-out of sending your Sin Username in GameSpy list requests.";
#endif

	cl_upspeed = Cvar_Get ("cl_upspeed", "200", 0);
	cl_forwardspeed = Cvar_Get ("cl_forwardspeed", "200", 0);
	cl_sidespeed = Cvar_Get ("cl_sidespeed", "320", 0);
	cl_add_triangles = Cvar_Get ("cl_tessellation", "1", CVAR_ARCHIVE);
	cl_add_detailmodels = Cvar_Get ("cl_detailmodels", "1", CVAR_ARCHIVE);
	cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", 0);
	cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "150", 0);
	cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0);

	cl_run = Cvar_Get ("cl_run", "0", CVAR_ARCHIVE);
	freelook = Cvar_Get( "freelook", "0", CVAR_ARCHIVE );
	lookspring = Cvar_Get ("lookspring", "0", CVAR_ARCHIVE);
	lookstrafe = Cvar_Get ("lookstrafe", "0", CVAR_ARCHIVE);
	sensitivity = Cvar_Get ("sensitivity", "3", CVAR_ARCHIVE);
	zoomscale = Cvar_Get ("zoomscale", ".1111", 0);
	m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE);
	m_yaw = Cvar_Get ("m_yaw", "0.022", 0);
	m_forward = Cvar_Get ("m_forward", "1", 0);
	m_side = Cvar_Get ("m_side", "1", 0);

	cl_shownet = Cvar_Get ("cl_shownet", "0", 0);
	cl_showmiss = Cvar_Get ("cl_showmiss", "0", 0);
	cl_showclamp = Cvar_Get ("showclamp", "0", 0);
	cl_timeout = Cvar_Get ("cl_timeout", "120", 0);
	cl_paused = Cvar_Get ("paused", "0", 0);
	cl_timedemo = Cvar_Get ("timedemo", "0", 0);

	rcon_client_password = Cvar_Get ("rcon_password", "", 0);
	rcon_address = Cvar_Get ("rcon_address", "", 0);

	cl_lightlevel = Cvar_Get ("r_lightlevel", "0", 0);

	//
	// userinfo
	//
	info_password = Cvar_Get ("password", "", CVAR_USERINFO);
	name = Cvar_Get ("name", "unnamed", CVAR_USERINFO | CVAR_ARCHIVE);
	skin  = Cvar_Get ("skin", "blade_base", CVAR_USERINFO | CVAR_ARCHIVE );
	model = Cvar_Get ("model", "pl_blade", CVAR_USERINFO | CVAR_ARCHIVE);
	bikeskin  = Cvar_Get ("bikeskin", "hover_02a_r", CVAR_USERINFO | CVAR_ARCHIVE );
	bikemodel = Cvar_Get ("bikemodel", "bike_prototype", CVAR_USERINFO | CVAR_ARCHIVE);

	// allows player to turn off auto weapon switching
	Cvar_Get ("weaponswitch", "1", CVAR_USERINFO | CVAR_ARCHIVE);

	// allow turning off of weapon selection overriding
	Cvar_Get ("weaponoverride", "1", CVAR_USERINFO | CVAR_ARCHIVE);

//***
	dt0 = Cvar_Get("dt0", "0", 0);
	dt1 = Cvar_Get("dt1", "0", 0);
	dt2 = Cvar_Get("dt2", "0", 0);
	dt3 = Cvar_Get("dt3", "0", 0);
	dt4 = Cvar_Get("dt4", "0", 0);
	dt5 = Cvar_Get("dt5", "0", 0);
	dtprint = Cvar_Get("dtprint", "0", 0);
//***
	rate = Cvar_Get ("rate", "5000", CVAR_USERINFO | CVAR_ARCHIVE);	// FIXME
	msg = Cvar_Get ("msg", "1", CVAR_USERINFO | CVAR_ARCHIVE);
	hand = Cvar_Get ("hand", "0", CVAR_USERINFO | CVAR_ARCHIVE);

	if ( hand->value < 0 || hand->value > 2 )
		Cvar_SetValue( "hand", 0 );

	Cvar_Get ("fov", "90", CVAR_USERINFO | CVAR_ARCHIVE);
	cl_oldnoclip = Cvar_Get ("cl_oldnoclip", "0", CVAR_USERINFO | CVAR_ARCHIVE);
	cl_oldladders = Cvar_Get ("cl_oldladders", "0", CVAR_USERINFO | CVAR_ARCHIVE);
	cl_dumpdemo = Cvar_Get ("dumpdemo", "0", 0);
	cl_document = Cvar_Get ("document", "0", 0);
	precache = Cvar_Get("cl_precache", "1" , 0);
	cl_forcecinematic = Cvar_Get("cl_forcecinematic", "1" , CVAR_ARCHIVE);
	cl_forcecinematic->description= "Force video mode changes for AVI files.";

	cl_nospeech = Cvar_Get("cl_nospeech", "0" , CVAR_ARCHIVE);
	cl_speechdelay = Cvar_Get("cl_speechdelay", "1000" , CVAR_ARCHIVE);

	cl_autorepeat_allkeys = Cvar_Get("cl_autorepeat_allkeys", "0", CVAR_ARCHIVE); /* FS: Because I want to autorepeat whatever I want, hoss */
	cl_autorepeat_allkeys->description = "Allow to autorepeat any key, not just Backspace, Pause, PgUp, and PgDn keys.";

	//
	// register our commands
	//
	Cmd_AddCommand ("cmd", CL_ForwardToServer_f);
	Cmd_AddCommand ("pause", CL_Pause_f);
	Cmd_AddCommand ("pingservers", CL_PingServers_f);
	Cmd_AddCommand ("skins", CL_Skins_f);

	Cmd_AddCommand ("userinfo", CL_Userinfo_f);
	Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f);
	Cmd_AddCommand ("snd_shutdown", CL_Snd_Shutdown_f); /* FS */

	Cmd_AddCommand ("changing", CL_Changing_f);
	Cmd_AddCommand ("disconnect", CL_Disconnect_f);

	Cmd_AddCommand ("record", CL_Record_f);
	Cmd_AddCommand ("stop", CL_Stop_f);

	Cmd_AddCommand ("quit", CL_Quit_f);

	Cmd_AddCommand ("connect", CL_Connect_f);
	Cmd_AddCommand ("reconnect", CL_Reconnect_f);

	Cmd_AddCommand ("rcon", CL_Rcon_f);
#if 0 /* this is dangerous to leave in */
	Cmd_AddCommand ("packet", CL_Packet_f);
#endif

//	Cmd_AddCommand ("setenv", CL_Setenv_f );

	Cmd_AddCommand ("precache", CL_Precache_f);

	Cmd_AddCommand ("download", CL_Download_f);

#ifdef SIN_ARCADE
	Cmd_AddCommand ("playerdead", CL_Arcade_PlayerDead_f);
	Cmd_AddCommand ("gameover", CL_Arcade_GameOver_f);
	Cmd_AddCommand ("arcade_putbyte", CL_Arcade_PutByte_f );
	Cmd_AddCommand ("checkfirst", CL_Arcade_CheckFirst_f );
#endif

	Cmd_AddCommand ("rem", CL_Remark_f);
	Cmd_AddCommand ("printcommands", CL_PrintDefCommands);
	Cmd_AddCommand ("definfo", CL_DefInfo);
	Cmd_AddCommand ("precache_done", CL_PrecacheDone_f );
	Cmd_AddCommand ("loadmap", CL_PreLoadMap_f );
	Cmd_AddCommand ("cl_begin", CL_Begin_f );

	Cmd_AddCommand ("writeconfig", CL_WriteConfig_f); /* FS */

#ifdef GAMESPY /* FS */
	Cmd_AddCommand ("slist2", CL_PingNetServers_f);
	Cmd_AddCommand ("gspystop", CL_Gspystop_f);
#endif

	//
	// forward to server commands
	//
	Cmd_AddCommand ("wave", NULL);
	Cmd_AddCommand ("inven", NULL);
	Cmd_AddCommand ("kill", NULL);
	Cmd_AddCommand ("use", NULL);
	Cmd_AddCommand ("drop", NULL);
	Cmd_AddCommand ("say", NULL);
	Cmd_AddCommand ("say_team", NULL);
	Cmd_AddCommand ("info", NULL);
	Cmd_AddCommand ("prog", NULL);
	Cmd_AddCommand ("invuse", NULL);
	Cmd_AddCommand ("invprev", NULL);
	Cmd_AddCommand ("invnext", NULL);
	Cmd_AddCommand ("invdrop", NULL);
	Cmd_AddCommand ("weapnext", NULL);
	Cmd_AddCommand ("weapprev", NULL);
	Cmd_AddCommand ("gameversion", NULL);

	Cmd_AddCommand ("viewspawn", NULL);
	Cmd_AddCommand ("viewmodel", NULL);
	Cmd_AddCommand ("viewnext", NULL);
	Cmd_AddCommand ("viewprev", NULL);
	Cmd_AddCommand ("viewnextanim", NULL);
	Cmd_AddCommand ("viewprevanim", NULL);
	Cmd_AddCommand ("viewanimate", NULL);
	Cmd_AddCommand ("viewscaleup", NULL);
	Cmd_AddCommand ("viewscaledown", NULL);
	Cmd_AddCommand ("viewscale", NULL);
	Cmd_AddCommand ("viewyaw", NULL);
	Cmd_AddCommand ("viewthingnext", NULL);
	Cmd_AddCommand ("viewthingprev", NULL);
	Cmd_AddCommand ("viewattach", NULL);
	Cmd_AddCommand ("viewdetach", NULL);
	Cmd_AddCommand ("viewdelete", NULL);
	Cmd_AddCommand ("viewdetachall", NULL);
	Cmd_AddCommand ("viewbonegroup", NULL);
	Cmd_AddCommand ("viewbonenum", NULL);
	Cmd_AddCommand ("viewboneangles", NULL);
	Cmd_AddCommand ("vieworigin", NULL);
	Cmd_AddCommand ("viewnextskin", NULL);
	Cmd_AddCommand ("viewprevskin", NULL);
	Cmd_AddCommand ("viewautoanimate", NULL);
// added spritegun commands
	Cmd_AddCommand ("spg_save", NULL);
	Cmd_AddCommand ("spg_load", NULL);
	Cmd_AddCommand ("spg_file", NULL);
	Cmd_AddCommand ("spg_glow", NULL);
	Cmd_AddCommand ("spg_hitwater", NULL);
	Cmd_AddCommand ("spg_next", NULL);
	Cmd_AddCommand ("spg_previous", NULL);
	Cmd_AddCommand ("spg_delete", NULL);
	Cmd_AddCommand ("spg_scale", NULL);
	Cmd_AddCommand ("spg_alpha", NULL);
	Cmd_AddCommand ("spg_frame", NULL);
	Cmd_AddCommand ("spg_nextframe", NULL);
	Cmd_AddCommand ("spg_prevframe", NULL);
	Cmd_AddCommand ("spg_movesize", NULL);
	Cmd_AddCommand ("spg_moveright", NULL);
	Cmd_AddCommand ("spg_moveleft", NULL);
	Cmd_AddCommand ("spg_moveup", NULL);
	Cmd_AddCommand ("spg_movedown", NULL);
	Cmd_AddCommand ("spg_moveforward", NULL);
	Cmd_AddCommand ("spg_moveback", NULL);
	Cmd_AddCommand ("spg_setpitch", NULL);
	Cmd_AddCommand ("spg_setyaw", NULL);
	Cmd_AddCommand ("spg_setroll", NULL);
	Cmd_AddCommand ("spg_rotsize", NULL);
	Cmd_AddCommand ("spg_pitchup", NULL);
	Cmd_AddCommand ("spg_pitchdown", NULL);
	Cmd_AddCommand ("spg_yawright", NULL);
	Cmd_AddCommand ("spg_yawleft", NULL);
	Cmd_AddCommand ("spg_rollright", NULL);
	Cmd_AddCommand ("spg_rollleft", NULL);
	Cmd_AddCommand ("spg_get", NULL);
	Cmd_AddCommand ("spg_give", NULL);
	Cmd_AddCommand ("spg_clear", NULL);
	Cmd_AddCommand ("spg_flip", NULL);
	Cmd_AddCommand ("spg_animspeed", NULL);
	Cmd_AddCommand ("spg_animdelay", NULL);
	Cmd_AddCommand ("spg_pitchspeed", NULL);
	Cmd_AddCommand ("spg_yawspeed", NULL);
	Cmd_AddCommand ("spg_rollspeed", NULL);
	Cmd_AddCommand ("spg_rotdelay", NULL);
	Cmd_AddCommand ("spg_blinkspeed", NULL);
	Cmd_AddCommand ("spg_blinkdelay", NULL);
	Cmd_AddCommand ("spg_targetname", NULL);
	Cmd_AddCommand ("spg_triggereffect", NULL);

	Cmd_AddCommand ("consolecmd", NULL);
	Cmd_AddCommand ("consolevar", NULL);
	Cmd_AddCommand ("consolepos", NULL);
	Cmd_AddCommand ("consoleneg", NULL);
	Cmd_AddCommand ("consoleret", NULL);
	Cmd_AddCommand ("consolemenu", NULL);
	Cmd_AddCommand ("exitcon", NULL);
	Cmd_AddCommand ("deflist", SINMDL_ModelList);
	Cmd_AddCommand ("aliasdump", Alias_Dump);
	Cmd_AddCommand ("weaponuse", NULL);
	Cmd_AddCommand ("taunt", NULL);

#ifdef __DJGPP__
	Cmd_AddCommand ("memstats", Sys_Memory_Stats_f); /* FS: Added to keep track of memory usage in DOS */
#endif

	// process the global defs
	for (i = 0; i < 10; i++) {
		CL_CMDS_ProcessFile( va( "global/global%i.scr", i ) );
	}
	CL_ResetMusic();

	CL_ArchiveRendererCVARS(); /* FS */
}


/*
===============
CL_WriteConfiguration

Writes key bindings and archived cvars to config.cfg
===============
*/
void CL_WriteConfiguration (const char *cfgName)
{
	FILE	*f;
	char	path[MAX_QPATH];

	if (cls.state == ca_uninitialized)
		return;

	if(!(cfgName) || (cfgName[0] == 0)) /* FS: Sanity check */
		Com_sprintf (path, sizeof(path),"%s/config.cfg", FS_Playerdir());
	else
		Com_sprintf (path, sizeof(path),"%s/%s.cfg",FS_Playerdir(), cfgName);

	f = fopen (path, "w");
	if (!f)
	{
		Com_Printf ("Couldn't write %s.cfg.\n", cfgName);
		return;
	}

	fprintf (f, "rem generated by Sin, do not modify\n");
	fprintf (f, "rem use autoexec.cfg for adding custom settings.\n");
	Key_WriteBindings (f);
	fclose (f);

	Cvar_WriteVariables (path);
}

void CL_WriteConfig_f (void)
{
	char cfgName[MAX_QPATH];

	if ((Cmd_Argc() == 1) || (Cmd_Argc() == 2))
	{
		if (Cmd_Argc() == 1)
			COM_StripExtension(cfg_default->string, cfgName);
		else
			strncpy (cfgName, Cmd_Argv(1), sizeof(cfgName));

		CL_WriteConfiguration (cfgName);
		Com_Printf ("Wrote config file %s/%s.cfg.\n", FS_Playerdir(), cfgName);
	}
	else
		Com_Printf ("Usage: writeconfig <name>\n");
}

/*
==================
CL_FixCvarCheats

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

typedef struct
{
	const char	*name;
	const char	*value;
	cvar_t	*var;
} cheatvar_t;

cheatvar_t	cheatvars[] = {
	{"timescale", "1"},
	{"timedemo", "0"},
	{"r_drawworld", "1"},
//	{"cl_testlights", "0"},
	{"r_draworder", "0"},
	{"r_fullbright", "0"},
	{"r_drawflat", "0"},
	{"paused", "0"},
	{"fixedtime", "0"},
	{"gl_showtris", "0"},
	{"gl_bbox", "0"},
	{"gl_lightmap", "0"},
	{"gl_saturatelighting", "0"},
	{NULL, NULL}
};

int		numcheatvars;

static void CL_FixCvarCheats (void)
{
	int			i;
	cheatvar_t	*var;

	if ( !strcmp(cl.configstrings[CS_MAXCLIENTS], "1") 
		|| !cl.configstrings[CS_MAXCLIENTS][0] || cl.attractloop) /* FS: Allow timedemo from deathmatch demos to work */
		return;		// single player can cheat

	// find all the cvars if we haven't done it yet
	if (!numcheatvars)
	{
		while (cheatvars[numcheatvars].name)
		{
			cheatvars[numcheatvars].var = Cvar_Get (cheatvars[numcheatvars].name,
					cheatvars[numcheatvars].value, 0);
			numcheatvars++;
		}
	}

	// make sure they are all set to the proper values
	for (i=0, var = cheatvars ; i<numcheatvars ; i++, var++)
	{
		if ( strcmp (var->var->string, var->value) )
		{
			Cvar_Set (var->name, var->value);
		}
	}
}

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

static void CL_GetVersionReplyString (char *versionStr, int verLen) /* FS: !version reply */
{
	if (!versionStr || verLen <= 0)
	{
		return;
	}

#ifdef __DJGPP__
	if(!Q_stricmp(Cvar_VariableString("vid_ref"), "gl"))
		Com_sprintf(versionStr, verLen, "SiN with 3DFX Voodoo %4.2f %s %s %s", VERSION, CPUSTRING, __DATE__, BUILDSTRING);
	else
#endif
	{
		Com_sprintf(versionStr, verLen, "SiN %4.2f %s %s %s", VERSION, CPUSTRING, __DATE__, BUILDSTRING);
	}
}

qboolean CL_IsADemoWithTimeDemo(void) // FS: For my sanity
{
	/* FS: 4 is ss_demo, see server.h */
	if(cl_timedemo->value && Com_ServerState() == 4) /* FS: Timedemo and a Demo Playing */
	{
		return true;
	}

	return false;
}

#ifdef SPLIT_NETWORK_FRAME	/* Knightmare 10/4/12- added async support */
void CL_RefreshInputs (void)
{
	// fetch results from server
	CL_ReadPackets ();

	// get new key events
	Sys_SendKeyEvents ();

	// allow mice or other external controllers to add commands
	IN_Commands ();

	// process console commands
	Cbuf_Execute ();

	// fix any cheating cvars
	CL_FixCvarCheats ();

	// fetch results from server
//	CL_ReadPackets ();

	// Update usercmd state
	if (cls.state > ca_connecting)
	{
		CL_RefreshCmd ();
	}
	else
	{
		CL_RefreshMove ();
	}
}

void CL_SendCommand_Async (void)
{
	// send intentions now
	CL_SendCmd_Async ();

	// resend a connection request if necessary
	CL_CheckForResend ();
}


/*
==================
CL_Frame_Async
==================
*/
#define FRAMETIME_MAX	0.5
void CL_Frame_Async (int msec)
{
	static int	packetDelta = 0;
	static int	renderDelta = 0;
	static int	miscDelta = 0;
	static int	lasttimecalled;
	qboolean	packetFrame = true;
	qboolean	renderFrame = true;
	qboolean	miscFrame = true;

	// Don't allow setting maxfps too low or too high
	if (net_maxfps->value < 10)
	{
		Cvar_SetValue ("net_maxfps", 10);
	}

	if (net_maxfps->value > 90)
	{
		Cvar_SetValue("net_maxfps", 90);
	}

	if (r_maxfps->value < 10)
	{
		Cvar_SetValue("r_maxfps", 10);
	}

	if (r_maxfps->value > 1000)
	{
		Cvar_SetValue("r_maxfps", 1000);
	}

	packetDelta += msec;
	renderDelta += msec;
	miscDelta += msec;

	// decide the simulation time
	cls.frametime = packetDelta * 0.001f;
	cls.netFrametime = packetDelta * 0.001f;
	cls.renderFrametime = renderDelta * 0.001f;
	cl.time += msec;
	cls.realtime = curtime;

	// don't get too far ahead
//	if (cls.frametime > FRAMETIME_MAX)
//		cls.frametime = FRAMETIME_MAX;
	if (cls.netFrametime > FRAMETIME_MAX)
	{
		cls.netFrametime = FRAMETIME_MAX;
	}

	if (cls.renderFrametime > FRAMETIME_MAX)
	{
		cls.renderFrametime = FRAMETIME_MAX;
	}

	// if in the debugger last frame, don't timeout
	if (msec > 5000)
	{
		cls.netchan.last_received = Sys_Milliseconds ();
	}

	if (!CL_IsADemoWithTimeDemo()) /* FS: timedemo only in demos, please */
	{
		if (cls.state == ca_connected && packetDelta < 100)
		{
			packetFrame = false;			// don't flood packets out while connecting
		}
		
		if (packetDelta < 1000.0 / net_maxfps->value)
		{
			packetFrame = false;
		}
//		else if (cls.netFrametime = cls.renderFrametime)
//		{
//			packetFrame = false;
//		}

		if (renderDelta < 1000.0 / r_maxfps->value)
		{
			renderFrame  = false;			// framerate is too high
		}

		// misc stuff only needs 10 fps
		if (miscDelta < 1000.0 / 10)
		{
			miscFrame = false;
		}
		if (!packetFrame && !renderFrame && !cls.forcePacket /*&& !userinfo_modified*/)
		{	// sleep while waiting to render next frame
			if (cl_sleep->value != 0)
			{
				int	testTime = min( (1000.0 / r_maxfps->value - renderDelta), (1000.0 / net_maxfps->value - packetDelta) );
				if (testTime > 1)
					Sys_SleepMilliseconds (1);
			}
			return;
		}
	}

	if (packetFrame || renderFrame)
	{
		CL_RefreshInputs ();
	}

	if (cls.forcePacket || userinfo_modified)
	{
		packetFrame = true;
		cls.forcePacket = false;
	}

	if (packetFrame)
	{
		packetDelta = 0;
		CL_SendCommand_Async();
	}

	if (renderFrame)
	{
		renderDelta = 0;

		if (miscFrame)
		{
			miscDelta = 0;

			if (cls.spamTime && (cls.spamTime < cls.realtime)) /* FS: !version reply */
			{
				char versionStr[256];
				char getVersion[256];

				CL_GetVersionReplyString(getVersion, sizeof(getVersion));
				Com_sprintf(versionStr, sizeof(versionStr), "say %s\n", getVersion);

				Cbuf_AddText(versionStr);
				cls.lastSpamTime = cls.realtime;
				cls.spamTime = 0.0f;
			}

			// let the mouse activate or deactivate
			IN_Frame ();

			// allow rendering DLL change
			VID_CheckChanges ();

		}
		// predict all unacknowledged movements
		CL_PredictMovement ();

		// To the surface thing
		CL_ProcessSurfaces ();

		if (!cl.refresh_prepped && cls.state == ca_active)
		{
			CL_CalculateLoadBar();
			CL_PrepRefresh ();
		}

		// update the screen
		if (host_speeds->value)
		{
			time_before_ref = Sys_Milliseconds ();
		}

		SCR_UpdateScreen ();

		if (host_speeds->value)
		{
			time_after_ref = Sys_Milliseconds ();
		}

		// update audio
		S_Update (cl.refdef.vieworg, cl.v_forward, cl.v_right, cl.v_up);

		if (miscFrame)
		{
			CDAudio_Update();
		}

		// advance local effects for next frame
		CL_ServerListThink();
		SCR_RunCinematic ();
		SCR_RunConsole ();

		cls.framecount++;

		if (log_stats->value)
		{
			if (cls.state == ca_active)
			{
				if (!lasttimecalled)
				{
					lasttimecalled = Sys_Milliseconds();

					if (log_stats_file)
					{
						fprintf( log_stats_file, "0\n" );
					}
				}
				else
				{
					int now = Sys_Milliseconds();

					if (log_stats_file)
					{
						fprintf( log_stats_file, "%d\n", now - lasttimecalled );
					}

					lasttimecalled = now;
				}
			}
		}

		if ( cl_document->value )
		{
			static int lasttime=-1;
			int inttime;

			inttime = cls.realtime / (cl_document->value*1000);
			if ( inttime != lasttime)
			{
				lasttime = inttime;
				Cbuf_AddText("screenshot" );
			}
		}

		if ((cl_dumpdemo->value) && (Com_ServerState() == 4)) /* FS: Removed demoplaying CVAR.  4 == ss_demo in server.h */
		{
			Cbuf_AddText ( "screenshot" );
		}

		CL_UpdateMusic();
	}
}
#endif /* SPLIT_NETWORK_FRAME */  /* end Knightmare */

/*
==================
CL_SendCommand

==================
*/
void CL_SendCommand (void)
{
	// get new key events
	Sys_SendKeyEvents ();

	// allow mice or other external controllers to add commands
	IN_Commands ();

	// process console commands
	Cbuf_Execute ();

	// fix any cheating cvars
	CL_FixCvarCheats ();

	// send intentions now
	CL_SendCmd ();

	// resend a connection request if necessary
	CL_CheckForResend ();
}


/*
==================
CL_Frame

==================
*/
void CL_Frame (int msec)
{
	static int	extratime;
	static int	lasttimecalled;

	if (dedicated->value)
	{
		return;
	}

#ifdef GAMESPY
	GameSpy_Async_Think(); /* FS: Update the query loop if we're pinging some servers */
#endif

#ifdef SPLIT_NETWORK_FRAME
	if (cl_async->value && !CL_IsADemoWithTimeDemo()) /* FS: timedemo only in demos, please */
	{
		CL_Frame_Async (msec);
		return;
	}
#endif /* SPLIT_NETWORK_FRAME */

	extratime += msec;
	if (!CL_IsADemoWithTimeDemo()) /* FS: timedemo only in demos, please */
	{
		if (cls.state == ca_connected && extratime < 100)
		{
			return;			// don't flood packets out while connecting
		}
		if (extratime < 1000/cl_maxfps->value)
		{
			return;			// framerate is too high
		}
	}

	// let the mouse activate or deactivate
	IN_Frame ();

	// decide the simulation time
	cls.frametime = extratime/1000.0;
	cl.time += extratime;
	cls.realtime = curtime;

	extratime = 0;
	if (cls.frametime > (1.0 / 5))
		cls.frametime = (1.0 / 5);

	cls.netFrametime = cls.frametime;
	cls.renderFrametime = cls.frametime;

	// if in the debugger last frame, don't timeout
	if (msec > 5000)
		cls.netchan.last_received = Sys_Milliseconds ();

	// fetch results from server
	CL_ReadPackets ();

	// send a new command message to the server
	CL_SendCommand ();

	if (cls.spamTime && (cls.spamTime < cls.realtime)) /* FS: !version reply */
	{
		char versionStr[256];
		char getVersion[256];

		CL_GetVersionReplyString(getVersion, sizeof(getVersion));
		Com_sprintf(versionStr, sizeof(versionStr), "say %s\n", getVersion);

		Cbuf_AddText(versionStr);
		cls.lastSpamTime = cls.realtime;
		cls.spamTime = 0;
	}

	// predict all unacknowledged movements
	CL_PredictMovement ();

	// To the surface thing
	CL_ProcessSurfaces ();

	// allow rendering DLL change
	VID_CheckChanges ();
	if (!cl.refresh_prepped && cls.state == ca_active)
	{
		CL_CalculateLoadBar();
		CL_PrepRefresh ();
	}

	// update the screen
	if (host_speeds->value)
	{
		time_before_ref = Sys_Milliseconds ();
	}

	SCR_UpdateScreen ();

	if (host_speeds->value)
	{
		time_after_ref = Sys_Milliseconds ();
	}

	// update audio
	S_Update (cl.refdef.vieworg, cl.v_forward, cl.v_right, cl.v_up);

	CDAudio_Update();

	// advance local effects for next frame
	CL_ServerListThink();
	SCR_RunCinematic ();
	SCR_RunConsole ();

	cls.framecount++;

	if ( log_stats->value )
	{
		if ( cls.state == ca_active )
		{
			if ( !lasttimecalled )
			{
				lasttimecalled = Sys_Milliseconds();

				if ( log_stats_file )
				{
					fprintf( log_stats_file, "0\n" );
				}
			}
			else
			{
				int now = Sys_Milliseconds();

				if ( log_stats_file )
				{
					fprintf( log_stats_file, "%d\n", now - lasttimecalled );
				}

				lasttimecalled = now;
			}
		}
	}

	if ( cl_document->value )
	{
		static int lasttime=-1;
		int inttime;

		inttime = cls.realtime / (cl_document->value*1000);
		if ( inttime != lasttime)
		{
			lasttime = inttime;
			Cbuf_AddText("screenshot" );
		}
	}

	if ((cl_dumpdemo->value) && (Com_ServerState() == 4)) /* FS: Removed demoplaying CVAR.  4 == ss_demo in server.h */
	{
		Cbuf_AddText ( "screenshot" );
	}

	CL_UpdateMusic();
}


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

/*
====================
CL_Init
====================
*/
void CL_Init (void)
{
	if (dedicated->value)
		return;		// nothing running on the client

	// all archived variables will now be loaded

	Con_Init ();

#ifdef SIN_ARCADE
	ARCADE_SetupCommunications();
#endif

	VID_Init ();
	S_Init ();	// sound must be initialized after window is created

	V_Init ();

	net_message.data = net_message_buffer;
	net_message.maxsize = sizeof(net_message_buffer);

	SCR_Init ();

	CDAudio_Init ();
	CL_InitLocal ();
	CON_Init();
	IN_Init ();
	FS_ExecAutoexec ();
	Cbuf_Execute ();
	InitGamespy(); /* FS: Added */
}


/*
===============
CL_Shutdown

FIXME: this is a callback from Sys_Quit and Com_Error.  It would be better
to run quit through here before the final handoff to the sys code.
===============
*/
void CL_Shutdown(void)
{
	static qboolean isdown = false;
	char cfgName[MAX_QPATH];

	if (isdown)	{
		Com_DPrintf(DEVELOPER_MSG_GENERIC_VERBOSE, "recursive shutdown\n");
		return;
	}
	isdown = true;

	COM_StripExtension(cfg_default->string, cfgName);
	CL_WriteConfiguration(cfgName);

	CDAudio_Shutdown ();
	S_Shutdown();
	IN_Shutdown ();
	VID_Shutdown();

#ifdef SIN_ARCADE
	ARCADE_CloseCommunications();
#endif
}

#ifdef GAMESPY
static void CL_Gamespy_Check_Error(GServerList lst, int error)
{
	if (error != GE_NOERROR) /* FS: Grab the error code */
	{
		Com_Printf("\x02GameSpy Error: ");
		Com_Printf("%s.\n", ServerListErrorDesc(lst, error));
		if(cls.state == ca_disconnected)
			NET_Config(false);
	}
}

static void GameSpy_Async_Think(void)
{
	int error;

	if(!serverlist)
		return;

	if(ServerListState(serverlist) == sl_idle && cls.gamespyupdate)
	{
		cls.gamespyupdate = 0;

		ServerListClear(serverlist);
		ServerListFree(serverlist);
		serverlist = NULL; /* FS: This is on purpose so future ctrl+c's won't try to close empty serverlists */
		if(cls.state == ca_disconnected)
			NET_Config(false);
	}
	else
	{
		error = ServerListThink(serverlist);
		CL_Gamespy_Check_Error(serverlist, error);
	}
}

static void CL_Gspystop_f (void)
{
	if(serverlist != NULL && cls.gamespyupdate) /* FS: Immediately abort gspy scans */
	{
		Com_Printf("\x02Server scan aborted!\n");
		ServerListHalt(serverlist);
	}
}

void ListCallBack(GServerList serverlist, int msg, void *instance, void *param1, void *param2)
{
	GServer server;
	if (msg == LIST_PROGRESS)
	{
		server = (GServer)param1;
		Com_Printf ("%s:%d [%d] %s %d/%d %s\n",ServerGetAddress(server),ServerGetQueryPort(server), ServerGetPing(server),ServerGetStringValue(server, "hostname","(NONE)"), ServerGetIntValue(server,"numplayers",0), ServerGetIntValue(server,"maxplayers",0), ServerGetStringValue(server,"mapname","(NO MAP)"));
	}
}

void CL_PingNetServers_f (void)
{
	char secret_key[7];
	int error = 0;
	int allocatedSockets;

	if(cls.gamespyupdate)
	{
		Com_Printf("Error: Already querying the GameSpy Master!\n");
		return;
	}

	NET_Config(true); /* FS: Retrieve a MOTD.  This is reset again later if we error out, abort, or if the server completes as long as we are disconnected */

	//set the secret key, in a semi-obfuscated manner
	secret_key[0] = 'I';
	secret_key[1] = 'j';
	secret_key[2] = '1';
	secret_key[3] = 'u';
	secret_key[4] = 'A';
	secret_key[5] = 'B';
	secret_key[6] = '\0';

	cls.gamespystarttime = (int)Sys_Milliseconds();
	allocatedSockets = bound(cl_master_server_queries->intValue, 5, 40);

	serverlist = ServerListNew(GAME_NAME,GAME_NAME,secret_key,allocatedSockets,ListCallBack,GCALLBACK_FUNCTION,NULL);
	error = ServerListUpdate(serverlist,true); /* FS: Use Async now! */
	CL_Gamespy_Check_Error(serverlist, error);
}
#endif /* GAMESPY */
