/* 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_ents.c -- entity parsing and management #include "client.h" /* ========================================================================= FRAME PARSING ========================================================================= */ /* ============= CL_ParseDeltaMatchState ============= */ void CL_ParseDeltaMatchState( msg_t *msg, frame_t *oldframe, frame_t *newframe ) { match_state_t *matchstate; int bitmask; // start from old state or 0 if none matchstate = &newframe->match; if( oldframe ) { *matchstate = oldframe->match; } else { memset( matchstate, 0, sizeof(match_state_t) ); } bitmask = MSG_ReadByte( msg ); if( bitmask & MATCHSTATE_FLAG_STATE ) matchstate->state = MSG_ReadByte( msg ); if( bitmask & MATCHSTATE_FLAG_TIMELIMIT ) { int timelimitmask = MSG_ReadLong( msg ); if( timelimitmask & MATCHSTATE_EXTENDEDTIME_BIT ) { matchstate->extendedtime = qtrue; timelimitmask &= ~MATCHSTATE_EXTENDEDTIME_BIT; } else { matchstate->extendedtime = qfalse; } matchstate->timelimit = timelimitmask; } if( bitmask & MATCHSTATE_FLAG_CLOCK_MSECS ) matchstate->clock_msecs = MSG_ReadByte( msg ); if( bitmask & MATCHSTATE_FLAG_CLOCK_SECS ) matchstate->clock_secs = MSG_ReadByte( msg ); if( bitmask & MATCHSTATE_FLAG_CLOCK_MINUTES ) matchstate->clock_mins = MSG_ReadShort( msg ); } /* =================== CL_ParsePlayerstate =================== */ void CL_ParsePlayerstate( msg_t *msg, frame_t *oldframe, frame_t *newframe ) { int flags; player_state_t *state; int i, b; int statbits; state = &newframe->playerState; // clear to old value before delta parsing if( oldframe ) *state = oldframe->playerState; else memset( state, 0, sizeof(*state) ); flags = MSG_ReadByte( msg ); if( flags & PS_MOREBITS1 ) { b = MSG_ReadByte( msg ); flags |= b<<8; } if( flags & PS_MOREBITS2 ) { b = MSG_ReadByte( msg ); flags |= b<<16; } if( flags & PS_MOREBITS3 ) { b = MSG_ReadByte( msg ); flags |= b<<24; } // // parse the pmove_state_t // if( flags & PS_M_TYPE ) state->pmove.pm_type = MSG_ReadByte( msg ); if( flags & PS_M_ORIGIN0 ) state->pmove.origin[0] = MSG_ReadInt3( msg ); if( flags & PS_M_ORIGIN1 ) state->pmove.origin[1] = MSG_ReadInt3( msg ); if( flags & PS_M_ORIGIN2 ) state->pmove.origin[2] = MSG_ReadInt3( msg ); if( flags & PS_M_VELOCITY0 ) state->pmove.velocity[0] = MSG_ReadInt3( msg ); if( flags & PS_M_VELOCITY1 ) state->pmove.velocity[1] = MSG_ReadInt3( msg ); if( flags & PS_M_VELOCITY2 ) state->pmove.velocity[2] = MSG_ReadInt3( msg ); if( flags & PS_M_TIME ) state->pmove.pm_time = MSG_ReadByte( msg ); if( flags & PS_M_FLAGS ) state->pmove.pm_flags = MSG_ReadShort( msg ); if( flags & PS_M_DELTA_ANGLES0 ) state->pmove.delta_angles[0] = MSG_ReadShort( msg ); if( flags & PS_M_DELTA_ANGLES1 ) state->pmove.delta_angles[1] = MSG_ReadShort( msg ); if( flags & PS_M_DELTA_ANGLES2 ) state->pmove.delta_angles[2] = MSG_ReadShort( msg ); if( flags & PS_EVENT ) state->event = MSG_ReadShort( msg ); if( flags & PS_VIEWANGLES ) { state->viewangles[0] = MSG_ReadAngle16( msg ); state->viewangles[1] = MSG_ReadAngle16( msg ); state->viewangles[2] = MSG_ReadAngle16( msg ); } if( flags & PS_M_GRAVITY ) state->pmove.gravity = MSG_ReadShort( msg ); if( flags & PS_FOV ) state->fov = MSG_ReadByte( msg ); if( flags & PS_POVNUM ) state->POVnum = MSG_ReadByte( msg ); if( flags & PS_VIEWHEIGHT ) state->viewheight = MSG_ReadChar( msg ); if( flags & PS_PMOVESTATS ) { for( i = 0 ; i < PM_STAT_SIZE ; i++ ) state->pmove.stats[i] = MSG_ReadShort( msg ); } if( flags & PS_WEAPONLIST ) { // parse weaponlist statbits = MSG_ReadShort( msg ); for( i = 0; i < MAX_WEAPLIST_STATS; i++ ) { if( statbits & (1<weaponlist[i][0] = MSG_ReadByte( msg ); state->weaponlist[i][1] = MSG_ReadByte( msg ); state->weaponlist[i][2] = MSG_ReadByte( msg ); } } } // parse stats statbits = MSG_ReadLong( msg ); for( i = 0; i < PS_MAX_STATS; i++ ) { if( statbits & (1<stats[i] = MSG_ReadShort( msg ); } } /* ================= CL_ParseEntityBits Returns the entity number and the header bits ================= */ int CL_ParseEntityBits( msg_t *msg, unsigned *bits ) { unsigned b, total; int number; total = MSG_ReadByte( msg ); if (total & U_MOREBITS1) { b = MSG_ReadByte( msg ); total |= b<<8; } if (total & U_MOREBITS2) { b = MSG_ReadByte( msg ); total |= b<<16; } if (total & U_MOREBITS3) { b = MSG_ReadByte( msg ); total |= b<<24; } if (total & U_NUMBER16) number = MSG_ReadShort( msg ); else number = MSG_ReadByte( msg ); *bits = total; return number; } /* ================== CL_ParseDelta Can go from either a baseline or a previous packet_entity ================== */ void CL_ParseDelta( msg_t *msg, entity_state_t *from, entity_state_t *to, int number, unsigned bits ) { // set everything to the state we are delta'ing from *to = *from; VectorCopy (from->origin, to->old_origin); to->number = number; if (bits & U_SOLID) to->solid = MSG_ReadShort( msg ); if (bits & U_MODEL) to->modelindex = MSG_ReadByte( msg ); if (bits & U_MODEL2) to->modelindex2 = MSG_ReadByte( msg ); if (bits & U_FRAME8) to->frame = MSG_ReadByte( msg ); if (bits & U_FRAME16) to->frame = MSG_ReadShort( msg ); if ((bits & U_SKIN8) && (bits & U_SKIN16)) //used for laser colors to->skinnum = MSG_ReadLong( msg ); else if (bits & U_SKIN8) to->skinnum = MSG_ReadByte( msg ); else if (bits & U_SKIN16) to->skinnum = MSG_ReadShort( msg ); if ( (bits & (U_EFFECTS8|U_EFFECTS16)) == (U_EFFECTS8|U_EFFECTS16) ) to->effects = MSG_ReadLong( msg ); else if (bits & U_EFFECTS8) to->effects = MSG_ReadByte( msg ); else if (bits & U_EFFECTS16) to->effects = MSG_ReadShort( msg ); if ( (bits & (U_RENDERFX8|U_RENDERFX16)) == (U_RENDERFX8|U_RENDERFX16) ) to->renderfx = MSG_ReadLong( msg ); else if (bits & U_RENDERFX8) to->renderfx = MSG_ReadByte( msg ); else if (bits & U_RENDERFX16) to->renderfx = MSG_ReadShort( msg ); if (bits & U_ORIGIN1) to->origin[0] = MSG_ReadCoord( msg ); if (bits & U_ORIGIN2) to->origin[1] = MSG_ReadCoord( msg ); if (bits & U_ORIGIN3) to->origin[2] = MSG_ReadCoord( msg ); if ( (bits & U_ANGLE1) && (to->solid == SOLID_BMODEL) ) to->angles[0] = MSG_ReadAngle16( msg ); else if (bits & U_ANGLE1) to->angles[0] = MSG_ReadAngle( msg ); if ( (bits & U_ANGLE2) && (to->solid == SOLID_BMODEL) ) to->angles[1] = MSG_ReadAngle16( msg ); else if (bits & U_ANGLE2) to->angles[1] = MSG_ReadAngle( msg ); if ( (bits & U_ANGLE3) && (to->solid == SOLID_BMODEL) ) to->angles[2] = MSG_ReadAngle16( msg ); else if (bits & U_ANGLE3) to->angles[2] = MSG_ReadAngle( msg ); if (bits & U_OLDORIGIN) MSG_ReadPos( msg, to->old_origin); if (bits & U_TYPE) { to->type = MSG_ReadByte( msg ); to->takedamage = to->type & ET_INVERSE; to->type &= ~ET_INVERSE; } if (bits & U_SOUND) to->sound = MSG_ReadByte( msg ); if ( bits & U_EVENT ) { int event; event = MSG_ReadByte( msg ); if ( event & EV_INVERSE ) { to->events[0] = event & ~EV_INVERSE; to->eventParms[0] = MSG_ReadByte( msg ); } else { to->events[0] = event; to->eventParms[0] = 0; } } else { to->events[0] = 0; to->eventParms[0] = 0; } if ( bits & U_EVENT2 ) { int event; event = MSG_ReadByte( msg ); if ( event & EV_INVERSE ) { to->events[1] = event & ~EV_INVERSE; to->eventParms[1] = MSG_ReadByte( msg ); } else { to->events[1] = event; to->eventParms[1] = 0; } } else { to->events[1] = 0; to->eventParms[1] = 0; } if (bits & U_WEAPON) to->weapon = MSG_ReadByte( msg ); if (bits & U_LIGHT) to->light = MSG_ReadLong( msg ); if( bits & U_TEAM ) to->team = MSG_ReadByte( msg ); } /* ================== CL_DeltaEntity Parses deltas from the given base and adds the resulting entity to the current frame ================== */ void CL_DeltaEntity( msg_t *msg, frame_t *frame, int newnum, entity_state_t *old, unsigned bits ) { entity_state_t *state; state = &frame->parsedEntities[frame->numEntities & (MAX_PARSE_ENTITIES-1)]; frame->numEntities++; CL_ParseDelta( msg, old, state, newnum, bits ); } //================== //CL_ParsePacketEntities // //An svc_packetentities has just been parsed, deal with the //rest of the data stream. //================== void CL_ParsePacketEntities( msg_t *msg, frame_t *oldframe, frame_t *newframe ) { int newnum; unsigned bits; entity_state_t *oldstate = NULL; int oldindex, oldnum; newframe->numEntities = 0; // delta from the entities present in oldframe oldindex = 0; if( !oldframe ) oldnum = 99999; else { if( oldindex >= oldframe->numEntities ) oldnum = 99999; else { oldstate = &oldframe->parsedEntities[oldindex & (MAX_PARSE_ENTITIES-1)]; oldnum = oldstate->number; } } while(1) { newnum = CL_ParseEntityBits( msg, &bits ); if( newnum >= MAX_EDICTS ) Com_Error( ERR_DROP, "CL_ParsePacketEntities: bad number:%i", newnum ); if( msg->readcount > msg->cursize) Com_Error( ERR_DROP, "CL_ParsePacketEntities: end of message" ); if( !newnum ) break; while( oldnum < newnum ) { // one or more entities from the old packet are unchanged if( cl_shownet->integer == 3 ) Com_Printf(" unchanged: %i\n", oldnum); CL_DeltaEntity( msg, newframe, oldnum, oldstate, 0 ); oldindex++; if( oldindex >= oldframe->numEntities ) oldnum = 99999; else { oldstate = &oldframe->parsedEntities[oldindex & (MAX_PARSE_ENTITIES-1)]; oldnum = oldstate->number; } } if( bits & U_REMOVE ) { // the entity present in oldframe is not in the current frame if( cl_shownet->integer == 3 ) Com_Printf( " remove: %i\n", newnum ); if( oldnum != newnum ) Com_Printf( "U_REMOVE: oldnum != newnum\n" ); oldindex++; if( oldindex >= oldframe->numEntities ) oldnum = 99999; else { oldstate = &oldframe->parsedEntities[oldindex & (MAX_PARSE_ENTITIES-1)]; oldnum = oldstate->number; } continue; } if( oldnum == newnum ) { // delta from previous state if( cl_shownet->integer == 3 ) Com_Printf( " delta: %i\n", newnum ); CL_DeltaEntity( msg, newframe, newnum, oldstate, bits ); oldindex++; if( oldindex >= oldframe->numEntities ) oldnum = 99999; else { oldstate = &oldframe->parsedEntities[oldindex & (MAX_PARSE_ENTITIES-1)]; oldnum = oldstate->number; } continue; } if( oldnum > newnum ) { // delta from baseline if( cl_shownet->integer == 3 ) Com_Printf( " baseline: %i\n", newnum ); CL_DeltaEntity( msg, newframe, newnum, &cl_baselines[newnum], bits ); 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 == 3 ) Com_Printf( " unchanged: %i\n", oldnum ); CL_DeltaEntity( msg, newframe, oldnum, oldstate, 0 ); oldindex++; if( oldindex >= oldframe->numEntities ) oldnum = 99999; else { oldstate = &oldframe->parsedEntities[oldindex & (MAX_PARSE_ENTITIES-1)]; oldnum = oldstate->number; } } } //================ //CL_ParseFrameSnap //================ frame_t *CL_ParseFrameSnap( msg_t *msg, frame_t *newframe ) { int cmd; int len; frame_t *old; memset( newframe, 0, sizeof(frame_t) ); newframe->serverTime = MSG_ReadLong( msg ); newframe->serverFrame = MSG_ReadLong( msg ); newframe->deltaFrame = MSG_ReadLong( msg ); cl.suppressCount = MSG_ReadByte( msg ); #ifdef RATEKILLED cl.suppressCount = 0; #endif if( cl_shownet->integer == 3 ) Com_Printf( " frame:%i delta:%i\n", newframe->serverFrame, newframe->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( newframe->deltaFrame <= 0 ) { newframe->valid = qtrue; // uncompressed frame old = NULL; cls.demowaiting = qfalse; // we can start recording now } else { old = &cl.frames[newframe->deltaFrame & UPDATE_MASK]; if( !old->valid ) { // should never happen Com_Printf( "Delta from invalid frame (not supposed to happen!).\n" ); } if( old->serverFrame != newframe->deltaFrame ) { // The frame that the server did the delta from // is too old, so we can't reconstruct it properly. Com_Printf( "Delta frame too old.\n" ); } else newframe->valid = qtrue; // valid delta parse } // read areabits len = MSG_ReadByte( msg ); MSG_ReadData( msg, &newframe->areabits, len ); // read match info cmd = MSG_ReadByte( msg ); SHOWNET( msg, svc_strings[cmd] ); if( cmd != svc_match ) Com_Error( ERR_DROP, "CL_ParseFrame: not match info" ); CL_ParseDeltaMatchState( msg, old, newframe ); // read playerinfo cmd = MSG_ReadByte( msg ); SHOWNET( msg, svc_strings[cmd] ); if( cmd != svc_playerinfo ) Com_Error( ERR_DROP, "CL_ParseFrame: not playerinfo" ); CL_ParsePlayerstate( msg, old, newframe ); // read packet entities cmd = MSG_ReadByte( msg ); SHOWNET( msg, svc_strings[cmd] ); if( cmd != svc_packetentities ) Com_Error( ERR_DROP, "CL_ParseFrame: not packetentities" ); CL_ParsePacketEntities( msg, old, newframe ); return old; } //================ //CL_ParseFrame //================ void CL_ParseFrame( msg_t *msg ) { static frame_t newframe; frame_t *deltaframe, *lerpframe = NULL; if( cl.curFrame && cl.curFrame->valid ) lerpframe = &cl.frames[cl.curFrame->serverFrame & UPDATE_MASK]; deltaframe = CL_ParseFrameSnap( msg, &newframe ); //ignore older than executed if( cl.curFrame && newframe.serverFrame <= cl.curFrame->serverFrame ) { if( cl.curFrame->serverFrame == newframe.serverFrame ) Com_Printf( "Frame %i received twice\n", cl.curFrame->serverFrame ); else Com_Printf( "Dropping older frame snap\n" ); return; } if( !newframe.valid ) return; // getting a valid frame message ends the connection process if( cls.state != CA_ACTIVE ) CL_SetClientState( CA_ACTIVE ); cl.soundPrepped = qtrue; // can start mixing ambient sounds // jalfixme: we will probably buffer frame snaps later on, // and this servertimedelta adjustment must be done when the frame is fired. // We're firing it as we get it, so we do it here by now cl.serverTimeDelta = newframe.serverTime - cls.realtime; if( cls.realtime + cl.serverTimeDelta < 0 ) cl.serverTimeDelta = 0; cl.serverTime = cls.realtime + cl.serverTimeDelta; // adjust servertime again to newest delta // at this point, we have cl.serverTime having the same time as in the server, BUT LAGGED. // we have to find the lag offset and remove it from cl.serverTime. // (to do) // save the frame off in the backup array for later delta comparisons cl.frames[newframe.serverFrame & UPDATE_MASK] = newframe; // update curframe pointer cl.curFrame = &cl.frames[newframe.serverFrame & UPDATE_MASK]; //mark next three frames as invalid in the backup cl.frames[(newframe.serverFrame+1) & UPDATE_MASK].valid = qfalse; cl.frames[(newframe.serverFrame+2) & UPDATE_MASK].valid = qfalse; cl.frames[(newframe.serverFrame+3) & UPDATE_MASK].valid = qfalse; if( lerpframe ) { //Com_Printf( "newframe.serverTime:%u lerpFrame.serverTime:%u delta:%u\n", newframe.serverTime, lerpframe->serverTime, newframe.serverTime - lerpframe->serverTime ); if( cls.demoplaying ) { if( cl_timedemo->integer ) { cls.demoplay_lastsnaptime = 0; } else { cls.demoplay_lastsnaptime = newframe.serverTime - lerpframe->serverTime; // jalfixme: We should know the server pps and clamp this time to them } } CL_GameModule_NewFrameSnap( &cl.frames[newframe.serverFrame & UPDATE_MASK], lerpframe ); } }