/*
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_parse.c  -- parse a message received from the server

#include "client.h"

#define CL_ShowSVC( cmd ) do { \
	Com_Printf( "%3i:%s\n", msg_read.readcount - 1, \
        MSG_ServerCommandString( cmd ) ); \
    } while( 0 )

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

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

Returns qtrue if the file exists, otherwise it attempts
to start a download from the server.
===============
*/
qboolean CL_CheckOrDownloadFile( const char *path ) {
	fileHandle_t	f;
	int		length;
	char filename[MAX_QPATH];
	char *ext;

	Q_strncpyz( filename, path, sizeof( filename ) );
	Q_strlwr( filename );

	length = strlen( filename );
	if( !length
		|| !Q_ispath( filename[0] )
		|| !Q_ispath( filename[ length - 1 ] )
		|| strchr( filename, '\\' )
		|| strchr( filename, ':' )
		|| !strchr( filename, '/' )
		|| strstr( filename, ".." ) )
	{
		Com_WPrintf( "Refusing to download an invalid path.\n" );
		return qtrue;
	}

	// trivial attempt to prevent some malicious server
	// uploading trojan horses to the client
	// only reject stuff loadable by quake2
	ext = COM_FileExtension( filename );
	if( !strcmp( ext, ".exe" )
		|| !strcmp( ext, ".dll" )
		|| !strcmp( ext, ".so" ) )
	{
		Com_WPrintf( "Refusing to download executable file.\n" );
		return qtrue;
	}

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

	strcpy( cls.downloadname, filename );

	// 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, sizeof( cls.downloadtempname ) );
	
	if( strlen( cls.downloadtempname ) >= sizeof( cls.downloadtempname ) - 5 ) {
		strcpy( cls.downloadtempname + sizeof( cls.downloadtempname ) - 5, ".tmp" );
	} else {
		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
	length = FS_FOpenFile( cls.downloadtempname, &f, FS_MODE_RDWR|FS_FLAG_RAW );
	if( length < 0 && f ) {
		Com_WPrintf( "Couldn't determine size of %s\n", cls.downloadtempname );
		FS_FCloseFile( f );
		f = 0;
	}
	if( f ) { // it exists
		cls.download = f;
		// give the server an offset to start the download
		Com_Printf( "Resuming %s\n", cls.downloadname );
		CL_ClientCommand( va( "download \"%s\" %i", cls.downloadname, length ) );
	} else {
		Com_Printf( "Downloading %s\n", cls.downloadname );
		CL_ClientCommand( va( "download \"%s\"", cls.downloadname ) );
	}

	cls.downloadnumber++;

	return qfalse;
}

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

Request a download from the server
===============
*/
void CL_Download_f( void ) {
	char *path;

	if( cls.state < ca_connected ) {
		Com_Printf( "Must be connected to a server.\n" );
		return;
	}

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

	path = Cmd_Argv( 1 );

	if( !allow_download->integer ) {
		Com_Printf( "Couldn't download '%s', downloading is locally disabled.\n", path );
		return;
	}

	if( cls.downloadtempname[0] ) {
		Com_Printf( "Already downloading.\n" );
		if( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) {
			Com_Printf( "Try using 'stopdl' command to abort the download.\n" );
		}
		return;
	}

	if( FS_LoadFile( path, NULL ) != -1 ) {	
		Com_Printf( "File '%s' already exists.\n", path );
		return;
	}

	CL_CheckOrDownloadFile( path );
}


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

A download message has been received from the server
=====================
*/
static void CL_ParseDownload( void ) {
	int		size, percent;

	if( !cls.downloadtempname[0] ) {
		Com_Error( ERR_DROP, "Server sending download, but no download was requested" );
	}

	// read the data
	size = MSG_ReadShort();
	percent = MSG_ReadByte();
	if( size == -1 ) {
		if( !percent ) {
			Com_Printf( "Server was unable to send this file.\n" );
		} else {
			Com_Printf( "Server stopped the download.\n" );
		}
		if( cls.download ) {
			// if here, we tried to resume a file but the server said no
			FS_FCloseFile( cls.download );
			cls.download = 0;
		}
		cls.downloadtempname[0] = 0;
		cls.downloadname[0] = 0;
		CL_RequestNextDownload();
		return;
	}

	if( size < 0 ) {
		Com_Error( ERR_DROP, "CL_ParseDownload: bad size: %d", size );
	}

	if( msg_read.readcount + size > msg_read.cursize ) {
		Com_Error( ERR_DROP, "CL_ParseDownload: read past end of message" );
	}

	// open the file if not opened yet
	if( !cls.download ) {
		FS_FOpenFile( cls.downloadtempname, &cls.download, FS_MODE_WRITE|FS_FLAG_RAW );

		if( !cls.download ) {
			msg_read.readcount += size;
			Com_WPrintf( "Failed to open '%s' for writing\n", cls.downloadtempname );
			cls.downloadtempname[0] = 0;
			cls.downloadname[0] = 0;
			CL_RequestNextDownload();
			return;
		}
	}

	FS_Write( msg_read.data + msg_read.readcount, size, cls.download );
	msg_read.readcount += size;
	
	if( percent != 100 ) {
		// request next block
		// change display routines by zoid
		cls.downloadpercent = percent;

		CL_ClientCommand( "nextdl" );
	} else {
		FS_FCloseFile( cls.download );

		// rename the temp file to it's final name
		if( !FS_RenameFile( cls.downloadtempname, cls.downloadname ) ) {
			Com_WPrintf( "Failed to rename %s to %s\n",
				cls.downloadtempname, cls.downloadname );
		}

		Com_Printf( "Downloaded successfully.\n" );

		cls.downloadtempname[0] = 0;
		cls.downloadname[0] = 0;

		cls.download = 0;
		cls.downloadpercent = 0;

		// get another file if needed
		CL_RequestNextDownload();
	}
}

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

  DELTA FRAME PARSING

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


/*
==================
CL_ParseDeltaEntity
==================
*/
static void CL_ParseDeltaEntity( clientFrame_t *frame, int newnum, entity_state_t *old, int bits ) {
	entity_state_t	*state;
	int i;

	i = cl.numEntityStates & PARSE_ENTITIES_MASK;
	state = &cl.entityStates[i];
	cl.numEntityStates++;
	if( frame->numEntityStates == MAX_PACKET_ENTITIES ) {
		Com_Error( ERR_DROP, "CL_ParseDeltaEntity: MAX_PACKET_ENTITIES exceeded" );
	}
	frame->numEntityStates++;

	if( cl_shownet->integer > 2 ) {
		MSG_ShowDeltaEntityBits( bits );
	}

	MSG_ParseDeltaEntity( old, state, newnum, bits );
}

/*
==================
CL_ParsePacketEntities
==================
*/
static void CL_ParsePacketEntities( clientFrame_t *oldframe, clientFrame_t *frame ) {
	int			newnum;
	int			bits;
	entity_state_t	*oldstate;
	int			oldindex, oldnum;
	int i;

	frame->firstEntityState = cl.numEntityStates;
	frame->numEntityStates = 0;

	// delta from the entities present in oldframe
	oldindex = 0;
	oldstate = NULL;
	if( !oldframe ) {
		oldnum = 99999;
	} else {
		if( oldindex >= oldframe->numEntityStates ) {
			oldnum = 99999;
		} else {
			i = oldframe->firstEntityState + oldindex;
			i &= PARSE_ENTITIES_MASK;
			oldstate = &cl.entityStates[i];
			oldnum = oldstate->number;
		}
	}

	while( 1 ) {
		newnum = MSG_ParseEntityBits( &bits );
		if( newnum < 0 || newnum >= MAX_EDICTS ) {
			Com_Error( ERR_DROP, "ParsePacketEntities: bad number %i",
				newnum );
		}

		if( msg_read.readcount > msg_read.cursize ) {
			Com_Error( ERR_DROP, "ParsePacketEntities: end of message" );
		}

		if( !newnum ) {
			break;
		}

		while( oldnum < newnum ) {
			// one or more entities from the old packet are unchanged
			if( cl_shownet->integer > 2 ) {
				Com_Printf( "   unchanged: %i\n", oldnum );
			}
			CL_ParseDeltaEntity( frame, oldnum, oldstate, 0 );
			
			oldindex++;

			if( oldindex >= oldframe->numEntityStates ) {
				oldnum = 99999;
			} else {
				i = oldframe->firstEntityState + oldindex;
				i &= PARSE_ENTITIES_MASK;
				oldstate = &cl.entityStates[i];
				oldnum = oldstate->number;
			}
		}

		if( bits & U_REMOVE ) {	
			// the entity present in oldframe is not in the current frame
			if( cl_shownet->integer > 2 ) {
				Com_Printf( "   remove: %i\n", newnum );
			}
			if( oldnum != newnum ) {
				Com_DPrintf( "U_REMOVE: oldnum != newnum\n" );
			}
			if( !oldframe ) {
				Com_Error( ERR_DROP, "U_REMOVE: NULL oldframe" );
			}
			if( !oldstate ) {
				Com_Error( ERR_DROP, "U_REMOVE: NULL oldstate" );
			}

			oldindex++;

			if( oldindex >= oldframe->numEntityStates ) {
				oldnum = 99999;
			} else {
				i = oldframe->firstEntityState + oldindex;
				i &= PARSE_ENTITIES_MASK;
				oldstate = &cl.entityStates[i];
				oldnum = oldstate->number;
			}
			continue;
		}

		if( oldnum == newnum ) {	
			// delta from previous state
			if( cl_shownet->integer > 2 ) {
				Com_Printf( "   delta: %i ", newnum );
			}
			CL_ParseDeltaEntity( frame, newnum, oldstate, bits );
			if( cl_shownet->integer > 2 ) {
				Com_Printf( "\n" );
			}

			oldindex++;

			if( oldindex >= oldframe->numEntityStates ) {
				oldnum = 99999;
			} else {
				i = oldframe->firstEntityState + oldindex;
				i &= PARSE_ENTITIES_MASK;
				oldstate = &cl.entityStates[i];
				oldnum = oldstate->number;
			}
			continue;
		}

		if( oldnum > newnum ) {	
			// delta from baseline
			if( cl_shownet->integer > 2 ) {
				Com_Printf( "   baseline: %i ", newnum );
			}
			CL_ParseDeltaEntity( frame, newnum, &cl.baselines[newnum], bits );
			if( cl_shownet->integer > 2 ) {
				Com_Printf( "\n" );
			}
			continue;
		}

	}

	// any remaining entities in the old frame are copied over
	while( oldnum != 99999 ) {	
		// one or more entities from the old packet are unchanged
		if( cl_shownet->integer > 2 ) {
			Com_Printf( "   unchanged: %i\n", oldnum );
		}
		CL_ParseDeltaEntity( frame, oldnum, oldstate, 0 );
		
		oldindex++;

		if( oldindex >= oldframe->numEntityStates ) {
			oldnum = 99999;
		} else {
			i = oldframe->firstEntityState + oldindex;
			i &= PARSE_ENTITIES_MASK;
			oldstate = &cl.entityStates[i];
			oldnum = oldstate->number;
		}
	}
}

/*
================
CL_SetActiveState
================
*/
static void CL_SetActiveState( void ) {
	cl.time = cl.serverTime; // set time, needed for demos
	cls.state = ca_active;
	cl.oldframe.valid = qfalse;

    Com_DPrintf( "Going from ca_precached to ca_active\n" );

	if( !cls.demoplayback ) {
        VectorScale( cl.frame.ps.ps.pmove.origin, 0.125f, cl.predicted_origin );
		VectorCopy( cl.frame.ps.ps.viewangles, cl.predicted_angles );
	}
	
	SCR_ClearLagometer();
	SCR_ClearChatHUD_f();
	SCR_EndLoadingPlaque ();	// get rid of loading plaque
	Con_Close();				// close console

    Cmd_ExecTrigger( TRIG_CLIENT_SYSTEM, "enterlevel" );

	Cvar_Set( "cl_paused", "0" );
}

/*
================
CL_ParseFrame
================
*/
static void CL_ParseFrame( int extrabits ) {
	uint32 bits, extraflags;
	int currentframe, deltaframe, delta;
	clientFrame_t frame, *oldframe;
	playerStateEx_t *from;
	int length, ping;
	
	memset( &frame, 0, sizeof( frame ) );

	extraflags = 0;
	if( cls.serverProtocol > PROTOCOL_VERSION_DEFAULT ) {
		bits = MSG_ReadLong();

		currentframe = bits & FRAMENUM_MASK;
		delta = bits >> FRAMENUM_BITS;

		if( delta == 31 ) {
			deltaframe = -1;
		} else {
			deltaframe = currentframe - delta;
		}

		bits = MSG_ReadByte();

		cl.surpressCount = bits & SURPRESSCOUNT_MASK;
		extraflags = ( extrabits << 4 ) | ( bits >> SURPRESSCOUNT_BITS );
	} else {
		currentframe = MSG_ReadLong();
		deltaframe = MSG_ReadLong();

		/* BIG HACK to let old demos continue to work */
		if( cls.serverProtocol != PROTOCOL_VERSION_OLD ) {
			cl.surpressCount = MSG_ReadByte();
		}
	}

	frame.serverFrame = currentframe;
	frame.deltaFrame = deltaframe;

	/* If the frame is delta compressed from data that we
	 * no longer have available, we must suck up the rest of
	 * the frame, but not use it, then ask for a non-compressed
	 * message
	 */
	if( deltaframe > 0 ) {
		oldframe = &cl.frames[deltaframe & UPDATE_MASK];
		from = &oldframe->ps;
		if( deltaframe == currentframe ) {
            /* old buggy q2 servers still cause this on map change */
			Com_DPrintf( "Delta from current frame (should not happen).\n" );
		} else if( !oldframe->valid ) {	
			/* should never happen */
			Com_WPrintf( "Delta from invalid frame (should not happen).\n" );
		} else if( oldframe->serverFrame != deltaframe ) {
			/* The frame that the server did the delta from
			 * is too old, so we can't reconstruct it properly.
			 */
			Com_WPrintf( "Delta frame too old.\n" );
		} else if(
			cl.numEntityStates -
			oldframe->firstEntityState >
			MAX_PARSE_ENTITIES - MAX_PACKET_ENTITIES )
		{
			Com_WPrintf( "Delta entityStates too old.\n" );
		} else {
			frame.valid = qtrue;	/* valid delta parse */
		}
	} else {
		oldframe = NULL;
		from = NULL;
		frame.valid = qtrue;		/* uncompressed frame */
	}

	/* read areabits */
	length = MSG_ReadByte();
	if( length ) {
		if( length < 0 || msg_read.readcount + length > msg_read.cursize ) {
			Com_Error( ERR_DROP, "CL_ParseFrame: read past end of message" );
		}
		if( length > sizeof( frame.areabits ) ) {
			Com_Error( ERR_DROP, "CL_ParseFrame: invalid areabits length" );
		}
		MSG_ReadData( frame.areabits, length );
		frame.areabytes = length;
	} else {
		memset( frame.areabits, 255, sizeof( frame.areabits ) );
		frame.areabytes = 0;
	}

	if( cls.serverProtocol <= PROTOCOL_VERSION_DEFAULT ) {
		if( MSG_ReadByte() != svc_playerinfo ) {
			Com_Error( ERR_DROP, "CL_ParseFrame: not playerinfo" );
		}
	}

	if( cl_shownet->integer > 2 ) {
		Com_Printf( "%3i:playerinfo\n", msg_read.readcount - 1 );
	}

	/* parse playerstate */
	bits = MSG_ReadShort();
	if( cls.serverProtocol > PROTOCOL_VERSION_DEFAULT ) {
		bits |= extraflags << PS_BITS; /* pass extra bits */
		MSG_ParseDeltaPlayerstate_Enhanced( from, &frame.ps, bits );
		if( cl_shownet->integer > 2 ) {
			MSG_ShowDeltaPlayerstateBits_Enhanced( bits );
			Com_Printf( "\n" );
		}
		if( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
			frame.ps.clientNum = cl.clientNum;
		}
	} else {
		MSG_ParseDeltaPlayerstate_Default( ( player_state_t * )from, &frame.ps.ps, bits );
		if( cl_shownet->integer > 2 ) {
			MSG_ShowDeltaPlayerstateBits_Default( bits );
			Com_Printf( "\n" );
		}
		frame.ps.clientNum = cl.clientNum;
	}
	if( !frame.ps.ps.fov ) {
        Com_Error( ERR_DROP, "CL_ParseFrame: bad fov" );
    }

	frame.ps.number = cl.clientNum;

	/* parse packetentities */
	if( cls.serverProtocol <= PROTOCOL_VERSION_DEFAULT ) {
		if( MSG_ReadByte() != svc_packetentities ) {
			Com_Error( ERR_DROP, "CL_ParseFrame: not packetentities" );
		}
	}

	if( cl_shownet->integer > 2 ) {
		Com_Printf( "%3i:packetentities\n", msg_read.readcount - 1 );
	}

	CL_ParsePacketEntities( oldframe, &frame );

	/* save the frame off in the backup array for
	 * later delta comparisons
	 */
	cl.frames[currentframe & UPDATE_MASK] = frame;

	if( cl_shownet->integer > 2 ) {
		if( cls.netchan ) {
			ping = cls.realtime - cl.history[cls.netchan->incoming_acknowledged & CMD_MASK].realtime;
		} else {
			ping = 999;
		}
		Com_Printf( "%3i: frame:%i  delta:%i  ping:%i\n",
			msg_read.readcount - 1, frame.serverFrame, frame.deltaFrame, ping );
	}

	if( !frame.valid ) {
		cl.frame.valid = qfalse;
		return; /* do not change anything */
	}

	cl.oldframe = cl.frame;
	cl.frame = frame;
	cl.serverTime = frame.serverFrame * 100;

	// getting a valid frame message ends the connection process
	if( cls.state == ca_precached ) {
		CL_SetActiveState();
	}

	if( cl.frame.deltaFrame <= 0 ) {
		cls.demowaiting = qfalse; // we can start recording now
	}

	CL_DeltaFrame();

	CL_CheckPredictionError();

}


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

  SERVER CONNECTING MESSAGES

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

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

	Cbuf_Execute();		// make sure any stuffed commands are done
	
//
// wipe the client_state_t struct
//
	CL_ClearState();

// parse protocol version number
	i = MSG_ReadLong();
	cl.servercount = MSG_ReadLong();
	cl.attractLoop = MSG_ReadByte();

	Com_DPrintf( "Serverdata packet received (protocol=%d, servercount=%d, attractLoop=%d)\n",
		i, cl.servercount, cl.attractLoop );

	/* TODO: rework this mess someday... */
	if( cls.serverProtocol != i ) {
		serverDemo =
			sv_running->integer != ss_dead &&
			sv_running->integer != ss_game &&
			sv_running->integer != ss_broadcast;
		if( cls.serverProtocol != PROTOCOL_VERSION_OLD &&
			cls.serverProtocol != PROTOCOL_VERSION_DEFAULT && serverDemo )
		{
			Com_Error( ERR_DROP,
				"Local server returned protocol version %i, not %i\n"
				"To play a demo, use 'demo' command, not 'demomap'.",
				i, cls.serverProtocol );
		}
		
		if( cls.demoplayback || serverDemo ) { 
			// BIG HACK to let demos from release work with the 3.0x patch!!!
			if( i == PROTOCOL_VERSION_OLD ) {
				Com_DPrintf( "Using protocol %i for compatibility with old demos\n",
					PROTOCOL_VERSION_OLD );
			} else if( i == PROTOCOL_VERSION_MVD ) {
				Com_Error( ERR_DROP,
					"This demo uses MVD protocol.\n"
					"To play a MVD, use 'mvdplay' command, not 'demo'." );
			} else if(
				i != PROTOCOL_VERSION_DEFAULT &&
				i != PROTOCOL_VERSION_R1Q2 &&
				i != PROTOCOL_VERSION_Q2PRO )
			{
				Com_Error( ERR_DROP,
					"Demo uses unsupported protocol version %i.\n"
					"Supported protocols are %i, %i and %i.",
					i, PROTOCOL_VERSION_DEFAULT, PROTOCOL_VERSION_R1Q2, PROTOCOL_VERSION_Q2PRO );
			}
		} else {
			if( i == PROTOCOL_VERSION_DEFAULT || i == PROTOCOL_VERSION_R1Q2 || i == PROTOCOL_VERSION_Q2PRO ) {
				Com_Error( ERR_DROP, "Requested protocol %i, but server returned %i (should not happen).",
					cls.serverProtocol, i );
			} else {
				Com_Error( ERR_DROP,
					"Server returned unsupported protocol version %i.\n"
					"Supported protocols are %i, %i and %i.",
					i, PROTOCOL_VERSION_DEFAULT, PROTOCOL_VERSION_R1Q2, PROTOCOL_VERSION_Q2PRO );
			}
		}
		cls.serverProtocol = i;
	}

	if( cl.attractLoop >= ATR_UNKNOWN ) {
		Com_Error( ERR_DROP, "CL_ParseServerData: bad attractLoop type %i", cl.attractLoop );
	}

	if( cl.attractLoop == ATR_SERVERRECORD ) {
		Com_Error( ERR_DROP, "Serverside recorded demos are not supported\n" );
	}

	// game directory
	str = MSG_ReadString();
	Q_strncpyz( cl.gamedir, str, sizeof( cl.gamedir ) );

	/* Never allow demos to change gamedir.
	 * Don't set gamedir if connected to local sever,
	 * since it was already done by SV_InitGame.
	 */
	if( !cls.demoplayback && !sv_running->integer ) {
		Cvar_UserSet( "game", cl.gamedir );
		if( FS_NeedRestart() ) {
			CL_RestartFilesystem();
		}
	}

	// parse player entity number
	cl.clientNum = MSG_ReadShort();

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

	cl.pmp.speedMultiplier = 1;
	cl.pmp.maxspeed = 300;
	cl.pmp.upspeed = 350;
	cl.pmp.friction = 6;
	cl.pmp.waterfriction = 1;
	cl.pmp.airaccelerate = 0;	
	cl.gametype = GT_DEATHMATCH;
	//cl.pmp.highprec=qtrue;
	if( cls.serverProtocol == PROTOCOL_VERSION_R1Q2 ) {
		i = MSG_ReadByte();
		if( i ) {
			Com_Error( ERR_DROP, "'Enhanced' R1Q2 servers are not supported" );
		}
		i = MSG_ReadShort();
		if( i != PROTOCOL_VERSION_R1Q2_MINOR ) {
			if( cls.demoplayback ) {
				Com_WPrintf( "Demo uses different minor R1Q2 protocol version (%i instead of %i).\n"
					"It may not play back properly.\n", i, PROTOCOL_VERSION_R1Q2_MINOR );
			} else {
				if( i < PROTOCOL_VERSION_R1Q2_MINOR ) {
					Com_Error( ERR_DROP, "Server uses OLDER minor R1Q2 protocol version (%i < %i).\n"
						"Try reconnecting using original Quake2 protocol.",
							i, PROTOCOL_VERSION_R1Q2_MINOR );
				} else {
					Com_Error( ERR_DROP, "Server uses NEWER minor R1Q2 protocol version (%i > %i).\n"
						"Consider updating your client or try reconnecting using original Quake2 protocol.",
							i, PROTOCOL_VERSION_R1Q2_MINOR );
				}
			}
		}
		i = MSG_ReadByte();
		if( i ) { /* seems to be no longer used */
			Com_DPrintf( "R1Q2 advancedDeltas enabled\n" );
		}
		cl.pmp.strafeHack = MSG_ReadByte();
		if( cl.pmp.strafeHack ) {
			Com_DPrintf( "R1Q2 strafeHack enabled\n" );
		}
		cl.pmp.speedMultiplier = 2;
	} else if( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) {
		i = MSG_ReadShort();
		if( i != PROTOCOL_VERSION_Q2PRO_MINOR ) {
			if( cls.demoplayback ) {
				Com_WPrintf( "Demo uses different minor Q2PRO protocol version (%i instead of %i).\n"
					"It may not play back properly.\n", i, PROTOCOL_VERSION_Q2PRO_MINOR );
			} else {
				if( i < PROTOCOL_VERSION_Q2PRO_MINOR ) {
					Com_Error( ERR_DROP, "Server uses OLDER minor Q2PRO protocol version (%i < %i).\n"
						"Try reconnecting using original Quake2 protocol or R1Q2 protocol.",
							i, PROTOCOL_VERSION_Q2PRO_MINOR );
				} else {
					Com_Error( ERR_DROP, "Server uses NEWER minor Q2PRO protocol version (%i > %i).\n"
						"Consider updating your client or try reconnecting using original Quake2 protocol or R1Q2 protocol.",
							i, PROTOCOL_VERSION_Q2PRO_MINOR );
				}
			}
		}
		cl.gametype = MSG_ReadByte();
		cl.pmp.strafeHack = MSG_ReadByte();
		cl.pmp.qwmod = MSG_ReadByte(); //atu QWMod
		cl.pmp.speedMultiplier = 2;		

		if( cl.pmp.strafeHack ) {
			Com_DPrintf( "Q2PRO strafeHack enabled\n" );
		}
		if( cl.pmp.qwmod ) {
			Com_DPrintf( "Q2PRO QWMod enabled\n" );

			cl.pmp.maxspeed = 320;
			cl.pmp.upspeed = ((cl.pmp.qwmod == 2) ? 310 : 350);
			cl.pmp.friction = 4;
			cl.pmp.waterfriction = 4;
			cl.pmp.airaccelerate = qtrue;
		}
	}
	

	if( cl.clientNum == -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 */
		Con_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" );
		Con_Printf( S_COLOR_ALT "%s\n\n", str );

        /* make sure clientNum is in range */
        if( ( unsigned )cl.clientNum >= MAX_CLIENTS ) {
            cl.clientNum = CLIENTNUM_NONE;
        }
	}

}

