//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/cl_parse.c                                             $
// $Revision:: 4                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 2/5/02 7:36 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:
// parse a message received from the server
//

#include "client.h"

const char *svc_strings[256] =
{
	"svc_bad",

	"svc_muzzleflash",
	"svc_muzzlflash2",
	"svc_temp_entity",
	"svc_layout",
	"svc_inventory",
	"svc_console_command",

	"svc_nop",
	"svc_disconnect",
	"svc_reconnect",
	"svc_sound",
	"svc_print",
	"svc_stufftext",
	"svc_serverdata",
	"svc_configstring",
	"svc_spawnbaseline",
	"svc_centerprint",
	"svc_download",
	"svc_playerinfo",
	"svc_packetentities",
	"svc_deltapacketentities",
	"svc_frame",
	"svc_consolestates",
	"svc_surfacestates",
	"svc_spawnconbaseline",
	"svc_spawnsurfbaseline",
	"svc_extradata"
};

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

static void CL_DownloadFileName( char *dest, int destlen, char *fn )
{
	Com_sprintf ( dest, destlen, "%s/%s", FS_Gamedir(), fn );
}

/*
===============
CL_CheckOrDownloadFile

Returns true if the file exists, otherwise it attempts
to start a download from the server.
===============
*/
qboolean	CL_CheckOrDownloadFile( const char *filename )
{
	FILE *fp;
	char name[MAX_OSPATH];
	int	length = 0;
	char *p = NULL;

	if (strstr (filename, ".."))
	{
		Com_Printf ("Refusing to check a path with .. (%s)\n", filename);
		return true;
	}
	if (strchr (filename, ' '))
	{
		Com_Printf ("Refusing to check a path containing spaces (%s)\n", filename);
		return true;
	}
	if (strchr (filename, ':'))
	{
		Com_Printf ("Refusing to check a path containing a colon (%s)\n", filename);
		return true;
	}
	if (filename[0] == '/')
	{
		Com_Printf ("Refusing to check a path starting with / (%s)\n", filename);
		return true;
	}

	if (FS_LoadFile ( filename, NULL ) != -1)
	{   // it exists, no need to download
		return true;
	}

	strcpy ( cls.downloadname, filename );

	//r1: fix \ to /
	p = cls.downloadname;
	while ((p = strchr(p, '\\')))
		p[0] = '/';

	length = (int)strlen(cls.downloadname);

	//normalize path
	p = cls.downloadname;
	while ((p = strstr (p, "./")))
	{
		memmove (p, p+2, length - (p - cls.downloadname) - 1);
		length -= 2;
	}

	//r1: verify we are giving the server a legal path
	if (cls.downloadname[length-1] == '/')
	{
		Com_Printf ("Refusing to download bad path (%s)\n", filename);
		return true;
	}

	// download to a temp name, and only rename
	// to the real name when done, so if interrupted
	// a runt file wont be left
	COM_StripExtension ( cls.downloadname, cls.downloadtempname );
	strcat ( cls.downloadtempname, ".tmp" );

//ZOID
	// check to see if we already have a tmp for this file, if so, try to resume
	// open the file if not opened yet
	CL_DownloadFileName( name, sizeof(name), cls.downloadtempname );

	fp = fopen ( name, "r+b" );
	if (fp)
	{ // it exists
		int len;
		fseek(fp, 0, SEEK_END);
		len = ftell(fp);

		cls.download = fp;

		// give the server an offset to start the download
		Com_Printf ("Resuming %s\n", cls.downloadname);
		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
		MSG_WriteString (&cls.netchan.message,
		va("download %s %i", cls.downloadname, len));
	} else {
		Com_Printf ("Downloading %s\n", cls.downloadname);
		MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
		MSG_WriteString (&cls.netchan.message,
		va("download %s", cls.downloadname));
	}

	cls.downloadnumber++;
	cls.forcePacket = true;

	return false;
}

