/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
/* cl_main.c  -- client main loop */

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

#ifdef WITH_XMMS
void		MP3_Init(void);
void		MP3_Shutdown(void);
void		MP3_Frame(void);
#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_footsteps_override;
cvar_t         *cl_footsteps_volume;

cvar_t         *cl_timeout;
cvar_t         *cl_predict;

/* cvar_t	*cl_minfps; */
cvar_t         *cl_maxfps;
cvar_t         *cl_drawfps;
cvar_t         *cl_gun;

cvar_t         *cl_3dcam;
cvar_t         *cl_3dcam_angle;
cvar_t         *cl_3dcam_chase;
cvar_t         *cl_3dcam_dist;
cvar_t         *cl_3dcam_alpha;
cvar_t         *cl_3dcam_adjust;

#ifdef QMAX
cvar_t         *cl_blood;
cvar_t         *cl_railred;
cvar_t         *cl_railgreen;
cvar_t         *cl_railblue;
cvar_t         *cl_railtype;
cvar_t         *cl_gunsmoke;
cvar_t         *cl_explosion;
cvar_t         *cl_explosion_scale;
cvar_t         *cl_blaster_color;
cvar_t         *cl_blaster_type;
cvar_t         *cl_hyperblaster_color;
cvar_t         *cl_hyperblaster_particles;
cvar_t         *cl_hyperblaster_particles_type;
cvar_t         *cl_flame_enh;
cvar_t         *cl_flame_size;
cvar_t         *cl_particles_type;
cvar_t         *cl_teleport_particles;
cvar_t         *cl_nukeblast_enh;
#endif

cvar_t         *cl_add_particles;
cvar_t         *cl_add_lights;
cvar_t         *cl_add_entities;
cvar_t         *cl_add_blend;

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         *info_spectator;
cvar_t         *name;
cvar_t         *skin;
cvar_t         *rate;
cvar_t         *fov;
cvar_t         *msg;
cvar_t         *hand;
cvar_t         *gender;
cvar_t         *gender_auto;

cvar_t         *cl_vwep;

cvar_t         *cl_highlight;
cvar_t         *cl_gunalpha;
cvar_t         *cl_hudalpha;
cvar_t         *cl_sleep;
cvar_t         *cl_bobbing;
cvar_t         *cl_models_overbright;
cvar_t         *cl_underwater_movement;
cvar_t         *cl_underwater_trans;

#ifndef QMAX
cvar_t         *cl_railstyle;
cvar_t         *cl_railtrail;
cvar_t         *cl_railtrail_color;
#endif

cvar_t         *deathmatch;
cvar_t         *coop;

cvar_t         *cl_flashlight;
cvar_t         *cl_flashlight_decscale;
cvar_t         *cl_flashlight_distance;
cvar_t         *cl_flashlight_sound;
cvar_t         *cl_flashlight_red;
cvar_t         *cl_flashlight_green;
cvar_t         *cl_flashlight_blue;
cvar_t         *cl_flashlight_intensity;

#ifdef WITH_XMMS
cvar_t         *xmms_enable;
#endif

cvar_t         *cl_hud_red;
cvar_t         *cl_hud_green;
cvar_t         *cl_hud_blue;
cvar_t         *cl_menu_alpha;
cvar_t         *cl_conback_image;
cvar_t         *weap_shell;
cvar_t         *cl_drawlocs;
cvar_t         *loc_here;
cvar_t         *loc_there;

cvar_t         *cl_mouse_cursor;
cvar_t         *cl_mouse_scale;
cvar_t         *cl_mouse_alpha;
cvar_t         *cl_mouse_red;
cvar_t         *cl_mouse_green;
cvar_t         *cl_mouse_blue;
cvar_t         *cl_mouse_rotate;
cvar_t         *cl_mouse_sensitivity;

cvar_t         *font_color;

cvar_t         *gl_minimap_x;
cvar_t         *gl_minimap_y;

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;

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


/*
 * ==================== 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_Record_f
 *
 * record <demoname>
 *
 * Begins recording a demo from the current position ====================
 */