/*
==================
CL_ParseBaseline
==================
*/
static void CL_ParseBaseline( void ) {
	int				bits;
	int				newnum;

	newnum = MSG_ParseEntityBits( &bits );
	if( newnum < 1 || newnum >= MAX_EDICTS ) {
		Com_Error( ERR_DROP, "CL_ParseBaseline: bad entity number %i", newnum );
	}
	MSG_ParseDeltaEntity( NULL, &cl.baselines[newnum], newnum, bits );
}

/*
================
CL_LoadClientinfo

================
*/
void CL_LoadClientinfo( clientinfo_t *ci, char *s, int playerNum ) {
	int i;
	char		*t;
	char		model_name[MAX_QPATH];
	char		skin_name[MAX_QPATH];
	char		model_filename[MAX_QPATH];
	char		skin_filename[MAX_QPATH];
	char		weapon_filename[MAX_QPATH];

	Q_strncpyz( ci->cinfo, s, sizeof( ci->cinfo ) );
	s = ci->cinfo; // just to make string operations safe

	// isolate the player's name
	Q_strncpyz( ci->name, s, sizeof( ci->name ) );
	t = strstr( s, "\\" );
	if( t ) {
		ci->name[ t - s ] = 0;
		s = t + 1;
	}

	if( cl_noskins->integer || *s == 0 ) {
	    strcpy( model_filename, "players/male/tris.md2" );
		strcpy( weapon_filename, "players/male/weapon.md2" );
	    strcpy( skin_filename, "players/male/grunt.pcx" );
		strcpy( ci->iconname, "/players/male/grunt_i.pcx" );
		ci->model = ref.RegisterModel( model_filename );
		memset( ci->weaponmodel, 0, sizeof( ci->weaponmodel ) );
		ci->weaponmodel[0] = ref.RegisterModel( weapon_filename );
		ci->skin = ref.RegisterSkin( skin_filename );
		ci->icon = ref.RegisterPic( ci->iconname );
	} else {
		strcpy( model_name, s );

		// isolate the model name
		t = strstr( model_name, "/" );
		if( !t )
			t = strstr( model_name, "\\" );
		if( !t )
			t = model_name;
		*t = 0;

		// isolate the skin name
		strcpy( skin_name, t + 1 );

		// model file
		Com_sprintf( model_filename, sizeof( model_filename ),
            "players/%s/tris.md2", model_name );
		ci->model = ref.RegisterModel( model_filename );
		if( !ci->model && Q_stricmp( model_name, "male" ) ) {
			strcpy( model_name, "male" );
			Com_sprintf( model_filename, sizeof( model_filename ),
                "players/male/tris.md2" );
			ci->model = ref.RegisterModel( model_filename );
		}

		// skin file
		Com_sprintf( skin_filename, sizeof( skin_filename ),
            "players/%s/%s.pcx", model_name, skin_name );
		ci->skin = ref.RegisterSkin( skin_filename );

		// if we don't have the skin and the model was female,
		// see if athena skin exists
        if( !ci->skin && !Q_stricmp( model_name, "female" ) ) {
            strcpy( skin_name, "athena" );
            Com_sprintf( skin_filename, sizeof( skin_filename ),
                "players/%s/%s.pcx", model_name, skin_name );
            ci->skin = ref.RegisterSkin( skin_filename );
        }

		// if we don't have the skin and the model wasn't male,
		// see if the male has it (this is for CTF's skins)
 		if( !ci->skin && Q_stricmp( model_name, "male" ) ) {
			// change model to male
			strcpy( model_name, "male" );
			Com_sprintf( model_filename, sizeof( model_filename ),
                "players/male/tris.md2" );
			ci->model = ref.RegisterModel( model_filename );

			// see if the skin exists for the male model
			Com_sprintf( skin_filename, sizeof( skin_filename ),
                "players/%s/%s.pcx", model_name, skin_name );
			ci->skin = ref.RegisterSkin( skin_filename );
		}

		// if we still don't have a skin, it means that the male model
        // didn't have it, so default to grunt
		if( !ci->skin ) {
			// see if the skin exists for the male model
			Com_sprintf( skin_filename, sizeof( skin_filename ),
                "players/%s/grunt.pcx", model_name );
			ci->skin = ref.RegisterSkin( skin_filename );
		}

		// weapon file
		for( i = 0; i < cl.numWeaponModels; i++ ) {
			Com_sprintf( weapon_filename, sizeof( weapon_filename ),
                "players/%s/%s", model_name, cl.weaponModels[i] );
			ci->weaponmodel[i] = ref.RegisterModel( weapon_filename );
			if( !ci->weaponmodel[i] && strcmp( model_name, "cyborg" ) == 0 ) {
				// try male
				Com_sprintf( weapon_filename, sizeof( weapon_filename ),
                    "players/male/%s", cl.weaponModels[i] );
				ci->weaponmodel[i] = ref.RegisterModel( weapon_filename );
			}
			if( !cl_vwep->integer )
				break; // only one when vwep is off
		}

		// icon file
		Com_sprintf( ci->iconname, sizeof( ci->iconname ),
            "/players/%s/%s_i.pcx", model_name, skin_name );
		ci->icon = ref.RegisterPic( ci->iconname );
	}

	// must have loaded all data types to be valud
	if( !ci->skin || !ci->icon || !ci->model || !ci->weaponmodel[0] ) {
		ci->skin = 0;
		ci->icon = 0;
		ci->model = 0;
		ci->weaponmodel[0] = 0;
	}

}