/*
===============
CL_Download_f

Request a download from the server
===============
*/
void    CL_Download_f( void )
{
	char filename[MAX_OSPATH];

	if (Cmd_Argc() != 2) {
		Com_Printf( "Usage: download <filename>\n" );
		return;
	}

	if (cls.state < ca_connected)
	{
		Com_Printf ( "You must be connected to use this command.\n" );
		return;
	}

	Com_sprintf( filename, sizeof(filename), "%s", Cmd_Argv( 1 ));

	if (strstr ( filename, ".." ))
	{
		Com_Printf ( "Refusing to download a path with ..\n" );
		return;
	}

	if (FS_LoadFile ( filename, NULL ) != -1)
	{   // it exists, no need to download
		Com_Printf( "File already exists.\n" );
		return;
	}

	strcpy ( cls.downloadname, filename );
	Com_Printf ( "Downloading %s\n", cls.downloadname );

	// download to a temp name, and only rename
	// to the real name when done, so if interrupted
	// a runt file wont be left
	COM_StripExtension ( cls.downloadname, cls.downloadtempname );
	strcat ( cls.downloadtempname, ".tmp" );

	MSG_WriteByte ( &cls.netchan.message, clc_stringcmd );
	MSG_WriteString ( &cls.netchan.message,
	                  va( "download %s", cls.downloadname ));

	cls.downloadnumber++;
}

/*
======================
CL_RegisterSounds
======================
*/
void CL_RegisterSounds( qboolean status )
{
	int i;

	S_BeginRegistration ();

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

	CL_RegisterTEntSounds ();
	for (i=1; i<MAX_SOUNDS; i++)
	{
		if (!cl.configstrings[CS_SOUNDS+i][0])
			break;
		cl.sound_precache[i] = S_RegisterSound ( cl.configstrings[CS_SOUNDS+i] );
		Sys_SendKeyEvents ();   // pump message loop

		if ( status )
		{
			if ( !( i % 20 ) )
				Com_UpdateStatus( cl_current++, cl_total, "Loading Sounds" );
			else
				cl_current++;
		}
	}

	if ( status )
	{
		cl_current += ( MAX_SOUNDS - i );
		Com_UpdateStatus( cl_current, cl_total, "Loading Sounds" );
	}

	S_EndRegistration( status );
}


/*
=====================
CL_ParseDownload

A download message has been received from the server
=====================
*/
static void CL_ParseDownload( void )
{
	int size, percent;
	char name[MAX_OSPATH];
	int r;

	// read the data
	size = MSG_ReadShort ( &net_message );
	percent = MSG_ReadByte ( &net_message );
	if (size < 0)
	{
		if (size == -1)
			Com_Printf ("Server does not have this file.\n");
		else
			Com_Printf ("Bad download data from server.\n");

		//r1: nuke the temp filename
		cls.downloadtempname[0] = 0;
		cls.downloadname[0] = 0;

		if (cls.download)
		{
			// if here, we tried to resume a file but the server said no
			fclose ( cls.download );
			cls.download = NULL;
		}
		CL_RequestNextDownload ();
		return;
	}

	// open the file if not opened yet
	if (!cls.download) {
		CL_DownloadFileName( name, sizeof(name), cls.downloadtempname );

		FS_CreatePath ( name );

		cls.download = fopen ( name, "wb" );
		if (!cls.download) {
			net_message.readcount += size;
			Com_Printf ( "Failed to open %s\n", cls.downloadtempname );
			CL_RequestNextDownload ();
			return;
		}
	}

	SCR_EndLoadingPlaque(); /* FS: Drop to console to show we're doing something */

	fwrite ( net_message.data + net_message.readcount, 1, size, cls.download );
	net_message.readcount += size;

	if (percent != 100)
	{
		// request next block
		cls.downloadpercent = percent;
		MSG_WriteByte ( &cls.netchan.message, clc_stringcmd );
		SZ_Print ( &cls.netchan.message, "nextdl" );
		cls.forcePacket = true;
	}
	else
	{
		char oldn[MAX_OSPATH];
		char newn[MAX_OSPATH];

		fclose ( cls.download );
		// rename the temp file to it's final name
		CL_DownloadFileName( oldn, sizeof(oldn), cls.downloadtempname );
		CL_DownloadFileName( newn, sizeof(newn), cls.downloadname );
		r = FS_RenameFile( oldn, newn );
		if (!r)
		{
			Com_Printf ( "failed to rename.\n" );
		}
		cls.download = NULL;
		cls.downloadpercent = 0;

		// get another file if needed

		CL_RequestNextDownload ();
	}
}


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

SERVER CONNECTING MESSAGES

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