void
CL_Record_f(void)
{
	char		name[MAX_OSPATH];
	byte		buf_data[MAX_MSGLEN];
	sizebuf_t	buf;
	int		i;
	int		len;
	entity_state_t *ent;
	entity_state_t	nullstate;

	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, 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 always 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]) + 32 > 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]);
		}
	}

	/* baselines */
	memset(&nullstate, 0, sizeof(nullstate));
	for (i = 0; i < MAX_EDICTS; i++) {
		ent = &cl_entities[i].baseline;
		if (!ent->modelindex)
			continue;

		if (buf.cursize + 64 > 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);
	}

	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)
{
	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());
	}
}

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

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

		Q_strncpyz(buffer, Cmd_Argv(1), sizeof(buffer));
		strncat(buffer, "=", sizeof(buffer) - strlen(buffer) - 1);

		for (i = 2; i < argc; i++) {
			strncat(buffer, Cmd_Argv(i), sizeof(buffer) - strlen(buffer) - 1);
			strncat(buffer, " ", sizeof(buffer) - strlen(buffer) - 1);
		}

		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), env);
		}
	}
}


/*
 * ================== CL_ForwardToServer_f ==================
 */
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());
	}
}


/*
 * ================== 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)
{
	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.
 * ======================
 */
void
CL_SendConnectPacket(void)
{
	netadr_t	adr;
	int		port;

	memset(&adr, 0, sizeof(adr));

	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 =================
 */
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;
		Q_strncpyz(cls.servername, "localhost", sizeof(cls.servername));
		/* 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
 *
 * ================
 */
void
CL_Connect_f(void)
{
	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(va("Server quit\n", msg), false);
	} else {
		CL_Disconnect();
	}

	server = Cmd_Argv(1);

	NET_Config(true);	/* allow remote */

	CL_Disconnect();

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


/*
 * ===================== CL_Rcon_f
 *
 * Send the rest of the command line over as an unconnected command.
 * =====================
 */
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;
	}
	memset(&to, 0, sizeof(to));
	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, strlen(message) + 1, message, to);
}


/*
 * ===================== CL_ClearState
 *
 * =====================
 */
void
CL_ClearState(void)
{
	S_StopAllSounds();
	CL_ClearEffects();
	CL_ClearTEnts();

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

	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 && 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);

	M_ForceMenuOff();

	cls.connect_time = 0;

	SCR_StopCinematic();

	if (cls.demorecording)
		CL_Stop_f();

	/* send a disconnect message to the server */
	final[0] = clc_stringcmd;
	Q_strncpyz((char *)final + 1, "disconnect", sizeof(final));
	Netchan_Transmit(&cls.netchan, strlen((char *)final), final);
	Netchan_Transmit(&cls.netchan, strlen((char *)final), final);
	Netchan_Transmit(&cls.netchan, 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 ====================
 */
void
CL_Packet_f(void)
{
	char		send[2048];
	int		i, l;
	char           *in, *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 = 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);
}

/*
 * ================= CL_Changing_f
 *
 * Just sent as a hint to the client that they should drop to full console
 * =================
 */
void
CL_Changing_f(void)
{
	/* ZOID */

	/*
	 * 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 =================
 */
void
CL_Reconnect_f(void)
{
	/* ZOID */

	/*
	 * 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");
		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 =================
 */
void
CL_ParseStatusMessage(void)
{
	char           *s;

	s = MSG_ReadString(&net_message);

	Com_Printf("%s\n", s);
	M_AddToServerList(net_from, s);
}


/*
 * ================= CL_PingServers_f =================
 */
void
CL_PingServers_f(void)
{
	int		i;
	netadr_t	adr;
	char		name[32];
	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;
#ifdef HAVE_IPV6
		Com_Printf("pinging multicast...\n");
		adr.type = NA_MULTICAST6;
#endif
		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 =================
 */
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]);
		SCR_UpdateScreen();
		Sys_SendKeyEvents();	/* pump message loop */
		CL_ParseClientinfo(i);
	}
}


/*
 * ================= CL_ConnectionlessPacket
 *
 * Responses to broadcasts, etc =================
 */