/*
================
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, player );
}


/*
================
CL_ParseConfigString
================
*/
static void CL_ParseConfigString (void) {
	int		i;
	char	*s;
	char	olds[MAX_QPATH];
	int		length, maxlength;

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

	Q_strncpyz( olds, cl.configstrings[i], sizeof( olds ) );

	s = MSG_ReadString();

	if( cl_shownet->integer > 2 ) {
		Com_Printf( "    %i \"%s\"\n", i, Q_FormatString( s ) );
	}

	length = strlen( s );
    if( i >= CS_STATUSBAR && i < CS_AIRACCEL ) {
        maxlength = MAX_QPATH * ( CS_AIRACCEL - i );
    } else {
        maxlength = MAX_QPATH;
    }
    if( length >= maxlength ) {
		Com_Error( ERR_DROP, "CL_ParseConfigString: index %d overflowed", i );
    }

	strcpy( cl.configstrings[i], s );

	// do something apropriate 

	if( i == CS_MAXCLIENTS ) {
		cl.maxclients = atoi( cl.configstrings[CS_MAXCLIENTS] );
	} else if (i >= CS_LIGHTS && i < CS_LIGHTS+MAX_LIGHTSTYLES) {
		CL_SetLightstyle (i - CS_LIGHTS);
	} else if (i == CS_CDTRACK) {
		if (cls.state >= ca_precached)
			CDAudio_Play (atoi(cl.configstrings[CS_CDTRACK]), qtrue);
	} else if (i >= CS_MODELS && i < CS_MODELS+MAX_MODELS) {
		if( i == CS_MODELS + 1 ) {
			if( length <= 9 ) {
				Com_Error( ERR_DROP, "CL_ParseConfigString: bad world model %s", s );
			}
			strcpy( cl.mapname, s + 5 ); // skip "maps/"
			cl.mapname[length - 9] = 0; // cut off ".bsp"
		}
		if (cls.state >= ca_precached) {
			cl.model_draw[i-CS_MODELS] = ref.RegisterModel (cl.configstrings[i]);
			if (cl.configstrings[i][0] == '*')
				cl.model_clip[i-CS_MODELS] = CM_InlineModel (&cl.cm, cl.configstrings[i]);
			else
				cl.model_clip[i-CS_MODELS] = 0;
		}
	} else if (i >= CS_SOUNDS && i < CS_SOUNDS+MAX_MODELS) {
		if (cls.state >= ca_precached)
			cl.sound_precache[i-CS_SOUNDS] = S_RegisterSound (cl.configstrings[i]);
	} else if (i >= CS_IMAGES && i < CS_IMAGES+MAX_MODELS) {
		if (cls.state >= ca_precached)
			cl.image_precache[i-CS_IMAGES] = ref.RegisterPic (cl.configstrings[i]);
	} else if (i >= CS_PLAYERSKINS && i < CS_PLAYERSKINS+MAX_CLIENTS) {
		if (cls.state >= ca_precached && strcmp(olds, s))
			CL_ParseClientinfo (i-CS_PLAYERSKINS);
		
	} else if( i == CS_AIRACCEL && !cl.pmp.qwmod ) {
		cl.pmp.airaccelerate = atoi( s ) ? qtrue : qfalse;
	}
}


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

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;

	flags = MSG_ReadByte();
	sound_num = MSG_ReadByte();
	if( sound_num == -1 ) {
		Com_Error( ERR_DROP, "CL_ParseStartSoundPacket: read past end of message" );
	}

    if( flags & SND_VOLUME )
		volume = MSG_ReadByte() / 255.0;
	else
		volume = DEFAULT_SOUND_PACKET_VOLUME;
	
    if( flags & SND_ATTENUATION )
		attenuation = MSG_ReadByte() / 64.0;
	else
		attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;	

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

	if( flags & SND_ENT ) {	
		// entity relative
		channel = MSG_ReadShort(); 
		ent = channel >> 3;
		if( ent < 0 || ent >= MAX_EDICTS )
			Com_Error( ERR_DROP, "CL_ParseStartSoundPacket: bad ent: %d", ent );

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

	if( flags & SND_POS ) {
		// positioned in space
		MSG_ReadPos( pos_v );
		pos = pos_v;
	} else {
		if( !( flags & SND_ENT ) ) {
			Com_Error( ERR_DROP, "CL_ParseStartSoundPacket: neither SND_ENT nor SND_POS set" );
		}
		// use entity number
		pos = NULL;
	}

	if( !cl.sound_precache[sound_num] )
		return;

	S_StartSound( pos, ent, channel, cl.sound_precache[sound_num], volume, attenuation, ofs );
}

