/* 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" /* ===================== CL_ParseDownload A download message has been received from the server ===================== */ static void CL_ParseDownload( void ) { int size, percent; byte *data; if( !cls.download.temp[0] ) { Com_Error( ERR_DROP, "%s: no download requested", __func__ ); } // read the data size = MSG_ReadShort(); percent = MSG_ReadByte(); if( size == -1 ) { CL_HandleDownload( NULL, size, percent ); return; } if( size < 0 ) { Com_Error( ERR_DROP, "%s: bad size: %d", __func__, size ); } if( msg_read.readcount + size > msg_read.cursize ) { Com_Error( ERR_DROP, "%s: read past end of message", __func__ ); } data = msg_read.data + msg_read.readcount; msg_read.readcount += size; CL_HandleDownload( data, size, percent ); } /* ===================================================================== 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, "%s: MAX_PACKET_ENTITIES exceeded", __func__ ); } state = &cl.entityStates[cl.numEntityStates & PARSE_ENTITIES_MASK]; cl.numEntityStates++; frame->numEntities++; #ifdef _DEBUG if( cl_shownet->integer > 2 && bits ) { MSG_ShowDeltaEntityBits( bits ); Com_LPrintf( PRINT_DEVELOPER, "\n" ); } #endif MSG_ParseDeltaEntity( old, state, newnum, bits, cl.esFlags ); } /* ================== 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, "%s: bad number: %d", __func__, newnum ); } if( msg_read.readcount > msg_read.cursize ) { Com_Error( ERR_DROP, "%s: read past end of message", __func__ ); } if( !newnum ) { break; } while( oldnum < newnum ) { // one or more entities from the old packet are unchanged SHOWNET( 3, " 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 SHOWNET( 2, " 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 SHOWNET( 2, " delta: %i ", newnum ); CL_ParseDeltaEntity( frame, newnum, oldstate, bits ); if( !bits ) { SHOWNET( 2, "\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 SHOWNET( 2, " baseline: %i ", newnum ); CL_ParseDeltaEntity( frame, newnum, &cl.baselines[newnum], bits ); if( !bits ) { SHOWNET( 2, "\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 SHOWNET( 3, " 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.serverdelta = cl.frame.number; cl.time = cl.servertime = 0; // set time, needed for demos cls.state = ca_active; cl.oldframe.valid = qfalse; cl.frameflags = 0; cl.putaway = qfalse; if( cls.netchan ) { cl.initialSeq = cls.netchan->outgoing_sequence; } // set initial cl.predicted_origin and cl.predicted_angles if( !cls.demo.playback ) { VectorScale( cl.frame.ps.pmove.origin, 0.125f, cl.predicted_origin ); VectorScale( cl.frame.ps.pmove.velocity, 0.125f, cl.predicted_velocity ); if( cl.frame.ps.pmove.pm_type < PM_DEAD && cls.serverProtocol > PROTOCOL_VERSION_DEFAULT ) { // enhanced servers don't send viewangles CL_PredictAngles(); } else { // just use what server provided VectorCopy( cl.frame.ps.viewangles, cl.predicted_angles ); } } SCR_EndLoadingPlaque (); // get rid of loading plaque SCR_LagClear(); Con_Close( qfalse ); // get rid of connection screen CL_UpdateFrameTimes(); if( !cls.demo.playback ) { EXEC_TRIGGER( cl_beginmapcmd ); Cmd_ExecTrigger( "#cl_enterlevel" ); } Cvar_Set( "cl_paused", "0" ); } /* ================ CL_ParseFrame ================ */ static void CL_ParseFrame( int extrabits ) { uint32_t 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 ) { if( surpressed & FF_CLIENTPRED ) { // CLIENTDROP is implied, don't draw both surpressed &= ~FF_CLIENTDROP; } 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( "%s: delta from current frame\n", __func__ ); 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( "%s: delta frame was never received or too old\n", __func__ ); cl.frameflags |= FF_OLDFRAME; } else if( !oldframe->valid ) { // should never happen Com_DPrintf( "%s: delta from invalid frame\n", __func__ ); cl.frameflags |= FF_BADFRAME; } else if( cl.numEntityStates - oldframe->firstEntity > MAX_PARSE_ENTITIES - MAX_PACKET_ENTITIES ) { Com_DPrintf( "%s: delta entities too old\n", __func__ ); cl.frameflags |= FF_OLDENT; } else { frame.valid = qtrue; // valid delta parse } if( !frame.valid && cl.frame.valid && cls.demo.playback ) { Com_DPrintf( "%s: recovering broken demo\n", __func__ ); 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, "%s: read past end of message", __func__ ); } if( length > sizeof( frame.areabits ) ) { Com_Error( ERR_DROP, "%s: invalid areabits length", __func__ ); } memcpy( frame.areabits, msg_read.data + msg_read.readcount, length ); msg_read.readcount += length; frame.areabytes = length; } else { frame.areabytes = 0; } if( cls.serverProtocol <= PROTOCOL_VERSION_DEFAULT ) { if( MSG_ReadByte() != svc_playerinfo ) { Com_Error( ERR_DROP, "%s: not playerinfo", __func__ ); } } SHOWNET( 2, "%3"PRIz":playerinfo\n", msg_read.readcount - 1 ); // parse playerstate bits = MSG_ReadShort(); if( cls.serverProtocol > PROTOCOL_VERSION_DEFAULT ) { MSG_ParseDeltaPlayerstate_Enhanced( from, &frame.ps, bits, extraflags ); #ifdef _DEBUG if( cl_shownet->integer > 2 ) { MSG_ShowDeltaPlayerstateBits_Enhanced( bits ); Com_LPrintf( PRINT_DEVELOPER, "\n" ); } #endif if( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) { // parse clientNum if( extraflags & EPS_CLIENTNUM ) { frame.clientNum = MSG_ReadByte(); } else if( oldframe ) { frame.clientNum = oldframe->clientNum; } } else { frame.clientNum = cl.clientNum; } } else { MSG_ParseDeltaPlayerstate_Default( from, &frame.ps, bits ); #ifdef _DEBUG if( cl_shownet->integer > 2 ) { MSG_ShowDeltaPlayerstateBits_Default( bits ); Com_LPrintf( PRINT_DEVELOPER, "\n" ); } #endif frame.clientNum = cl.clientNum; } if( !frame.ps.fov ) { // fail out early to prevent spurious errors later Com_Error( ERR_DROP, "%s: bad fov", __func__ ); } // parse packetentities if( cls.serverProtocol <= PROTOCOL_VERSION_DEFAULT ) { if( MSG_ReadByte() != svc_packetentities ) { Com_Error( ERR_DROP, "%s: not packetentities", __func__ ); } } SHOWNET( 2, "%3"PRIz":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; #ifdef _DEBUG 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].sent; } Com_LPrintf( PRINT_DEVELOPER, "%3"PRIz":frame:%d delta:%d rtt:%d\n", msg_read.readcount - 1, frame.number, frame.delta, rtt ); } #endif if( !frame.valid ) { cl.frame.valid = qfalse; return; // do not change anything } cl.oldframe = cl.frame; cl.frame = frame; // getting a valid frame message ends the connection process if( cls.state == ca_precached ) { CL_SetActiveState(); } CL_DeltaFrame(); CL_CheckPredictionError(); } /* ===================================================================== SERVER CONNECTING MESSAGES ===================================================================== */ static void CL_ParseConfigstring( int index ) { size_t len, maxlen; char *string; if( index < 0 || index >= MAX_CONFIGSTRINGS ) { Com_Error( ERR_DROP, "%s: bad index: %d", __func__, index ); } string = cl.configstrings[index]; maxlen = CS_SIZE( index ); len = MSG_ReadString( string, maxlen ); SHOWNET( 2, " %d \"%s\"\n", index, string ); if( len >= maxlen ) { Com_WPrintf( "%s: index %d overflowed: %"PRIz" > %"PRIz"\n", __func__, index, len, maxlen - 1 ); len = maxlen - 1; } if( cls.demo.recording && cls.demo.paused ) { Q_SetBit( cl.dcs, index ); } // do something apropriate if( index == CS_MAXCLIENTS ) { cl.maxclients = atoi( string ); return; } if( index == CS_MODELS + 1 ) { if( len <= 9 ) { Com_Error( ERR_DROP, "%s: bad world model: %s", __func__, string ); } memcpy( cl.mapname, string + 5, len - 9 ); // skip "maps/" cl.mapname[len - 9] = 0; // cut off ".bsp" return; } #if USE_LIGHTSTYLES if (index >= CS_LIGHTS && index < CS_LIGHTS+MAX_LIGHTSTYLES) { CL_SetLightStyle( index - CS_LIGHTS, string, len ); return; } #endif if( cls.state < ca_precached ) { return; } if (index >= CS_MODELS+2 && index < CS_MODELS+MAX_MODELS) { cl.model_draw[index-CS_MODELS] = R_RegisterModel (string); if (*string == '*') cl.model_clip[index-CS_MODELS] = BSP_InlineModel (cl.bsp, string); else cl.model_clip[index-CS_MODELS] = NULL; } 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] = R_RegisterPic (string); } else if (index >= CS_PLAYERSKINS && index < CS_PLAYERSKINS+MAX_CLIENTS) { CL_LoadClientinfo( &cl.clientinfo[index - CS_PLAYERSKINS], string ); } else if( index == CS_AIRACCEL && !cl.pmp.qwmode ) { cl.pmp.airaccelerate = atoi( string ) ? qtrue : qfalse; } } static void CL_ParseBaseline( int index, int bits ) { if( index < 1 || index >= MAX_EDICTS ) { Com_Error( ERR_DROP, "%s: bad index: %d", __func__, index ); } #ifdef _DEBUG if( cl_shownet->integer > 2 ) { MSG_ShowDeltaEntityBits( bits ); Com_LPrintf( PRINT_DEVELOPER, "\n" ); } #endif MSG_ParseDeltaEntity( NULL, &cl.baselines[index], index, bits, cl.esFlags ); } /* ================== CL_ParseGamestate Instead of wasting space for svc_configstring and svc_spawnbaseline bytes, entire game state is compressed into a single stream. ================== */ static void CL_ParseGamestate( void ) { int index, bits; while( msg_read.readcount < msg_read.cursize ) { index = MSG_ReadShort(); if( index == MAX_CONFIGSTRINGS ) { break; } CL_ParseConfigstring( index ); } while( msg_read.readcount < msg_read.cursize ) { index = MSG_ParseEntityBits( &bits ); if( !index ) { break; } CL_ParseBaseline( index, bits ); } } /* ================== CL_ParseServerData ================== */ static void CL_ParseServerData( void ) { char levelname[MAX_QPATH]; int i, protocol, attractloop; size_t len; Cbuf_Execute( &cl_cmdbuf ); // make sure any stuffed commands are done // wipe the client_state_t struct CL_ClearState(); // parse protocol version number protocol = MSG_ReadLong(); cl.servercount = MSG_ReadLong(); attractloop = MSG_ReadByte(); Com_DPrintf( "Serverdata packet received " "(protocol=%d, servercount=%d, attractloop=%d)\n", protocol, cl.servercount, attractloop ); // check protocol if( cls.serverProtocol != protocol ) { if( !cls.demo.playback ) { Com_Error( ERR_DROP, "Requested protocol version %d, but server returned %d.", cls.serverProtocol, protocol ); } // BIG HACK to let demos from release work with the 3.0x patch!!! if( protocol < PROTOCOL_VERSION_OLD || protocol > PROTOCOL_VERSION_Q2PRO ) { Com_Error( ERR_DROP, "Demo uses unsupported protocol version %d.", protocol ); } cls.serverProtocol = protocol; } // game directory len = MSG_ReadString( cl.gamedir, sizeof( cl.gamedir ) ); if( len >= sizeof( cl.gamedir ) ) { Com_Error( ERR_DROP, "Oversize gamedir string" ); } // never allow demos to change gamedir // do not change gamedir if connected to local sever either, // as it was already done by SV_InitGame, and changing it // here will not work since server is now running if( !cls.demo.playback && !sv_running->integer ) { // pretend it has been set by user, so that 'changed' hook // gets called and filesystem is restarted Cvar_UserSet( "game", cl.gamedir ); // protect it from modifications while we are connected fs_game->flags |= CVAR_ROM; } // parse player entity number cl.clientNum = MSG_ReadShort(); // get the full level name MSG_ReadString( levelname, sizeof( levelname ) ); // setup default pmove parameters PmoveInit( &cl.pmp ); // setup default frame times cl.frametime = 100; cl.framefrac = 0.01f; if( cls.serverProtocol == PROTOCOL_VERSION_R1Q2 ) { i = MSG_ReadByte(); if( i ) { Com_Error( ERR_DROP, "'Enhanced' R1Q2 servers are not supported" ); } i = MSG_ReadShort(); // for some reason, R1Q2 servers always report the highest protocol // version they support, while still using the lower version // client specified in the 'connect' packet. oh well... if( !R1Q2_SUPPORTED( i ) ) { Com_WPrintf( "R1Q2 server reports unsupported protocol version %d.\n" "Assuming it really uses our current client version %d.\n" "Things will break if it does not!\n", i, PROTOCOL_VERSION_R1Q2_CURRENT ); clamp( i, PROTOCOL_VERSION_R1Q2_MINIMUM, PROTOCOL_VERSION_R1Q2_CURRENT ); } Com_DPrintf( "Using minor R1Q2 protocol version %d\n", i ); cls.protocolVersion = i; MSG_ReadByte(); // used to be advanced deltas i = MSG_ReadByte(); if( i ) { Com_DPrintf( "R1Q2 strafejump hack enabled\n" ); cl.pmp.strafehack = qtrue; } if( cls.protocolVersion >= PROTOCOL_VERSION_R1Q2_LONG_SOLID ) { cl.esFlags |= MSG_ES_LONGSOLID; } cl.pmp.speedmult = 2; } else if( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) { i = MSG_ReadShort(); if( !Q2PRO_SUPPORTED( i ) ) { Com_Error( ERR_DROP, "Q2PRO server reports unsupported protocol version %d.\n" "Current client version is %d.", i, PROTOCOL_VERSION_Q2PRO_CURRENT ); } Com_DPrintf( "Using minor Q2PRO protocol version %d\n", i ); cls.protocolVersion = i; MSG_ReadByte(); // used to be gametype i = MSG_ReadByte(); if( i ) { Com_DPrintf( "Q2PRO strafejump hack enabled\n" ); cl.pmp.strafehack = qtrue; } i = MSG_ReadByte(); //atu QWMod if( i ) { Com_DPrintf( "Q2PRO QW mode enabled\n" ); PmoveEnableQW( &cl.pmp ); } cl.esFlags |= MSG_ES_UMASK; if( cls.protocolVersion >= PROTOCOL_VERSION_Q2PRO_LONG_SOLID ) { cl.esFlags |= MSG_ES_LONGSOLID; } if( cls.protocolVersion >= PROTOCOL_VERSION_Q2PRO_WATERJUMP_HACK ) { i = MSG_ReadByte(); if( i ) { Com_DPrintf( "Q2PRO waterjump hack enabled\n" ); cl.pmp.waterhack = qtrue; } } cl.pmp.speedmult = 2; cl.pmp.flyhack = qtrue; // fly hack is unconditionally enabled cl.pmp.flyfriction = 4; } if( cl.clientNum == -1 ) { // tell the server to advance to the next map / cinematic CL_ClientCommand( va( "nextserver %i\n", cl.servercount ) ); } 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\37" "\n\n" ); Com_SetColor( COLOR_ALT ); Com_Printf( "%s\n", levelname ); Com_SetColor( COLOR_NONE ); // make sure clientNum is in range if( cl.clientNum < 0 || cl.clientNum >= MAX_CLIENTS ) { cl.clientNum = CLIENTNUM_NONE; } } } /* ================ CL_ParsePlayerSkin Breaks up playerskin into name (optional), model and skin components. If model or skin are found to be invalid, replaces them with sane defaults. ================ */ void CL_ParsePlayerSkin( char *name, char *model, char *skin, const char *s ) { size_t len; char *t; // configstring parsing guarantees that playerskins can never // overflow, but still check the length to be entirely fool-proof len = strlen( s ); if( len >= MAX_QPATH ) { Com_Error( ERR_DROP, "%s: oversize playerskin", __func__ ); } // isolate the player's name t = strchr( s, '\\' ); if( t ) { len = t - s; strcpy( model, t + 1 ); } else { len = 0; strcpy( model, s ); } // copy the player's name if( name ) { memcpy( name, s, len ); name[len] = 0; } // isolate the model name t = strchr( model, '/' ); if( !t ) t = strchr( model, '\\' ); if( !t ) t = model; if( t == model ) goto default_model; *t++ = 0; // apply restrictions on skins if( cl_noskins->integer == 2 || !COM_IsPath( t ) ) goto default_skin; if( cl_noskins->integer || !COM_IsPath( model ) ) goto default_model; // isolate the skin name strcpy( skin, t ); return; default_skin: if( !Q_stricmp( model, "female" ) ) { strcpy( model, "female" ); strcpy( skin, "athena" ); } else { default_model: strcpy( model, "male" ); strcpy( skin, "grunt" ); } } /* ================ CL_LoadClientinfo ================ */ void CL_LoadClientinfo( clientinfo_t *ci, const char *s ) { int i; 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]; char icon_filename[MAX_QPATH]; CL_ParsePlayerSkin( ci->name, model_name, skin_name, s ); // model file Q_concat( model_filename, sizeof( model_filename ), "players/", model_name, "/tris.md2", NULL ); ci->model = R_RegisterModel( model_filename ); if( !ci->model && Q_stricmp( model_name, "male" ) ) { strcpy( model_name, "male" ); strcpy( model_filename, "players/male/tris.md2" ); ci->model = R_RegisterModel( model_filename ); } // skin file Q_concat( skin_filename, sizeof( skin_filename ), "players/", model_name, "/", skin_name, ".pcx", NULL ); ci->skin = R_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" ); strcpy( skin_filename, "players/female/athena.pcx" ); ci->skin = R_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" ); strcpy( model_filename, "players/male/tris.md2" ); ci->model = R_RegisterModel( model_filename ); // see if the skin exists for the male model Q_concat( skin_filename, sizeof( skin_filename ), "players/male/", skin_name, ".pcx", NULL ); ci->skin = R_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 strcpy( skin_name, "grunt" ); strcpy( skin_filename, "players/male/grunt.pcx" ); ci->skin = R_RegisterSkin( skin_filename ); } // weapon file for( i = 0; i < cl.numWeaponModels; i++ ) { Q_concat( weapon_filename, sizeof( weapon_filename ), "players/", model_name, "/", cl.weaponModels[i], NULL ); ci->weaponmodel[i] = R_RegisterModel( weapon_filename ); if( !ci->weaponmodel[i] && Q_stricmp( model_name, "male" ) ) { // try male Q_concat( weapon_filename, sizeof( weapon_filename ), "players/male/", cl.weaponModels[i], NULL ); ci->weaponmodel[i] = R_RegisterModel( weapon_filename ); } } // icon file Q_concat( icon_filename, sizeof( icon_filename ), "/players/", model_name, "/", skin_name, "_i.pcx", NULL ); ci->icon = R_RegisterPic( icon_filename ); strcpy( ci->model_name, model_name ); strcpy( ci->skin_name, skin_name ); // must have loaded all data types to be valid if( !ci->skin || !ci->icon || !ci->model || !ci->weaponmodel[0] ) { ci->skin = 0; ci->icon = 0; ci->model = 0; ci->weaponmodel[0] = 0; ci->model_name[0] = 0; ci->skin_name[0] = 0; } } /* ===================================================================== ACTION MESSAGES ===================================================================== */ tent_params_t te; mz_params_t mz; #ifdef _DEBUG // for debugging problems when out-of-date entity origin is referenced static void CL_CheckEntityPresent( int entnum, const char *what ) { centity_t *e = &cl_entities[entnum]; if( entnum == cl.frame.clientNum + 1 ) { return; // player entity = current } e = &cl_entities[entnum]; if( e->serverframe == cl.frame.number ) { return; // current } if( e->serverframe ) { Com_LPrintf( PRINT_DEVELOPER, "SERVER BUG: %s on entity %d last seen %d frames ago\n", what, entnum, cl.frame.number - e->serverframe ); } else { Com_LPrintf( PRINT_DEVELOPER, "SERVER BUG: %s on entity %d never seen before\n", what, entnum ); } } #endif static void CL_ParseTEntParams( void ) { te.type = MSG_ReadByte(); switch( te.type ) { case TE_BLOOD: case TE_GUNSHOT: case TE_SPARKS: case TE_BULLET_SPARKS: case TE_SCREEN_SPARKS: case TE_SHIELD_SPARKS: case TE_SHOTGUN: case TE_BLASTER: case TE_GREENBLOOD: case TE_BLASTER2: case TE_FLECHETTE: case TE_HEATBEAM_SPARKS: case TE_HEATBEAM_STEAM: case TE_MOREBLOOD: case TE_ELECTRIC_SPARKS: MSG_ReadPos( te.pos1 ); MSG_ReadDir( te.dir ); break; case TE_SPLASH: case TE_LASER_SPARKS: case TE_WELDING_SPARKS: case TE_TUNNEL_SPARKS: te.count = MSG_ReadByte(); MSG_ReadPos( te.pos1 ); MSG_ReadDir( te.dir ); te.color = MSG_ReadByte(); break; case TE_BLUEHYPERBLASTER: case TE_RAILTRAIL: case TE_BUBBLETRAIL: case TE_DEBUGTRAIL: case TE_BUBBLETRAIL2: case TE_BFG_LASER: MSG_ReadPos( te.pos1 ); MSG_ReadPos( te.pos2 ); break; case TE_GRENADE_EXPLOSION: case TE_GRENADE_EXPLOSION_WATER: case TE_EXPLOSION2: case TE_PLASMA_EXPLOSION: case TE_ROCKET_EXPLOSION: case TE_ROCKET_EXPLOSION_WATER: case TE_EXPLOSION1: case TE_EXPLOSION1_NP: case TE_EXPLOSION1_BIG: case TE_BFG_EXPLOSION: case TE_BFG_BIGEXPLOSION: case TE_BOSSTPORT: case TE_PLAIN_EXPLOSION: case TE_CHAINFIST_SMOKE: case TE_TRACKER_EXPLOSION: case TE_TELEPORT_EFFECT: case TE_DBALL_GOAL: case TE_WIDOWSPLASH: case TE_NUKEBLAST: MSG_ReadPos( te.pos1 ); break; case TE_PARASITE_ATTACK: case TE_MEDIC_CABLE_ATTACK: case TE_HEATBEAM: case TE_MONSTER_HEATBEAM: te.entity1 = MSG_ReadShort(); MSG_ReadPos( te.pos1 ); MSG_ReadPos( te.pos2 ); break; case TE_GRAPPLE_CABLE: te.entity1 = MSG_ReadShort(); MSG_ReadPos( te.pos1 ); MSG_ReadPos( te.pos2 ); MSG_ReadPos( te.offset ); break; case TE_LIGHTNING: te.entity1 = MSG_ReadShort(); te.entity2 = MSG_ReadShort(); MSG_ReadPos( te.pos1 ); MSG_ReadPos( te.pos2 ); break; case TE_FLASHLIGHT: MSG_ReadPos( te.pos1 ); te.entity1 = MSG_ReadShort(); break; case TE_FORCEWALL: MSG_ReadPos( te.pos1 ); MSG_ReadPos( te.pos2 ); te.color = MSG_ReadByte(); break; case TE_STEAM: te.entity1 = MSG_ReadShort(); te.count = MSG_ReadByte(); MSG_ReadPos( te.pos1 ); MSG_ReadDir( te.dir ); te.color = MSG_ReadByte(); te.entity2 = MSG_ReadShort(); if( te.entity1 != -1 ) { te.time = MSG_ReadLong(); } break; case TE_WIDOWBEAMOUT: te.entity1 = MSG_ReadShort(); MSG_ReadPos( te.pos1 ); break; default: Com_Error( ERR_DROP, "%s: bad type", __func__ ); } CL_ParseTEnt(); } static void CL_ParseMuzzleFlashParams( void ) { int entity, weapon; entity = MSG_ReadShort(); if( entity < 1 || entity >= MAX_EDICTS ) Com_Error( ERR_DROP, "%s: bad entity", __func__ ); weapon = MSG_ReadByte(); mz.silenced = weapon & MZ_SILENCED; mz.weapon = weapon & ~MZ_SILENCED; mz.entity = entity; #ifdef _DEBUG if( developer->integer ) { CL_CheckEntityPresent( entity, "muzzleflash" ); } #endif CL_ParseMuzzleFlash(); } static void CL_ParseMuzzleFlashParams2( void ) { int entity; entity = MSG_ReadShort(); if( entity < 1 || entity >= MAX_EDICTS ) Com_Error( ERR_DROP, "%s: bad entity", __func__ ); mz.weapon = MSG_ReadByte(); mz.entity = entity; CL_ParseMuzzleFlash2(); } /* ================== 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__ ); } #ifdef _DEBUG if( developer->integer ) { CL_CheckEntityPresent( ent, "sound" ); } #endif // use entity number pos = NULL; } SHOWNET( 2, " %s\n", cl.configstrings[CS_SOUNDS+sound_num] ); 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.demo.playback ) { Com_Error( ERR_DISCONNECT, "Server disconnected" ); } Com_Printf( "Server disconnected, reconnecting\n" ); // free netchan now to prevent `disconnect' // message from being sent to server if( cls.netchan ) { Netchan_Close( cls.netchan ); cls.netchan = NULL; } CL_Disconnect( ERR_RECONNECT ); cls.state = ca_challenging; cls.connect_time -= CONNECT_FAST; cls.connect_count = 0; CL_CheckForResend(); } #if USE_AUTOREPLY /* ==================== CL_CheckForVersion ==================== */ static void CL_CheckForVersion( const char *string ) { char *p; p = strstr( string, ": " ); if( !p ) { return; } if( strncmp( p + 2, "!version", 8 ) ) { return; } if( cl.reply_time && cls.realtime - cl.reply_time < 120000 ) { return; } cl.reply_time = cls.realtime; cl.reply_delta = 1024 + ( rand() & 1023 ); } #endif // attempt to scan out an IP address in dotted-quad notation and // add it into circular array of recent addresses static void CL_CheckForIP( const char *s ) { unsigned b1, b2, b3, b4, port; netadr_t *a; char *p; while( *s ) { if( sscanf( s, "%3u.%3u.%3u.%3u", &b1, &b2, &b3, &b4 ) == 4 && b1 < 256 && b2 < 256 && b3 < 256 && b4 < 256 ) { p = strchr( s, ':' ); if( p ) { port = strtoul( p + 1, NULL, 10 ); if( port < 1024 || port > 65535 ) { break; // privileged or invalid port } } else { port = PORT_SERVER; } a = &cls.recent_addr[cls.recent_head++ & RECENT_MASK]; a->type = NA_IP; a->ip.u8[0] = b1; a->ip.u8[1] = b2; a->ip.u8[2] = b3; a->ip.u8[3] = b4; a->port = BigShort( port ); break; } s++; } } /* ===================== CL_ParsePrint ===================== */ static void CL_ParsePrint( void ) { int level; char string[MAX_STRING_CHARS]; const char *fmt; level = MSG_ReadByte(); MSG_ReadString( string, sizeof( string ) ); SHOWNET( 2, " %i \"%s\"\n", level, string ); if( level != PRINT_CHAT ) { Com_Printf( "%s", string ); if( !cls.demo.playback ) { COM_strclr( string ); Cmd_ExecTrigger( string ); } return; } if( CL_CheckForIgnore( string ) ) { return; } #if USE_AUTOREPLY if( !cls.demo.playback ) { CL_CheckForVersion( string ); } #endif CL_CheckForIP( string ); // disable notify if( !cl_chat_notify->integer ) { Con_SkipNotify( qtrue ); } // filter text if( cl_chat_filter->integer ) { COM_strclr( string ); fmt = "%s\n"; } else { fmt = "%s"; } Com_LPrintf( PRINT_TALK, fmt, string ); Con_SkipNotify( qfalse ); #if USE_CHATHUD SCR_AddToChatHUD( string ); #endif // play sound if( cl_chat_sound->string[0] ) { S_StartLocalSound_( cl_chat_sound->string ); } } /* ===================== CL_ParseCenterPrint ===================== */ static void CL_ParseCenterPrint( void ) { char string[MAX_STRING_CHARS]; MSG_ReadString( string, sizeof( string ) ); SHOWNET( 2, " \"%s\"\n", string ); SCR_CenterPrint( string ); if( !cls.demo.playback ) { COM_strclr( string ); Cmd_ExecTrigger( string ); } } /* ===================== CL_ParseStuffText ===================== */ static void CL_ParseStuffText( void ) { char s[MAX_STRING_CHARS]; MSG_ReadString( s, sizeof( s ) ); SHOWNET( 2, " \"%s\"\n", s ); Cbuf_AddText( &cl_cmdbuf, s ); } /* ===================== CL_ParseLayout ===================== */ static void CL_ParseLayout( void ) { MSG_ReadString( cl.layout, sizeof( cl.layout ) ); SHOWNET( 2, " \"%s\"\n", cl.layout ); cl.putaway = qfalse; } /* ================ CL_ParseInventory ================ */ static void CL_ParseInventory( void ) { int i; for( i = 0; i < MAX_ITEMS; i++ ) { cl.inventory[i] = MSG_ReadShort(); } cl.putaway = qfalse; } static void CL_ParseZPacket( void ) { #if USE_ZLIB sizebuf_t temp; byte buffer[MAX_MSGLEN]; size_t 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 = ( uInt )inlen; cls.z.next_out = buffer; cls.z.avail_out = ( uInt )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 } static void CL_ParseSetting( void ) { uint32_t index, value; index = MSG_ReadLong(); value = MSG_ReadLong(); switch( index ) { #if USE_FPS case SVS_FPS: if( !value ) { value = 10; } cl.frametime = 1000 / value; cl.framefrac = value * 0.001f; break; #endif default: break; } } /* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage( void ) { int cmd, extrabits; size_t readcount; int index, bits; #ifdef _DEBUG if( cl_shownet->integer == 1 ) { Com_LPrintf( PRINT_DEVELOPER, "%"PRIz" ", msg_read.cursize ); } else if( cl_shownet->integer > 1 ) { Com_LPrintf( PRINT_DEVELOPER, "------------------\n" ); } #endif // // parse the message // while( 1 ) { if( msg_read.readcount > msg_read.cursize ) { Com_Error( ERR_DROP, "%s: read past end of server message", __func__ ); } readcount = msg_read.readcount; if( ( cmd = MSG_ReadByte() ) == -1 ) { SHOWNET( 1, "%3"PRIz":END OF MESSAGE\n", msg_read.readcount - 1 ); break; } extrabits = cmd >> SVCMD_BITS; cmd &= SVCMD_MASK; #ifdef _DEBUG if( cl_shownet->integer > 1 ) { MSG_ShowSVC( cmd ); } #endif // other commands switch( cmd ) { default: badbyte: Com_Error( ERR_DROP, "%s: illegible server message: %d", __func__, 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(); continue; case svc_configstring: index = MSG_ReadShort(); CL_ParseConfigstring( index ); break; case svc_sound: CL_ParseStartSoundPacket(); break; case svc_spawnbaseline: index = MSG_ParseEntityBits( &bits ); CL_ParseBaseline( index, bits ); break; case svc_temp_entity: CL_ParseTEntParams(); break; case svc_muzzleflash: CL_ParseMuzzleFlashParams(); break; case svc_muzzleflash2: CL_ParseMuzzleFlashParams2(); 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: if( cls.serverProtocol < PROTOCOL_VERSION_R1Q2 ) { goto badbyte; } CL_ParseZPacket(); continue; case svc_gamestate: if( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) { goto badbyte; } CL_ParseGamestate(); continue; case svc_setting: if( cls.serverProtocol < PROTOCOL_VERSION_R1Q2 ) { goto badbyte; } CL_ParseSetting(); continue; } // // if recording demos, copy off protocol invariant stuff // if( cls.demo.recording && !cls.demo.paused ) { size_t len = msg_read.readcount - readcount; // it is very easy to overflow standard 1390 bytes // demo frame with modern servers... attempt to preserve // reliable messages at least, assuming they come first if( cls.demo.buffer.cursize + len < cls.demo.buffer.maxsize ) { SZ_Write( &cls.demo.buffer, msg_read.data + readcount, len ); } else { cls.demo.messages_dropped++; } } } // // if recording demos, write the message out // if( cls.demo.recording && !cls.demo.paused ) { CL_WriteDemoMessage( &cls.demo.buffer ); } }