void
CL_ConnectionlessPacket(void)
{
	char           *s;
	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;
		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
 * =================
 */
void
CL_DumpPackets(void)
{
	while (NET_GetPacket(NS_CLIENT, &net_from, &net_message)) {
		Com_Printf("dumnping a packet\n");
	}
}

/*
 * ================= 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("%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 && 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_FixUpGender_f ==============
 */
void
CL_FixUpGender(void)
{
	char           *p;
	char		sk[80];

	if (gender_auto->value) {

		if (gender->modified) {
			/* was set directly, don't override the user */
			gender->modified = false;
			return;
		}
		Q_strncpyz(sk, skin->string, sizeof(sk));
		if ((p = strchr(sk, '/')) != NULL)
			*p = 0;
		if (Q_stricmp(sk, "male") == 0 || Q_stricmp(sk, "cyborg") == 0)
			Cvar_Set("gender", "male");
		else if (Q_stricmp(sk, "female") == 0 || Q_stricmp(sk, "crackhor") == 0)
			Cvar_Set("gender", "female");
		else
			Cvar_Set("gender", "none");
		gender->modified = false;
	}
}

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

/*
 * ================= 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)
{
	S_Shutdown();
	S_Init();
	CL_RegisterSounds();
}

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

/* ENV_CNT is map load, ENV_CNT+1 is first env map */
#define ENV_CNT (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
#define TEXTURE_CNT (ENV_CNT+13)

static const char *env_suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};