/*
==================
CL_ParseServerData
==================
*/
static void CL_ParseServerData( void )
{
	const char  *str;
	int i;

	Com_DPrintf (DEVELOPER_MSG_NET, "Serverdata packet received.\n");
//
// wipe the client_state_t struct
//
	CL_ClearState ();
	cls.state = ca_connected;

// parse protocol version number
	i = MSG_ReadLong ( &net_message );

	if (i != PROTOCOL_VERSION)
	{
		if ( Com_ServerState() == 4 ) /* FS: Removed demoplaying CVAR.  4 == ss_demo in server.h */
		{
			// check to see if these were the demos shipped with the game
			if (i != 4)
				Com_Error ( ERR_DROP,"Server returned version %i, not %i", i, PROTOCOL_VERSION );
		}
		else
		{
			Com_Error ( ERR_DROP,"Server returned version %i, not %i", i, PROTOCOL_VERSION );
		}
	}

	cl.servercount = MSG_ReadLong ( &net_message );
	cl.attractloop = MSG_ReadByte ( &net_message );

	// game directory
	str = MSG_ReadString ( &net_message );
	strncpy ( cl.gamedir, str, sizeof(cl.gamedir)-1 );

	// set gamedir
	if ((!fs_gamedirvar->string) || (strcmp( fs_gamedirvar->string, str )))
	{
		if ( !FS_GameDirectoryExists( str ) )
			Com_Error( ERR_DROP, "Could not find game directory %s, cannot connect.", str );
		// free up cached models
		SINMDL_FreeAll();

		Cvar_Set( "game", str );

		MENU_Reload_f();
		HUD_Restart_f();
	}

	// parse player entity number
	cl.playernum = MSG_ReadShort ( &net_message );

	// get the full level name
	str = MSG_ReadString ( &net_message );

	if (cl.playernum == -1)
	{   // playing a cinematic or showing a pic, not a level
		SCR_PlayCinematic( str );
	}
	else
	{
		// seperate the printfs so the server message can have a color
		Com_Printf( "\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n" );
		Com_Printf ( "%c%s\n", 2, str );

		// need to prep refresh at next oportunity
		cl.refresh_prepped = false;

		if ( !developer->value && !scr_skip_loading->intValue) /* FS */
			cls.intermission_active = true;
	}
}

/*
==================
CL_ParseBaseline
==================
*/
static void CL_ParseBaseline( void )
{
	entity_state_t  *es;
	unsigned int bits;
	int newnum;
	entity_state_t nullstate;

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

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

	EulerToQuat( nullstate.angles, nullstate.quat );

	newnum = CL_ParseEntityBits ( &bits );
	es = &cl_entities[newnum].baseline;
	CL_ParseDelta ( &nullstate, es, newnum, bits );
	VectorCopy( es->origin, cl_entities[newnum].lerp_origin );
	cl_entities[newnum].current.scale = es->scale;
}

/*
================
CL_LoadClientinfo
================
*/
void CL_LoadClientinfo (clientinfo_t *ci, const char *s)
{
	char model[MAX_QPATH];
	char skin[MAX_QPATH];
	char damskin[MAX_QPATH];
	char icon[MAX_QPATH];
	char scratch[MAX_QPATH];
	char string[512];
	char *token;

	strncpy(string,s,sizeof(string)-1);
	if (!string[0])
		goto nodata;

	// Name
	token = strtok( string, "\\" );
	strncpy ( ci->name, token, sizeof(ci->name)-1 );

	// Model
	token = strtok( NULL, "\\" );
	COM_StripExtension( token,scratch );

#ifdef SIN_DEMO
	Com_sprintf( model, sizeof(model), "models/pl_blade", scratch );
#else
	Com_sprintf( model, sizeof(model), "models/%s", scratch );
#endif

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

	// Damage skin
	Com_sprintf( damskin, sizeof(damskin), "%s_dam", skin );

	// Icon
	Com_sprintf( icon, sizeof(icon), "/%s_ico", skin );

	COM_DefaultExtension( model,".def" );
	COM_DefaultExtension( skin,".tga" );
	COM_DefaultExtension( damskin,".tga" );
	COM_DefaultExtension( icon,".tga" );

	// If noskins, then forget about it
	if (cl_noskins->value)
	{
		memset( model, 0, sizeof( model ) );
		memset( skin, 0, sizeof( skin ) );
		memset( damskin, 0, sizeof( damskin ) );
		memset( icon, 0, sizeof( icon ) );
	}

	ci->skin        = re.RegisterSkin( skin );
	ci->damageskin  = re.RegisterSkin( damskin );
	ci->model       = re.RegisterModel( model );
	ci->icon        = re.RegisterPic( icon );

	if ( !ci->damageskin )
		ci->damageskin = ci->skin;

	if ( ci->icon )
		strcpy( ci->iconname, icon );
	else
	{
		ci->icon = re.RegisterPic( "unknown.tga" );
		if ( !ci->icon )
			Com_Error( ERR_DROP, "Icon unknown.tga not found.\n" );
		strcpy( ci->iconname, "unknown.tga" );
	}

	// must have loaded all data types to be valid
	if ( !ci->skin || !ci->model )
	{
nodata:
		ci->model = NULL;
		ci->skin = NULL;
		ci->damageskin = NULL;
		return;
	}
}