/*
=====================
CL_ParseReconnect
=====================
*/
static void CL_ParseReconnect( void ) {
	if( cls.demoplayback ) {
		return;
	}

	if ( cls.demorecording )
        CL_Stop_f();

	Com_Printf( "Server disconnected, reconnecting\n" );
	if( cls.download ) {
		FS_FCloseFile( cls.download );
		cls.download = 0;
	}

    Cmd_ExecTrigger( TRIG_CLIENT_SYSTEM, "changelevel" );

	cls.downloadtempname[0] = 0;
	cls.downloadname[0] = 0;

	CL_ClearState();
	cls.state = ca_challenging;
	cls.connect_time = -99999;	// CL_CheckForResend() will fire immediately
	cls.connectCount = 0;
}


/*
=====================
CL_ParsePrint
=====================
*/
static void CL_ParsePrint( void ) {
	int level;
	char *string;

	level = MSG_ReadByte();
	string = MSG_ReadString();

	if( cl_shownet->integer > 2 ) {
		Com_Printf( "    %i \"%s\"\n", level, Q_FormatString( string ) );
	}

	CL_CheckForPCMD( level, string );

	if( level != PRINT_CHAT ) {
		Com_Printf( "%s", string );
        Cmd_ExecTrigger( TRIG_CLIENT_PRINT, string );
		return;
	}

    Cmd_ExecTrigger( TRIG_CLIENT_CHAT, string );

	/* disable notify, if specified */
	if( !cl_chat_notify->integer ) {
		Con_AddToNotify( qfalse );
	}

	Com_Printf( S_COLOR_ALT "%s", string );

	/* re-enable notify */
	if( !cl_chat_notify->integer ) {
		Con_AddToNotify( qtrue );
	}

	SCR_AddToChatHUD( string );

	if( cl_chat_beep->integer ) {
		S_StartLocalSound( "misc/talk.wav" );
	}
	
}