void
CL_RequestNextDownload(void)
{
	unsigned	map_checksum;	/* for detecting cheater maps */
	char		fn[MAX_OSPATH];
	dmdl_t         *pheader;

	if (cls.state != ca_connected)
		return;

	if (!allow_download->value && precache_check < ENV_CNT)
		precache_check = ENV_CNT;

	/* ZOID */
	if (precache_check == CS_MODELS) {	/* confirm map */
		precache_check = CS_MODELS + 2;	/* 0 isn't used */
		if (allow_download_maps->value)
			if (!CL_CheckOrDownloadFile(cl.configstrings[CS_MODELS + 1]))
				return;	/* started a download */
	}
	if (precache_check >= CS_MODELS && precache_check < CS_MODELS + MAX_MODELS) {
		if (allow_download_models->value) {
			while (precache_check < CS_MODELS + MAX_MODELS &&
			    cl.configstrings[precache_check][0]) {
				if (cl.configstrings[precache_check][0] == '*' ||
				    cl.configstrings[precache_check][0] == '#') {
					precache_check++;
					continue;
				}
				if (precache_model_skin == 0) {
					if (!CL_CheckOrDownloadFile(cl.configstrings[precache_check])) {
						precache_model_skin = 1;
						return;	/* started a download */
					}
					precache_model_skin = 1;
				}
				/* checking for skins in the model */
				if (!precache_model) {

					FS_LoadFile(cl.configstrings[precache_check], (void **)&precache_model);
					if (!precache_model) {
						precache_model_skin = 0;
						precache_check++;
						continue;	/* couldn't load it */
					}
					if (LittleLong(*(unsigned *)precache_model) != IDALIASHEADER) {
						/* not an alias model */
						FS_FreeFile(precache_model);
						precache_model = 0;
						precache_model_skin = 0;
						precache_check++;
						continue;
					}
					pheader = (dmdl_t *) precache_model;
					if (LittleLong(pheader->version) != ALIAS_VERSION) {
						precache_check++;
						precache_model_skin = 0;
						continue;	/* couldn't load it */
					}
				}
				pheader = (dmdl_t *) precache_model;

				while (precache_model_skin - 1 < LittleLong(pheader->num_skins)) {
					if (!CL_CheckOrDownloadFile((char *)precache_model +
					    LittleLong(pheader->ofs_skins) +
					    (precache_model_skin - 1) * MAX_SKINNAME)) {
						precache_model_skin++;
						return;	/* started a download */
					}
					precache_model_skin++;
				}
				if (precache_model) {
					FS_FreeFile(precache_model);
					precache_model = 0;
				}
				precache_model_skin = 0;
				precache_check++;
			}
		}
		precache_check = CS_SOUNDS;
	}
	if (precache_check >= CS_SOUNDS && precache_check < CS_SOUNDS + MAX_SOUNDS) {
		if (allow_download_sounds->value) {
			if (precache_check == CS_SOUNDS)
				precache_check++;	/* zero is blank */
			while (precache_check < CS_SOUNDS + MAX_SOUNDS &&
			    cl.configstrings[precache_check][0]) {
				if (cl.configstrings[precache_check][0] == '*') {
					precache_check++;
					continue;
				}
				Com_sprintf(fn, sizeof(fn), "sound/%s", cl.configstrings[precache_check++]);
				if (!CL_CheckOrDownloadFile(fn))
					return;	/* started a download */
			}
		}
		precache_check = CS_IMAGES;
	}
	if (precache_check >= CS_IMAGES && precache_check < CS_IMAGES + MAX_IMAGES) {
		if (precache_check == CS_IMAGES)
			precache_check++;	/* zero is blank */
		while (precache_check < CS_IMAGES + MAX_IMAGES &&
		    cl.configstrings[precache_check][0]) {
			Com_sprintf(fn, sizeof(fn), "pics/%s.pcx", cl.configstrings[precache_check++]);
			if (!CL_CheckOrDownloadFile(fn))
				return;	/* started a download */
		}
		precache_check = CS_PLAYERSKINS;
	}
	/* skins are special, since a player has three things to download: */
	/* model, weapon model and skin */
	/* so precache_check is now *3 */
	if (precache_check >= CS_PLAYERSKINS && precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT) {
		if (allow_download_players->value) {
			while (precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT) {
				int		i         , n;
				char		model     [MAX_QPATH], skin[MAX_QPATH],
				               *p;

				i = (precache_check - CS_PLAYERSKINS) / PLAYER_MULT;
				n = (precache_check - CS_PLAYERSKINS) % PLAYER_MULT;

				if (!cl.configstrings[CS_PLAYERSKINS + i][0]) {
					precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT;
					continue;
				}
				if ((p = strchr(cl.configstrings[CS_PLAYERSKINS + i], '\\')) != NULL)
					p++;
				else
					p = cl.configstrings[CS_PLAYERSKINS + i];
				Q_strncpyz(model, p, sizeof(model));
				p = strchr(model, '/');
				if (p) {
					*p++ = 0;
					Q_strncpyz(skin, p, sizeof(skin));
				} else
					*skin = 0;

				switch (n) {
				case 0:	/* model */
					Com_sprintf(fn, sizeof(fn), "players/%s/tris.md2", model);
					if (!CL_CheckOrDownloadFile(fn)) {
						precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 1;
						return;	/* started a download */
					}
					n++;
					/* FALL THROUGH */

				case 1:	/* weapon model */
					Com_sprintf(fn, sizeof(fn), "players/%s/weapon.md2", model);
					if (!CL_CheckOrDownloadFile(fn)) {
						precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 2;
						return;	/* started a download */
					}
					n++;
					/* FALL THROUGH */

				case 2:	/* weapon skin */
					Com_sprintf(fn, sizeof(fn), "players/%s/weapon.pcx", model);
					if (!CL_CheckOrDownloadFile(fn)) {
						precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 3;
						return;	/* started a download */
					}
					n++;
					/* FALL THROUGH */

				case 3:	/* skin */
					Com_sprintf(fn, sizeof(fn), "players/%s/%s.pcx", model, skin);
					if (!CL_CheckOrDownloadFile(fn)) {
						precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 4;
						return;	/* started a download */
					}
					n++;
					/* FALL THROUGH */

				case 4:	/* skin_i */
					Com_sprintf(fn, sizeof(fn), "players/%s/%s_i.pcx", model, skin);
					if (!CL_CheckOrDownloadFile(fn)) {
						precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 5;
						return;	/* started a download */
					}
					/* move on to next model */
					precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT;
				}
			}
		}
		/* precache phase completed */
		precache_check = ENV_CNT;
	}
	if (precache_check == ENV_CNT) {
		precache_check = ENV_CNT + 1;

		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: %i != '%s'\n",
			    map_checksum, cl.configstrings[CS_MAPCHECKSUM]);
			return;
		}
	}
	if (precache_check > ENV_CNT && precache_check < TEXTURE_CNT) {
		if (allow_download->value && allow_download_maps->value) {
			while (precache_check < TEXTURE_CNT) {
				int		n = precache_check++ - ENV_CNT - 1;

				if (n & 1)
					Com_sprintf(fn, sizeof(fn), "env/%s%s.pcx",
					    cl.configstrings[CS_SKY], env_suf[n / 2]);
				else
					Com_sprintf(fn, sizeof(fn), "env/%s%s.tga",
					    cl.configstrings[CS_SKY], env_suf[n / 2]);
				if (!CL_CheckOrDownloadFile(fn))
					return;	/* started a download */
			}
		}
		precache_check = TEXTURE_CNT;
	}
	if (precache_check == TEXTURE_CNT) {
		precache_check = TEXTURE_CNT + 1;
		precache_tex = 0;
	}
	/* confirm existance of textures, download any that don't exist */
	if (precache_check == TEXTURE_CNT + 1) {
		/* from qcommon/cmodel.c */
		extern int	numtexinfo;
		extern mapsurface_t map_surfaces[];

		if (allow_download->value && allow_download_maps->value) {
			while (precache_tex < numtexinfo) {
				char	fn[MAX_OSPATH];

				Com_sprintf(fn, sizeof(fn), "textures/%s.wal", map_surfaces[precache_tex++].rname);
				if (!CL_CheckOrDownloadFile(fn))
					return;	/* started a download */
			}
		}
		precache_check = TEXTURE_CNT + 999;
	}
	/* ZOID */
	CL_RegisterSounds();
	CL_PrepRefresh();

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