/*
================
CL_ParseClientinfo

Load the skin, icon, and model for a client
================
*/
void CL_ParseClientinfo( int player )
{
	char            *s;
	clientinfo_t    *ci;

	s = cl.configstrings[player+CS_PLAYERSKINS];
	ci = &cl.clientinfo[player];
	CL_LoadClientinfo ( ci, s );
}

/*
================
CL_LoadBikeinfo
================
*/
void CL_LoadBikeinfo (bikeinfo_t *bi, const char *s)
{
	char model[MAX_QPATH];
	char skin[MAX_QPATH];
	char string[512];
	char *token;
	char scratch[MAX_QPATH];

	strncpy(string,s,sizeof(string)-1);
	if (!string[0])
		goto nodata;

	// Model
	token = strtok( string, "\\" );
//	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",model, scratch);

	COM_DefaultExtension(model,".def");
	COM_DefaultExtension(skin,".tga");

	// If noskins, then forget about it
	if (cl_noskins->value)
	{
		memset( model, 0, sizeof(model));
		memset( skin, 0, sizeof(skin));
	}

	bi->model = re.RegisterModel( model );
	bi->skin  = re.RegisterSkin( skin );
	//Com_DPrintf("* bike model : %s\n", model);
	//Com_DPrintf("* bike skin : %s\n", skin);

	// must have loaded all data types to be valid
	if ( !bi->skin || !bi->model )
	{
	nodata:
		//Com_DPrintf("* bike : no data\n");
		bi->model = NULL;
		bi->skin = NULL;
		return;
	}
}

/*
================
CL_ParseBikeinfo

Load the skin, and model for a client's bike
================
*/
void CL_ParseBikeinfo( int player )
{
	char        *s;
	bikeinfo_t  *bi;

	s = cl.configstrings[player+CS_BIKESKINS];
	bi = &cl.bikeinfo[player];
	CL_LoadBikeinfo ( bi, s );
}