/*
=====================
CL_ParseCenterPrint
=====================
*/
static void CL_ParseCenterPrint( void ) {
	char *string;

	string = MSG_ReadString();

	if( cl_shownet->integer > 2 ) {
		Com_Printf( "    \"%s\"\n", Q_FormatString( string ) );
	}

    SCR_CenterPrint( string );
}

/*
=====================
CL_ParseStuffText
=====================
*/
static void CL_ParseStuffText( void ) {
	char *string;

	string = MSG_ReadString();

	if( cl_shownet->integer > 2 ) {
		Com_Printf( "    \"%s\"\n", Q_FormatString( string ) );
	}

	if( cls.demoplayback &&
            strcmp( string, "precache\n" ) &&
            strcmp( string, "changing\n" ) &&
            strcmp( string, "reconnect\n" ) )
    {
		Com_DPrintf( "ignored stufftext: %s\n", string );
		return;
	}

	Com_DPrintf( "stufftext: %s\n", string );

	Cbuf_AddText( string );
}

/*
=====================
CL_ParseLayout
=====================
*/
static void CL_ParseLayout( void ) {
	char *string;

	string = MSG_ReadString();

	if( cl_shownet->integer > 2 ) {
		Com_Printf( "    \"%s\"\n", Q_FormatString( string ) );
	}
	
	Q_strncpyz( cl.layout, string, sizeof( cl.layout ) );
}

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

	for( i = 0; i < MAX_ITEMS; i++ ) {
		cl.inventory[i] = MSG_ReadShort();
	}
}