/*
 * ================= CL_Precache_f
 *
 * The server will send this command right before allowing the client into the
 * server =================
 */
void
CL_Precache_f(void)
{
	/* Yet another hack to let old demos work */
	/* the old precache sequence */
	if (Cmd_Argc() < 2) {
		unsigned	map_checksum;	/* for detecting cheater maps */

		CM_LoadMap(cl.configstrings[CS_MODELS + 1], true, &map_checksum);
		CL_RegisterSounds();
		CL_PrepRefresh();
		return;
	}
	precache_check = CS_MODELS;
	precache_spawncount = atoi(Cmd_Argv(1));
	precache_model = 0;
	precache_model_skin = 0;

	CL_RequestNextDownload();
}

void
CL_AddLoc(void)
{
	if (Cmd_Argc() != 2) {
		Com_Printf("usage: loc_add <label>\n");
		return;
	}
	CL_LocAdd(Cmd_Argv(1));
}

void
CL_DeleteLoc(void)
{
	CL_LocDelete();
}

void
CL_SaveLoc(void)
{
	if (Cmd_Argc() != 2) {
		Com_Printf("usage: loc_save <filename>\n");
		return;
	}
	CL_LocWrite(Cmd_Argv(1));
}


/*
 * ================= CL_InitLocal =================
 */