/*
================
CL_ParseConfigString
================
*/
void CL_ParseConfigString( void )
{
	int		i;
	const char	*s;
	char model_name[MAX_QPATH];
	char token[MAX_QPATH];
	size_t sz;
#ifdef SIN_CHECKSUM
	unsigned server_checksum;
	unsigned client_checksum;
#endif

	i = MSG_ReadShort (&net_message);
	if (i < 0 || i >= MAX_CONFIGSTRINGS)
		Com_Error ( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" );

	s = MSG_ReadString( &net_message );
	strcpy ( cl.configstrings[i], s );

	// do something apropriate

	if (i >= CS_LIGHTS && i < CS_LIGHTS+MAX_LIGHTSTYLES)
		CL_SetLightstyle ( i - CS_LIGHTS );
	else if (i == CS_SOUNDTRACK)
	{
		CL_NewSoundtrack( cl.configstrings[CS_SOUNDTRACK] );
	}
	else if (i == CS_CDTRACK)
	{
		if (cl.refresh_prepped)
			CDAudio_Play ( atoi( cl.configstrings[CS_CDTRACK] ), true );
	}
	else if (i >= CS_MODELS && i < CS_MODELS+MAX_MODELS)
	{
		if (cl.refresh_prepped)
		{
			strcpy( model_name, COM_Parse( &s ) );
			strcpy( token, COM_Parse( &s ) );

#ifdef SIN_CHECKSUM
			server_checksum = (unsigned)COM_ParseHex( token );
#endif

			cl.model_draw[i-CS_MODELS] = re.RegisterModel ( model_name );

			if (cl.configstrings[i][0] == '*')
				cl.model_clip[i-CS_MODELS] = CM_InlineModel ( model_name );
			else
				cl.model_clip[i-CS_MODELS] = NULL;

			//
			// process any initialization commands
			//
			sz = strlen(model_name);
			if (sz > 3 && !Q_stricmp(&model_name[sz - 3], "def"))
			{
				int handle;

				handle = re.SINMDL_GetDefHandle(cl.model_draw[i - CS_MODELS]);
				if ( handle >= 0 )
				{
					sinmdl_cmd_t * cmds;

#ifdef SIN_CHECKSUM
					// Check the checksum
					client_checksum = SINMDL_Checksum( handle );

					if ( client_checksum != server_checksum )
					{
						Com_Printf( "Checksum invalid for model index %i\n", i-CS_MODELS );
						Com_Printf( "client checksum = %x, server checksum = %x\n", client_checksum, server_checksum );
						Com_Error (ERR_DROP,"CL_ParseConfigString: Model %s differs from server", model_name);
					}
#endif

					cmds = SINMDL_InitCommands_Client( handle );
					if ( cmds )
					{
						int j;
						for ( j = 0; j < cmds->num_cmds; j++ )
						{
							CL_CMDS_ProcessCommands( 0, handle, cmds->cmds[j].num_args, (const char **)cmds->cmds[j].args );
						}
					}
				}
			}
		}
	}
	else if (i >= CS_SOUNDS && i < CS_SOUNDS+MAX_SOUNDS)
	{
		if (cl.refresh_prepped)
			cl.sound_precache[i-CS_SOUNDS] = S_RegisterSound ( cl.configstrings[i] );
	}
	else if (i >= CS_IMAGES && i < CS_IMAGES+MAX_IMAGES)
	{
		if (cl.refresh_prepped)
			cl.image_precache[i-CS_IMAGES] = re.RegisterPic ( cl.configstrings[i] );
	}
	else if (i >= CS_PLAYERSKINS && i < CS_PLAYERSKINS+MAX_CLIENTS)
	{
		if (cl.refresh_prepped)
			CL_ParseClientinfo ( i-CS_PLAYERSKINS );
	}
	else if (i >= CS_BIKESKINS && i < CS_BIKESKINS+MAX_CLIENTS)
	{
		if (cl.refresh_prepped)
			CL_ParseBikeinfo ( i-CS_BIKESKINS );
	}
}


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

ACTION MESSAGES

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

/*
==================
CL_ParseStartSoundPacket
==================
*/
static void CL_ParseStartSoundPacket( void )
{
	vec3_t	pos_v;
	float	*pos;
	int		channel, ent;
	int		sound_num;
	float	volume;
	float	attenuation;
	int		flags;
	float	ofs;
	float	pitch;
	float	fadetime;
	int		sndflags;

	flags = MSG_ReadByte (&net_message);
	sound_num = MSG_ReadShort (&net_message);

	if (flags & SND_VOLUME)
		volume = MSG_ReadByte (&net_message) / 63.0;
	else
		volume = DEFAULT_SOUND_PACKET_VOLUME;

	if (flags & SND_ATTENUATION)
		attenuation = MSG_ReadByte (&net_message) / 63.0;
	else
		attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;

	if (flags & SND_OFFSET)
		ofs = MSG_ReadByte (&net_message) / 1000.0;
	else
		ofs = 0;

	if (flags & SND_ENT)
	{   // entity reletive
		channel = MSG_ReadShort( &net_message );
		ent = channel>>3;
		if (ent > MAX_EDICTS)
			Com_Error ( ERR_DROP,"CL_ParseStartSoundPacket: ent = %i", ent );

		channel &= 7;
	}
	else
	{
		ent = 0;
		channel = 0;
	}

	if (flags & SND_POS)
	{   // positioned in space
		MSG_ReadPos ( &net_message, pos_v );

		pos = pos_v;
	}
	else    // use entity number
		pos = NULL;

	if (flags & SND_PITCH)
		pitch = PKTPITCH_TO_PITCH( MSG_ReadByte ( &net_message ) );
	else
		pitch = DEFAULT_SOUND_PACKET_PITCH;
	if (flags & SND_FADE)
		fadetime = PKTFADE_TO_FADE( MSG_ReadByte ( &net_message ) );
	else
		fadetime = 0;
	if (flags & SND_FLAGS)
		sndflags = MSG_ReadByte ( &net_message );
	else
		sndflags = SOUND_SYNCH;

	if (!cl.sound_precache[sound_num])
		return;
	S_StartSoundEx( pos, ent, channel, cl.sound_precache[sound_num], volume, attenuation, ofs, pitch, fadetime, 0, NULL, sndflags );
}


void SHOWNET(const char *s)
{
	if (cl_shownet->value>=2)
		Com_Printf ( "%3i:%s\n", net_message.readcount-1, s );
}

/*
=====================
CL_ParseServerMessage
=====================
*/
void CL_ParseServerMessage( void )
{
	int cmd;
	const char      *s;
	int i;

//
// if recording demos, copy the message out
//
	if (cl_shownet->value == 1)
		Com_Printf ( "%i ",net_message.cursize );
	else if (cl_shownet->value >= 2)
		Com_Printf ( "------------------\n" );

//
// parse the message
//
	while (1)
	{
		if (net_message.readcount > net_message.cursize)
		{
			Com_Error ( ERR_DROP,"CL_ParseServerMessage: Bad server message" );
			break;
		}

		cmd = MSG_ReadByte (&net_message);
		if (cmd == -1)
		{
			SHOWNET( "END OF MESSAGE" );
			break;
		}

		if (cl_shownet->value>=2)
		{
			if (!svc_strings[cmd])
				Com_Printf ( "%3i:BAD CMD %i\n", net_message.readcount-1,cmd );
			else
				SHOWNET( svc_strings[cmd] );
		}

		// other commands
		switch (cmd)
		{
		default:
			Com_DPrintf(DEVELOPER_MSG_NET, "Error %d\n",cmd);
			Com_Error ( ERR_DROP,"CL_ParseServerMessage: Illegible server message\n" );
			break;

		case svc_nop:
//			Com_Printf ("svc_nop\n");
			break;

		case svc_disconnect:
			Com_Error (ERR_DISCONNECT,"Server disconnected\n");
			break;

		case svc_reconnect:
			Com_Printf ("Server disconnected, reconnecting\n");
			if (cls.download)
			{
				//ZOID, close download
				fclose (cls.download);
				cls.download = NULL;
			}
			if (Com_ServerState())
				cls.state = ca_disconnected;
			else
				cls.state = ca_connecting;
			cls.connect_time = -99999;	// CL_CheckForResend() will fire immediately
			break;

		case svc_print:
			i = MSG_ReadByte (&net_message);
			s = MSG_ReadString (&net_message);

			if (i == PRINT_CHAT)
			{
				S_StartLocalSound ("misc/talk.wav");
				con.ormask = 128;
			}

			if ((strstr (s, "!version")) &&
				(cls.lastSpamTime == 0 || cls.realtime > cls.lastSpamTime + 300000)) /* FS: 5 minutes */
			{
				cls.spamTime = cls.realtime + 1500; /* FS: 1.5 second delay */
			}

			if ( i == PRINT_TALK )
			{
				CL_Talk(s);
			}
			else
			{
				Com_Printf ("%s", s);
			}

			con.ormask = 0;
			break;

		case svc_centerprint:
			CONSOLE_CenterPrint (MSG_ReadString (&net_message));
			break;

		case svc_stufftext:
			s = MSG_ReadString (&net_message);
			Com_DPrintf (DEVELOPER_MSG_NET, "stufftext: %s\n", s);
			Cbuf_AddText (s);
			break;

		case svc_serverdata:
			Cbuf_Execute ();	// make sure any stuffed commands are done
			CL_ParseServerData ();
			break;

		case svc_configstring:
			CL_ParseConfigString ();
			break;

		case svc_sound:
			CL_ParseStartSoundPacket();
			break;

		case svc_spawnbaseline:
			CL_ParseBaseline ();
			break;

		case svc_temp_entity:
			CL_ParseTEnt ();
			break;

		case svc_download:
			CL_ParseDownload ();
			break;

		case svc_frame:
			CL_ParseFrame ();
			break;

		case svc_inventory:
			CL_ParseInventory ();
			break;

		case svc_layout:
			s = MSG_ReadString ( &net_message );
			strncpy ( cl.layout, s, sizeof(cl.layout)-1 );
			break;

		case svc_spawnconbaseline:
			CL_ParseConsoleSingleState();
			break;

		case svc_spawnsurfbaseline:
			CL_ParseSurfaceStates( true );
			break;

		case svc_console_command:
			s = MSG_ReadString ( &net_message );
			CONSOLE_ParseCommandString ( s,svc_console_command );
			break;

#ifdef SEPERATE_SURFSANDCONSOLE
		case svc_extradata:
			CL_ParseExtraData ();
			break;
#endif

		case svc_consolestates:
		case svc_surfacestates:
		case svc_playerinfo:
		case svc_packetentities:
		case svc_deltapacketentities:
			Com_Error ( ERR_DROP, "Out of place frame data" );
			break;
		}
	}

	CL_AddNetgraph ();

	//
	// we don't know if it is ok to save a demo message until
	// after we have parsed the frame
	//
	if (cls.demorecording && !cls.demowaiting)
		CL_WriteDemoMessage ();
}