/*
=====================
CL_ParseServerMessage
=====================
*/
void CL_ParseServerMessage( void ) {
	int			cmd;
	int			extrabits;

	if( cl_shownet->integer == 1 ) {
		Com_Printf( "%i ", msg_read.cursize );
	} else if( cl_shownet->integer > 1 ) {
		Com_Printf( "------------------\n" );
	}

//
// parse the message
//
	while( 1 ) {
		if( msg_read.readcount > msg_read.cursize ) {
			Com_Error( ERR_DROP, "CL_ParseServerMessage: read past end of server message" );
		}

		if( ( cmd = MSG_ReadByte() ) == -1 ) {
			if( cl_shownet->integer > 1 ) {
				Com_Printf( "%3i:END OF MESSAGE\n", msg_read.readcount - 1 );
			}
			break;
		}

		extrabits = cmd >> SVCMD_BITS;
		cmd &= SVCMD_MASK;

		if( cl_shownet->integer > 1 ) {
			CL_ShowSVC( cmd );
		}
	
	// other commands
		switch( cmd ) {
		default:
			Com_Error( ERR_DROP, "CL_ParseServerMessage: illegible server message: %d", cmd );
			break;
			
		case svc_nop:
			break;
			
		case svc_disconnect:
			Com_Error( ERR_DISCONNECT, "Server disconnected" );
			break;

		case svc_reconnect:
			CL_ParseReconnect();
			return;

		case svc_print:
			CL_ParsePrint();
			break;
			
		case svc_centerprint:
			CL_ParseCenterPrint();
			break;
			
		case svc_stufftext:
			CL_ParseStuffText();
			break;
			
		case svc_serverdata:
			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_muzzleflash:
			CL_ParseMuzzleFlash();
			break;

		case svc_muzzleflash2:
			CL_ParseMuzzleFlash2();
			break;

		case svc_download:
			CL_ParseDownload();
			break;

		case svc_frame:
			CL_ParseFrame( extrabits );
			break;

		case svc_inventory:
			CL_ParseInventory();
			break;

		case svc_layout:
			CL_ParseLayout();
			break;

		case svc_playerinfo:
		case svc_packetentities:
		case svc_deltapacketentities:
			Com_Error( ERR_DROP, "CL_ParseServerMessage: out of place frame data" );
			break;

		case svc_zpacket:
			MSG_ParseZPacket( CL_ParseServerMessage );
			break;
		}
	}


}