void
CL_InitLocal(void)
{
	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", 0);
	cl_stereo = Cvar_Get("cl_stereo", "0", 0);

	cl_add_blend = Cvar_Get("cl_blend", "1", 0);
	cl_add_lights = Cvar_Get("cl_lights", "1", 0);
	cl_add_particles = Cvar_Get("cl_particles", "1", 0);
	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_footsteps_override = Cvar_Get ("cl_footsteps_override", "1", CVAR_ARCHIVE);
	cl_footsteps_volume = Cvar_Get ("cl_footsteps_volume", "0.7", CVAR_ARCHIVE);
	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_minfps = Cvar_Get ("cl_minfps", "5", 0); */
	cl_maxfps = Cvar_Get("cl_maxfps", "90", CVAR_ARCHIVE);
	cl_drawfps = Cvar_Get("cl_drawfps", "0", CVAR_ARCHIVE);	/* FPS hack */
	/* 3d camera */
	cl_3dcam = Cvar_Get("cl_3dcam", "0", CVAR_ARCHIVE);
	cl_3dcam_angle = Cvar_Get("cl_3dcam_angle", "0", CVAR_ARCHIVE);
	cl_3dcam_dist = Cvar_Get("cl_3dcam_dist", "50", CVAR_ARCHIVE);
	cl_3dcam_alpha = Cvar_Get("cl_3dcam_alpha", "1", CVAR_ARCHIVE);
	cl_3dcam_chase = Cvar_Get("cl_3dcam_chase", "1", CVAR_ARCHIVE);
	cl_3dcam_adjust = Cvar_Get("cl_3dcam_adjust", "1", CVAR_ARCHIVE);

#ifdef QMAX
	cl_blood = Cvar_Get("cl_blood", "1", CVAR_ARCHIVE);
	cl_railred = Cvar_Get("cl_railred", "20", CVAR_ARCHIVE);
	cl_railgreen = Cvar_Get("cl_railgreen", "50", CVAR_ARCHIVE);
	cl_railblue = Cvar_Get("cl_railblue", "175", CVAR_ARCHIVE);
	cl_railtype = Cvar_Get("cl_railtype", "1", CVAR_ARCHIVE);
	cl_gunsmoke = Cvar_Get("cl_gunsmoke", "1", CVAR_ARCHIVE);
	cl_explosion = Cvar_Get("cl_explosion", "1", CVAR_ARCHIVE);
	cl_explosion_scale = Cvar_Get("cl_explosion_scale", "1", CVAR_ARCHIVE);
	cl_blaster_color = Cvar_Get("cl_blaster_color", "0", CVAR_ARCHIVE);
	cl_blaster_type = Cvar_Get("cl_blaster_bubble", "2", CVAR_ARCHIVE);
	cl_hyperblaster_color = Cvar_Get("cl_hyperblaster_color", "0", CVAR_ARCHIVE);
	cl_hyperblaster_particles = Cvar_Get("cl_hyperblaster_particles", "1", CVAR_ARCHIVE);
	cl_hyperblaster_particles_type = Cvar_Get("cl_hyperblaster_bubble", "2", CVAR_ARCHIVE);
	cl_flame_enh = Cvar_Get("cl_flame_enh", "1", CVAR_ARCHIVE);
	cl_flame_size = Cvar_Get("cl_flame_size", "10.0", CVAR_ARCHIVE);
	cl_particles_type = Cvar_Get("cl_particles_type", "2", CVAR_ARCHIVE);
	cl_teleport_particles = Cvar_Get("cl_teleport_particles", "4", CVAR_ARCHIVE);
	cl_nukeblast_enh = Cvar_Get("cl_nukeblast_enh", "0", CVAR_ARCHIVE);
#endif

	cl_upspeed = Cvar_Get("cl_upspeed", "200", 0);
	cl_forwardspeed = Cvar_Get("cl_forwardspeed", "200", 0);
	cl_sidespeed = Cvar_Get("cl_sidespeed", "200", 0);
	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", "1", CVAR_ARCHIVE);
	freelook = Cvar_Get("freelook", "1", CVAR_ARCHIVE);
	lookspring = Cvar_Get("lookspring", "0", CVAR_ARCHIVE);
	lookstrafe = Cvar_Get("lookstrafe", "0", CVAR_ARCHIVE);
	sensitivity = Cvar_Get("sensitivity", "3.5", CVAR_ARCHIVE);

	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);
	info_spectator = Cvar_Get("spectator", "0", CVAR_USERINFO);
	name = Cvar_Get("name", "Quaker", CVAR_USERINFO | CVAR_ARCHIVE);
	skin = Cvar_Get("skin", "male/grunt", CVAR_USERINFO | CVAR_ARCHIVE);
	rate = Cvar_Get("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE);	/* FIXME */
	msg = Cvar_Get("msg", "1", CVAR_USERINFO | CVAR_ARCHIVE);
	hand = Cvar_Get("hand", "0", CVAR_USERINFO | CVAR_ARCHIVE);
	fov = Cvar_Get("fov", "90", CVAR_USERINFO | CVAR_ARCHIVE);
	gender = Cvar_Get("gender", "male", CVAR_USERINFO | CVAR_ARCHIVE);
	gender_auto = Cvar_Get("gender_auto", "1", CVAR_ARCHIVE);
	gender->modified = false;	/* clear this so we know when user sets it manually */

	cl_vwep = Cvar_Get("cl_vwep", "1", CVAR_ARCHIVE);

	cl_highlight = Cvar_Get("cl_highlight", "1", CVAR_ARCHIVE);
	cl_gunalpha = Cvar_Get("cl_gunalpha", "1", CVAR_ARCHIVE);
	cl_drawclock = Cvar_Get("cl_drawclock", "0", CVAR_ARCHIVE);
	cl_drawtimestamps = Cvar_Get("cl_drawtimestamps", "1", CVAR_ARCHIVE);
	cl_hudalpha = Cvar_Get("cl_hudalpha", "1", CVAR_ARCHIVE);
	cl_drawmaptime = Cvar_Get("cl_drawmaptime", "0", CVAR_ARCHIVE);
#ifndef QMAX
	cl_railstyle = Cvar_Get("cl_railstyle", "0", CVAR_ARCHIVE);
	cl_railtrail = Cvar_Get("cl_railtrail", "0", CVAR_ARCHIVE);
	cl_railtrail_color = Cvar_Get("cl_railtrail_color", "0", CVAR_ARCHIVE);
#endif
	cl_sleep = Cvar_Get("cl_sleep", "0", CVAR_ARCHIVE);	/* CPU usage */
	cl_bobbing = Cvar_Get("cl_bobbing", "1", CVAR_ARCHIVE);
	cl_models_overbright = Cvar_Get("cl_models_overbright", "0", CVAR_ARCHIVE);
	cl_underwater_movement = Cvar_Get("cl_underwater_movement", "1", CVAR_ARCHIVE);
	cl_underwater_trans = Cvar_Get("cl_underwater_trans", "0", CVAR_ARCHIVE);
	cl_flashlight = Cvar_Get("cl_flashlight", "1", 0);
	cl_flashlight_decscale = Cvar_Get("cl_flashlight_decscale", "0.6", CVAR_ARCHIVE);
	cl_flashlight_distance = Cvar_Get("cl_flashlight_distance", "900", CVAR_ARCHIVE);
	cl_flashlight_sound = Cvar_Get("cl_flashlight_sound", "1", CVAR_ARCHIVE);
	cl_flashlight_red = Cvar_Get("cl_flashlight_red", "1.0", CVAR_ARCHIVE);
	cl_flashlight_green = Cvar_Get("cl_flashlight_green", "1.0", CVAR_ARCHIVE);
	cl_flashlight_blue = Cvar_Get("cl_flashlight_blue", "1.0", CVAR_ARCHIVE);
	cl_flashlight_intensity = Cvar_Get("cl_flashlight_intensity", "150", CVAR_ARCHIVE);
#ifdef WITH_XMMS
	xmms_enable = Cvar_Get("xmms_enable", "1", CVAR_ARCHIVE);
#endif
	cl_hud_red = Cvar_Get("cl_hud_red", "1", CVAR_ARCHIVE);
	cl_hud_green = Cvar_Get("cl_hud_green", "1", CVAR_ARCHIVE);
	cl_hud_blue = Cvar_Get("cl_hud_blue", "1", CVAR_ARCHIVE);
	cl_menu_alpha = Cvar_Get("cl_menu_alpha", "0", CVAR_ARCHIVE);
	cl_conback_image = Cvar_Get("cl_conback_image", "conback", CVAR_ARCHIVE);
	weap_shell = Cvar_Get("weap_shell", "1", CVAR_ARCHIVE);

	cl_drawlocs = Cvar_Get("cl_drawlocs", "0", 0);

	cl_mouse_cursor = Cvar_Get("cl_mouse_cursor", "0", CVAR_ARCHIVE);
	cl_mouse_scale = Cvar_Get("cl_mouse_scale", "0.5", CVAR_ARCHIVE);
	cl_mouse_alpha = Cvar_Get("cl_mouse_alpha", "0.7", CVAR_ARCHIVE);
	cl_mouse_red = Cvar_Get("cl_mouse_red", "0.2", CVAR_ARCHIVE);
	cl_mouse_green = Cvar_Get("cl_mouse_green", "1", CVAR_ARCHIVE);
	cl_mouse_blue = Cvar_Get("cl_mouse_blue", "0.2", CVAR_ARCHIVE);
	cl_mouse_rotate = Cvar_Get("cl_mouse_rotate", "0", 0);
	cl_mouse_sensitivity = Cvar_Get("cl_mouse_sensitivity", "1", CVAR_ARCHIVE);

	font_color = Cvar_Get("font_color", "default", CVAR_ARCHIVE);
	
	gl_minimap_x = Cvar_Get("gl_minimap_x", "800", CVAR_ARCHIVE);
	gl_minimap_y = Cvar_Get("gl_minimap_y", "400", CVAR_ARCHIVE);
		
	deathmatch = Cvar_Get("deathmatch", "0", 0);
	coop = Cvar_Get("coop", "0", 0);
	
	/* 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("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);

	/*
	 * Cmd_AddCommand ("packet", CL_Packet_f); // this is dangerous to
	 * leave in
	 */

	Cmd_AddCommand("setenv", CL_Setenv_f);

	Cmd_AddCommand("precache", CL_Precache_f);

	Cmd_AddCommand("download", CL_Download_f);

	/* forward to server commands */
	/* the only thing this does is allow command completion */
	/* to work -- all unknown commands are automatically forwarded to the server */
	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("give", NULL);
	Cmd_AddCommand("god", NULL);
	Cmd_AddCommand("notarget", NULL);
	Cmd_AddCommand("noclip", 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("loc_add", CL_AddLoc);
	Cmd_AddCommand("loc_del", CL_DeleteLoc);
	Cmd_AddCommand("loc_save", CL_SaveLoc);
	Cmd_AddCommand("loc_help", CL_LocHelp_f);
}



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

	if (cls.state == ca_uninitialized)
		return;

	Com_sprintf(path, sizeof(path), "%s/QuDos.cfg", FS_Gamedir());
	f = fopen(path, "w");
	if (!f) {
		Com_Printf("Couldn't write QuDos.cfg.\n");
		return;
	}
	fprintf(f, "// generated by QuDos -- http://qudos.quakedev.com\n");
	Key_WriteBindings(f);
	fclose(f);

	Cvar_WriteVariables(path);
}


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

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

