summaryrefslogtreecommitdiff
path: root/source/cl_parse.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/cl_parse.c')
-rw-r--r--source/cl_parse.c1581
1 files changed, 1581 insertions, 0 deletions
diff --git a/source/cl_parse.c b/source/cl_parse.c
new file mode 100644
index 0000000..07e85cf
--- /dev/null
+++ b/source/cl_parse.c
@@ -0,0 +1,1581 @@
+/*
+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 "cl_local.h"
+
+//=============================================================================
+
+
+static const char *const validExts[] = {
+ ".pcx", ".wal", ".tga", ".jpg", ".png",
+ ".md2", ".md3", ".sp2", ".wav", ".dm2",
+ ".bsp", ".txt", ".loc", ".ent", NULL
+};
+
+/*
+===============
+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 i, 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 file with invalid path.\n" );
+ return qtrue;
+ }
+
+ // a trivial attempt to prevent malicious server from
+ // uploading trojan executables to the win32 client
+ ext = COM_FileExtension( filename );
+ for( i = 0; validExts[i]; i++ ) {
+ if( !strcmp( ext, validExts[i] ) ) {
+ break;
+ }
+ }
+ if( !validExts[i] ) {
+ Com_WPrintf( "Refusing to download file with invalid extension.\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, MAX_QPATH );
+
+ if( strlen( cls.downloadtempname ) >= MAX_QPATH - 5 ) {
+ strcpy( cls.downloadtempname + MAX_QPATH - 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 inline void CL_ParseDeltaEntity( server_frame_t *frame,
+ int newnum,
+ entity_state_t *old,
+ int bits )
+{
+ entity_state_t *state;
+
+ if( frame->numEntities == MAX_PACKET_ENTITIES ) {
+ Com_Error( ERR_DROP, "CL_ParseDeltaEntity: MAX_PACKET_ENTITIES exceeded" );
+ }
+
+ state = &cl.entityStates[cl.numEntityStates & PARSE_ENTITIES_MASK];
+ cl.numEntityStates++;
+ frame->numEntities++;
+
+ if( cl_shownet->integer > 2 ) {
+ MSG_ShowDeltaEntityBits( bits );
+ }
+
+ MSG_ParseDeltaEntity( old, state, newnum, bits );
+}
+
+/*
+==================
+CL_ParsePacketEntities
+==================
+*/
+static void CL_ParsePacketEntities( server_frame_t *oldframe,
+ server_frame_t *frame )
+{
+ int newnum;
+ int bits;
+ entity_state_t *oldstate;
+ int oldindex, oldnum;
+ int i;
+
+ frame->firstEntity = cl.numEntityStates;
+ frame->numEntities = 0;
+
+ // delta from the entities present in oldframe
+ oldindex = 0;
+ oldstate = NULL;
+ if( !oldframe ) {
+ oldnum = 99999;
+ } else {
+ if( oldindex >= oldframe->numEntities ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstEntity + oldindex;
+ oldstate = &cl.entityStates[i & PARSE_ENTITIES_MASK];
+ 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->numEntities ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstEntity + oldindex;
+ oldstate = &cl.entityStates[i & PARSE_ENTITIES_MASK];
+ 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" );
+ }
+
+ oldindex++;
+
+ if( oldindex >= oldframe->numEntities ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstEntity + oldindex;
+ oldstate = &cl.entityStates[i & PARSE_ENTITIES_MASK];
+ 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->numEntities ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstEntity + oldindex;
+ oldstate = &cl.entityStates[i & PARSE_ENTITIES_MASK];
+ 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->numEntities ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstEntity + oldindex;
+ oldstate = &cl.entityStates[i & PARSE_ENTITIES_MASK];
+ 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;
+ cl.frameflags = 0;
+
+ if( !cls.demoplayback ) {
+ VectorScale( cl.frame.ps.pmove.origin, 0.125f, cl.predicted_origin );
+ VectorCopy( cl.frame.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, surpressed;
+ server_frame_t frame, *oldframe;
+ player_state_t *from;
+ int length;
+
+ memset( &frame, 0, sizeof( frame ) );
+
+ cl.frameflags = 0;
+
+ surpressed = 0;
+ 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();
+
+ surpressed = bits & SURPRESSCOUNT_MASK;
+ if( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) {
+ cl.frameflags |= surpressed;
+ } else if( surpressed ) {
+ cl.frameflags |= FF_SURPRESSED;
+ }
+ 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 ) {
+ surpressed = MSG_ReadByte();
+ if( surpressed ) {
+ cl.frameflags |= FF_SURPRESSED;
+ }
+ }
+ }
+
+ frame.number = currentframe;
+ frame.delta = deltaframe;
+
+ if( cls.netchan && cls.netchan->dropped ) {
+ cl.frameflags |= FF_SERVERDROP;
+ }
+
+ /* 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.\n" );
+ cl.frameflags |= FF_BADFRAME;
+ } else if( oldframe->number != deltaframe ) {
+ // The frame that the server did the delta from
+ // is too old, so we can't reconstruct it properly.
+ Com_DPrintf( "Delta frame was never received or too old.\n" );
+ cl.frameflags |= FF_OLDFRAME;
+ } else if( !oldframe->valid ) {
+ // should never happen
+ Com_DPrintf( "Delta from invalid frame.\n" );
+ cl.frameflags |= FF_BADFRAME;
+ } else if( cl.numEntityStates - oldframe->firstEntity >
+ MAX_PARSE_ENTITIES - MAX_PACKET_ENTITIES )
+ {
+ Com_DPrintf( "Delta entities too old.\n" );
+ cl.frameflags |= FF_OLDENT;
+ } else {
+ frame.valid = qtrue; // valid delta parse
+ }
+ if( !frame.valid && cl.frame.valid && cls.demoplayback ) {
+ Com_DPrintf( "Trying to recover from the broken demo recording.\n" );
+ oldframe = &cl.frame;
+ from = &oldframe->ps;
+ frame.valid = qtrue;
+ }
+ } else {
+ oldframe = NULL;
+ from = NULL;
+ frame.valid = qtrue; // uncompressed frame
+ //if( !cls.demowaiting ) {
+ cl.frameflags |= FF_NODELTA;
+ //}
+ //cls.demowaiting = qfalse; // we can start recording now
+ }
+
+ // 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 {
+ 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 );
+ }
+
+ frame.clientNum = cl.clientNum;
+
+ // parse playerstate
+ bits = MSG_ReadShort();
+ if( cls.serverProtocol > PROTOCOL_VERSION_DEFAULT ) {
+ MSG_ParseDeltaPlayerstate_Enhanced( from, &frame.ps, bits, extraflags );
+ if( cl_shownet->integer > 2 ) {
+ MSG_ShowDeltaPlayerstateBits_Enhanced( bits );
+ Com_Printf( "\n" );
+ }
+ if( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) {
+ // parse clientNum
+ if( extraflags & EPS_CLIENTNUM ) {
+ frame.clientNum = MSG_ReadByte();
+ } else if( oldframe ) {
+ frame.clientNum = oldframe->clientNum;
+ }
+ }
+ } else {
+ MSG_ParseDeltaPlayerstate_Default( from, &frame.ps, bits );
+ if( cl_shownet->integer > 2 ) {
+ MSG_ShowDeltaPlayerstateBits_Default( bits );
+ Com_Printf( "\n" );
+ }
+ }
+ if( !frame.ps.fov ) {
+ // fail out early to prevent spurious errors later
+ Com_Error( ERR_DROP, "CL_ParseFrame: bad fov" );
+ }
+
+ // 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 ) {
+ int rtt = 0;
+ if( cls.netchan ) {
+ int seq = cls.netchan->incoming_acknowledged & CMD_MASK;
+ rtt = cls.realtime - cl.history[seq].realtime;
+ }
+ Com_Printf( "%3i: frame:%i delta:%i rtt:%i\n",
+ msg_read.readcount - 1, frame.number, frame.delta, rtt );
+ }
+
+ if( !frame.valid ) {
+ cl.frame.valid = qfalse;
+ return; // do not change anything
+ }
+
+ cl.oldframe = cl.frame;
+ cl.frame = frame;
+ cl.serverTime = frame.number * 100;
+
+ // getting a valid frame message ends the connection process
+ if( cls.state == ca_precached ) {
+ CL_SetActiveState();
+ }
+
+ CL_DeltaFrame();
+
+ CL_CheckPredictionError();
+}
+
+
+/*
+=====================================================================
+
+ SERVER CONNECTING MESSAGES
+
+=====================================================================
+*/
+
+/*
+================
+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];
+
+ if( strcmp( ci->cinfo, s ) ) {
+ CL_LoadClientinfo( ci, s );
+ }
+}
+
+
+static void CL_ConfigString( int index, const char *string ) {
+ int length, maxlength;
+
+ if( index >= CS_STATUSBAR && index < CS_AIRACCEL ) {
+ maxlength = MAX_QPATH * ( CS_AIRACCEL - index );
+ } else {
+ maxlength = MAX_QPATH;
+ }
+ length = strlen( string );
+ if( length >= maxlength ) {
+ Com_Error( ERR_DROP, "%s: index %d overflowed: %d chars",
+ __func__, index, length );
+ }
+
+ strcpy( cl.configstrings[index], string );
+
+ // do something apropriate
+
+ if( index == CS_MAXCLIENTS ) {
+ cl.maxclients = atoi( string );
+ return;
+ }
+ if( index == CS_MODELS + 1 ) {
+ if( length <= 9 ) {
+ Com_Error( ERR_DROP, "%s: bad world model: %s", __func__, string );
+ }
+ strcpy( cl.mapname, string + 5 ); // skip "maps/"
+ cl.mapname[length - 9] = 0; // cut off ".bsp"
+ return;
+ }
+ if (index >= CS_LIGHTS && index < CS_LIGHTS+MAX_LIGHTSTYLES) {
+ CL_SetLightstyle (index - CS_LIGHTS);
+ return;
+ }
+
+ if( cls.state < ca_precached ) {
+ return;
+ }
+
+ if (index >= CS_MODELS && index < CS_MODELS+MAX_MODELS) {
+ cl.model_draw[index-CS_MODELS] = ref.RegisterModel (string);
+ if (*string == '*')
+ cl.model_clip[index-CS_MODELS] = CM_InlineModel (&cl.cm, string);
+ else
+ cl.model_clip[index-CS_MODELS] = 0;
+ } else if (index >= CS_SOUNDS && index < CS_SOUNDS+MAX_MODELS) {
+ cl.sound_precache[index-CS_SOUNDS] = S_RegisterSound (string);
+ } else if (index >= CS_IMAGES && index < CS_IMAGES+MAX_MODELS) {
+ cl.image_precache[index-CS_IMAGES] = ref.RegisterPic (string);
+ } else if (index >= CS_PLAYERSKINS && index < CS_PLAYERSKINS+MAX_CLIENTS) {
+ CL_ParseClientinfo (index-CS_PLAYERSKINS);
+ } else if( index == CS_AIRACCEL && !cl.pmp.qwmod ) {
+ cl.pmp.airaccelerate = atoi( string ) ? qtrue : qfalse;
+ }
+}
+
+static void CL_ParseGamestate( void ) {
+ int index, bits;
+ char *string;
+
+ while( 1 ) {
+ index = MSG_ReadShort();
+ if( index == MAX_CONFIGSTRINGS ) {
+ break;
+ }
+ if( index < 0 || index >= MAX_CONFIGSTRINGS ) {
+ Com_Error( ERR_DROP, "%s: bad configstring index: %d",
+ __func__, index );
+ }
+
+ string = MSG_ReadString();
+
+ CL_ConfigString( index, string );
+ }
+
+ while( 1 ) {
+ index = MSG_ParseEntityBits( &bits );
+ if( !index ) {
+ break;
+ }
+ if( index < 1 || index >= MAX_EDICTS ) {
+ Com_Error( ERR_DROP, "%s: bad baseline index: %d",
+ __func__, index );
+ }
+ MSG_ParseDeltaEntity( NULL, &cl.baselines[index], index, bits );
+ }
+}
+
+/*
+==================
+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 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];
+
+ strcpy( ci->cinfo, s );
+
+ // isolate the player's name
+ strcpy( ci->name, s );
+ 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_ParseConfigString
+================
+*/
+static void CL_ParseConfigString (void) {
+ int i;
+ char *s;
+
+ i = MSG_ReadShort ();
+ if (i < 0 || i >= MAX_CONFIGSTRINGS)
+ Com_Error( ERR_DROP, "%s: bad index: %d", __func__, i );
+
+ s = MSG_ReadString();
+
+ if( cl_shownet->integer > 2 ) {
+ Com_Printf( " %i \"%s\"\n", i, Q_FormatString( s ) );
+ }
+
+ CL_ConfigString( i, s );
+}
+
+
+/*
+=====================================================================
+
+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, "%s: read past end of message", __func__ );
+ }
+
+ 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, "%s: bad ent: %d", __func__, 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, "%s: neither SND_ENT nor SND_POS set", __func__ );
+ }
+ // use entity number
+ pos = NULL;
+ }
+
+ if( cl.sound_precache[sound_num] ) {
+ S_StartSound( pos, ent, channel, cl.sound_precache[sound_num],
+ volume, attenuation, ofs );
+ }
+}
+
+/*
+=====================
+CL_ParseReconnect
+=====================
+*/
+static void CL_ParseReconnect( void ) {
+ if( cls.demoplayback ) {
+ return;
+ }
+
+ S_StopAllSounds();
+
+ 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_CheckForPCMD
+====================
+*/
+static void CL_CheckForPCMD( const char *string ) {
+ char * p;
+
+ if ( cls.demoplayback ) {
+ return;
+ }
+
+ p = strstr( string, ": " );
+ if ( !p ) {
+ return;
+ }
+
+ if ( strncmp( p + 2, "!version", 8 ) ) {
+ return;
+ }
+
+ if ( cl.replyTime && cls.realtime - cl.replyTime < 120000 ) {
+ return;
+ }
+
+ cl.replyTime = cls.realtime + 1024 + ( rand() & 1023 );
+}
+
+
+/*
+=====================
+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 ) );
+ }
+
+ if( level != PRINT_CHAT ) {
+ Com_Printf( "%s", string );
+ Cmd_ExecTrigger( TRIG_CLIENT_PRINT, string );
+ return;
+ }
+
+ CL_CheckForPCMD( string );
+
+ Cmd_ExecTrigger( TRIG_CLIENT_CHAT, string );
+
+ /* disable notify, if specified */
+ if( !cl_chat_notify->integer ) {
+ Con_SkipNotify( qtrue );
+ }
+
+ if( cl_chat_clear->integer ) {
+ Q_ClearStr( string, string, MAX_STRING_CHARS - 1 );
+ Q_strcat( string, MAX_STRING_CHARS, "\n" );
+ }
+
+ Com_Printf( S_COLOR_ALT "%s", string );
+
+ Con_SkipNotify( qfalse );
+
+ 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();
+ }
+}
+
+static void CL_ParseZPacket( void ) {
+#if USE_ZLIB
+ sizebuf_t temp;
+ byte buffer[MAX_MSGLEN];
+ unsigned inlen, outlen;
+
+ if( msg_read.data != msg_read_buffer ) {
+ Com_Error( ERR_DROP, "%s: recursively entered", __func__ );
+ }
+
+ inlen = MSG_ReadShort();
+ if( msg_read.readcount + inlen > msg_read.cursize ) {
+ Com_Error( ERR_DROP, "%s: read past end of message", __func__ );
+ }
+
+ outlen = MSG_ReadShort();
+ if( outlen > MAX_MSGLEN ) {
+ Com_Error( ERR_DROP, "%s: invalid output length", __func__ );
+ }
+
+ inflateReset( &cls.z );
+
+ cls.z.next_in = msg_read.data + msg_read.readcount;
+ cls.z.avail_in = inlen;
+ cls.z.next_out = buffer;
+ cls.z.avail_out = outlen;
+ if( inflate( &cls.z, Z_FINISH ) != Z_STREAM_END ) {
+ Com_Error( ERR_DROP, "%s: inflate() failed: %s", __func__, cls.z.msg );
+ }
+
+ msg_read.readcount += inlen;
+
+ temp = msg_read;
+ SZ_Init( &msg_read, buffer, outlen );
+ msg_read.cursize = outlen;
+
+ CL_ParseServerMessage();
+
+ msg_read = temp;
+#else
+ Com_Error( ERR_DROP, "Compressed server packet received, "
+ "but no zlib support linked in." );
+#endif
+}
+
+/*
+=====================
+CL_ParseServerMessage
+=====================
+*/
+void CL_ParseServerMessage( void ) {
+ int cmd, readcount;
+ 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" );
+ }
+
+ readcount = msg_read.readcount;
+
+ 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 ) {
+ MSG_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();
+ continue;
+
+ case svc_frame:
+ CL_ParseFrame( extrabits );
+ continue;
+
+ case svc_inventory:
+ CL_ParseInventory();
+ break;
+
+ case svc_layout:
+ CL_ParseLayout();
+ break;
+
+ case svc_zpacket:
+ CL_ParseZPacket();
+ continue;
+
+ case svc_gamestate:
+ CL_ParseGamestate();
+ continue;
+ }
+
+ // copy protocol invariant stuff
+ if( cls.demorecording ) {
+ SZ_Write( &cls.demobuff, msg_read.data + readcount,
+ msg_read.readcount - readcount );
+ }
+ }
+
+//
+// if recording demos, write the message out
+//
+ if( cls.demorecording ) {
+ CL_WriteDemoMessage( &cls.demobuff );
+ }
+}
+