cheatvar_t	cheatvars[] = {
	{"timescale", "1"},
	{"timedemo", "0"},
	{"r_drawworld", "1"},
	{"cl_testlights", "0"},
	{"r_fullbright", "0"},
	{"r_drawflat", "0"},
	{"paused", "0"},
	{"fixedtime", "0"},
	{"sw_draworder", "0"},
	{"gl_lightmap", "0"},
	{"gl_saturatelighting", "0"},
	{"cl_underwater_trans", "0"},
	{"cl_flashlight", "0"},
	{"gl_minimap", "0"},
	{NULL, NULL}
};

cheatvar_t	cheatvars_coop[] = {
	{"timescale", "1"},
	{"timedemo", "0"},
	{"r_drawworld", "1"},
	{"cl_testlights", "0"},
	{"r_fullbright", "0"},
	{"r_drawflat", "0"},
	{"paused", "0"},
	{"fixedtime", "0"},
	{"sw_draworder", "0"},
	{"gl_lightmap", "0"},
	{"gl_saturatelighting", "0"},
	{NULL, NULL}
};

int		numcheatvars;

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

	if (!strcmp(cl.configstrings[CS_MAXCLIENTS], "1")
	    || !cl.configstrings[CS_MAXCLIENTS][0])
		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);
		}
	}
}
void
CL_FixCvarCheats_Coop(void)
{
	int		i;
	cheatvar_t     *var;

	if (!strcmp(cl.configstrings[CS_MAXCLIENTS], "1")
	    || !cl.configstrings[CS_MAXCLIENTS][0])
		return;		/* single player can cheat */

	/* find all the cvars if we haven't done it yet */
	if (!numcheatvars) {
		while (cheatvars_coop[numcheatvars].name) {
			cheatvars_coop[numcheatvars].var = Cvar_Get(cheatvars_coop[numcheatvars].name,
			    cheatvars_coop[numcheatvars].value, 0);
			numcheatvars++;
		}
	}
	/* make sure they are all set to the proper values */
	for (i = 0, var = cheatvars_coop; i < numcheatvars; i++, var++) {
		if (strcmp(var->var->string, var->value)) {
			Cvar_Set(var->name, var->value);
		}
	}
}

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

/*
 * ================== 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 */
	if (coop->value)
		CL_FixCvarCheats_Coop();
	else
		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;

	extratime += msec;

	if (!cl_timedemo->value) {
		if (cls.state == ca_connected && extratime < 100)
			return;	/* don't flood packets out while connecting */
		if (extratime < 1000 / cl_maxfps->value) {
			/* CPU usage fix */
			if (cl_sleep->value) {
				int		temptime = 1000 / cl_maxfps->value - extratime;

				if (temptime > 1)
					usleep(1);
			}
			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);

	/* 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();

	/* predict all unacknowledged movements */
	CL_PredictMovement();

	/* allow rendering DLL change */
	VID_CheckChanges();
	if (!cl.refresh_prepped && cls.state == ca_active)
		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();

#ifdef WITH_XMMS
	if (xmms_enable->value) {
		MP3_Frame();
	}
#endif
	/* advance local effects for next frame */
	CL_RunDLights();
	CL_RunLightStyles();
	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;
			}
		}
	}
}

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

	/* all archived variables will now be loaded */

	Con_Init();

	S_Init();
	VID_Init();

	V_Init();

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

	M_Init();

	SCR_Init();
	cls.disable_screen = true;	/* don't draw yet */

	CDAudio_Init();
	CL_InitLocal();
	IN_Init();

#ifdef WITH_XMMS
	MP3_Init();
#endif
	/* Cbuf_AddText ("exec autoexec.cfg\n"); */
	FS_ExecAutoexec();
	Cbuf_Execute();

}


/*
 * =============== 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;

	if (isdown) {
		printf("Recursive shutdown\n");
		return;
	}
	isdown = true;

	CL_WriteConfiguration();

	CDAudio_Shutdown();
	S_Shutdown();
#ifdef WITH_XMMS
	MP3_Shutdown();
#endif
	IN_Shutdown();
	VID_Shutdown();
}
