/* ============= CL_ParseReliableEvent ============= */ void CL_ParseReliableEvent( sizebuf_t *msg ) { int event_index; event_args_t nullargs, args; float delay = 0.0f; cl_entity_t *pEnt; Q_memset( &nullargs, 0, sizeof( nullargs )); event_index = BF_ReadUBitLong( msg, MAX_EVENT_BITS ); if( BF_ReadOneBit( msg )) delay = (float)BF_ReadWord( msg ) * (1.0f / 100.0f); // reliable events not use delta-compression just null-compression MSG_ReadDeltaEvent( msg, &nullargs, &args ); if(( pEnt = CL_GetEntityByIndex( args.entindex )) != NULL ) { if( VectorIsNull( args.origin )) VectorCopy( pEnt->curstate.origin, args.origin ); if( VectorIsNull( args.angles )) VectorCopy( pEnt->curstate.angles, args.angles ); if( VectorIsNull( args.velocity )) VectorCopy( pEnt->curstate.velocity, args.velocity ); } CL_QueueEvent( FEV_RELIABLE|FEV_SERVER, event_index, delay, &args ); }
/* =============== CL_SetSolid Builds all the pmove physents for the current frame Note that CL_SetUpPlayerPrediction() must be called first! pmove must be setup with world and solid entity hulls before calling (via CL_PredictMove) =============== */ void CL_SetSolidPlayers( int playernum ) { int j; extern vec3_t player_mins; extern vec3_t player_maxs; cl_entity_t *ent; physent_t *pe; if( !cl_solid_players->integer ) return; for( j = 0; j < cl.maxclients; j++ ) { // the player object never gets added if( j == playernum ) continue; ent = CL_GetEntityByIndex( j + 1 ); if( !ent || !ent->player ) continue; // not present this frame pe = &clgame.pmove->physents[clgame.pmove->numphysent]; if( CL_CopyEntityToPhysEnt( pe, ent )) clgame.pmove->numphysent++; } }
/* =============== CL_AddPacketEntities =============== */ void CL_AddPacketEntities( frame_t *frame ) { cl_entity_t *ent, *clent; int i, e, entityType; clent = CL_GetLocalPlayer(); if( !clent ) return; for( i = 0; i < cl.frame.num_entities; i++ ) { e = cls.packet_entities[(cl.frame.first_entity + i) % cls.num_client_entities].number; ent = CL_GetEntityByIndex( e ); if( !ent || ent == clgame.entities ) continue; CL_UpdateEntityFields( ent ); if( ent->player ) entityType = ET_PLAYER; else if( ent->curstate.entityType == ENTITY_BEAM ) entityType = ET_BEAM; else entityType = ET_NORMAL; CL_AddVisibleEntity( ent, entityType ); } }
/* ============= CL_GetWaterEntity returns water brush where inside pos ============= */ cl_entity_t *CL_GetWaterEntity( const float *rgflPos ) { int entnum; entnum = CL_WaterEntity( rgflPos ); if( entnum <= 0 ) return NULL; // world or not water return CL_GetEntityByIndex( entnum ); }
/* ==================== CL_AddLinksToPmove collect solid entities ==================== */ void CL_AddLinksToPmove() { cl_entity_t *check; physent_t *pe; int i, solid, idx; for( i = 0; i < cl.frame.num_entities; i++ ) { idx = cls.packet_entities[(cl.frame.first_entity + i) % cls.num_client_entities].number; check = CL_GetEntityByIndex( idx ); // don't add the world and clients here if( !check || check == &clgame.entities[0] ) continue; if( clgame.pmove->numvisent < MAX_PHYSENTS ) { pe = &clgame.pmove->visents[clgame.pmove->numvisent]; if( CL_CopyEntityToPhysEnt( pe, check )) clgame.pmove->numvisent++; } // players will be added later if( check->player ) continue; // can't collide with zeroed hull if( VectorIsNull( check->curstate.mins ) && VectorIsNull( check->curstate.maxs )) continue; solid = check->curstate.solid; if( solid == SOLID_BSP || solid == SOLID_BBOX || solid == SOLID_SLIDEBOX || solid == SOLID_CUSTOM ) { // reserve slots for all the clients if( clgame.pmove->numphysent < ( MAX_PHYSENTS - cl.maxclients )) { pe = &clgame.pmove->physents[clgame.pmove->numphysent]; if( CL_CopyEntityToPhysEnt( pe, check )) clgame.pmove->numphysent++; } } else if( solid == SOLID_NOT && check->curstate.skin != CONTENTS_NONE ) { if( clgame.pmove->nummoveent < MAX_MOVEENTS ) { pe = &clgame.pmove->moveents[clgame.pmove->nummoveent]; if( CL_CopyEntityToPhysEnt( pe, check )) clgame.pmove->nummoveent++; } } } }
static void pfnPlaybackEventFull( int flags, int clientindex, word eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) { cl_entity_t *ent; ent = CL_GetEntityByIndex( clientindex + 1 ); if( ent == NULL ) return; CL_PlaybackEvent( flags, (edict_t *)ent, eventindex, delay, origin, angles, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2 ); }
void SND_CloseMouth( channel_t *ch ) { if( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_STREAM ) { cl_entity_t *clientEntity; clientEntity = CL_GetEntityByIndex( ch->entnum ); if( clientEntity ) { // shut mouth clientEntity->mouth.mouthopen = 0; } } }
void SND_MoveMouth8( channel_t *ch, wavdata_t *pSource, int count ) { cl_entity_t *clientEntity; char *pdata = NULL; mouth_t *pMouth = NULL; int savg, data; int scount, pos = 0; uint i; clientEntity = CL_GetEntityByIndex( ch->entnum ); if( !clientEntity ) return; pMouth = &clientEntity->mouth; if( ch->isSentence ) { if( ch->currentWord ) pos = ch->currentWord->sample; } else pos = ch->pMixer.sample; count = S_GetOutputData( pSource, &pdata, pos, count, ch->use_loop ); if( pdata == NULL ) return; i = 0; scount = pMouth->sndcount; savg = 0; while( i < count && scount < CAVGSAMPLES ) { data = pdata[i]; savg += abs( data ); i += 80 + ((byte)data & 0x1F); scount++; } pMouth->sndavg += savg; pMouth->sndcount = (byte)scount; if( pMouth->sndcount >= CAVGSAMPLES ) { pMouth->mouthopen = pMouth->sndavg / CAVGSAMPLES; pMouth->sndavg = 0; pMouth->sndcount = 0; } }
/* ============== CL_ParseStudioDecal Studio Decal message. Used by engine in case we need save\restore decals ============== */ void CL_ParseStudioDecal( sizebuf_t *msg ) { modelstate_t state; vec3_t start, pos; int decalIndex, entityIndex; int modelIndex = 0; int flags; pos[0] = BF_ReadCoord( msg ); pos[1] = BF_ReadCoord( msg ); pos[2] = BF_ReadCoord( msg ); start[0] = BF_ReadCoord( msg ); start[1] = BF_ReadCoord( msg ); start[2] = BF_ReadCoord( msg ); decalIndex = BF_ReadShort( msg ); entityIndex = BF_ReadShort( msg ); flags = BF_ReadByte( msg ); state.sequence = BF_ReadShort( msg ); state.frame = BF_ReadShort( msg ); state.blending[0] = BF_ReadByte( msg ); state.blending[1] = BF_ReadByte( msg ); state.controller[0] = BF_ReadByte( msg ); state.controller[1] = BF_ReadByte( msg ); state.controller[2] = BF_ReadByte( msg ); state.controller[3] = BF_ReadByte( msg ); if( cls.state == ca_connected ) { // this message came on restore. // read modelindex in case client models are not linked with entities // because first client frame has not yet received modelIndex = BF_ReadShort( msg ); } if( clgame.drawFuncs.R_StudioDecalShoot ) { int decalTexture = CL_DecalIndex( decalIndex ); cl_entity_t *ent = CL_GetEntityByIndex( entityIndex ); if( ent && !ent->model && modelIndex != 0 ) ent->model = Mod_Handle( modelIndex ); clgame.drawFuncs.R_StudioDecalShoot( decalTexture, ent, start, pos, flags, &state ); } }
void SND_InitMouth( int entnum, int entchannel ) { if(( entchannel == CHAN_VOICE || entchannel == CHAN_STREAM ) && entnum > 0 ) { cl_entity_t *clientEntity; // init mouth movement vars clientEntity = CL_GetEntityByIndex( entnum ); if( clientEntity ) { clientEntity->mouth.mouthopen = 0; clientEntity->mouth.sndavg = 0; clientEntity->mouth.sndcount = 0; } } }
// // sound engine implementation // qboolean CL_GetEntitySpatialization( int entnum, vec3_t origin, float *pradius ) { cl_entity_t *ent; qboolean valid_origin; ASSERT( origin != NULL ); if( entnum == 0 ) return true; // static sound if(( entnum - 1 ) == cl.playernum ) { VectorCopy( cl.frame.local.client.origin, origin ); return true; } valid_origin = VectorIsNull( origin ) ? false : true; ent = CL_GetEntityByIndex( entnum ); // entity is not present on the client but has valid origin if( !ent || !ent->index ) return valid_origin; if( ent->curstate.messagenum == 0 ) { // entity is never has updates on the client // so we should use static origin instead return valid_origin; } #if 0 // uncomment this if you want enable additional check by PVS if( ent->curstate.messagenum != cl.parsecount ) return valid_origin; #endif // setup origin VectorAverage( ent->curstate.mins, ent->curstate.maxs, origin ); VectorAdd( origin, ent->curstate.origin, origin ); // setup radius if( pradius ) { if( ent->model != NULL && ent->model->radius ) *pradius = ent->model->radius; else *pradius = RadiusFromBounds( ent->curstate.mins, ent->curstate.maxs ); } return true; }
/* =============== CL_SetSolid Builds all the pmove physents for the current frame Note that CL_SetUpPlayerPrediction() must be called first! pmove must be setup with world and solid entity hulls before calling (via CL_PredictMove) =============== */ void CL_SetSolidPlayers( int playernum ) { int j; cl_entity_t *ent; entity_state_t *state; physent_t *pe; if( !cl_solid_players->integer ) return; for( j = 0; j < cl.maxclients; j++ ) { // the player object never gets added if( j == playernum ) continue; ent = CL_GetEntityByIndex( j + 1 ); if( !ent || !ent->player ) continue; // not present this frame #if 1 // came from SetUpPlayerPrediction state = cl.frames[cl.parsecountmod].playerstate + j; if( ent->curstate.messagenum != cl.parsecount ) continue; // not present this frame [2] if( ent->curstate.movetype == MOVETYPE_NONE ) continue; if( state->effects & EF_NODRAW ) continue; // skip invisible if( !state->solid ) continue; // not solid #endif pe = &clgame.pmove->physents[clgame.pmove->numphysent]; if( CL_CopyEntityToPhysEnt( pe, ent )) clgame.pmove->numphysent++; } }
/* ============== CL_ParseStudioDecal Studio Decal message. Used by engine in case we need save\restore decals ============== */ void CL_ParseStudioDecal( sizebuf_t *msg ) { modelstate_t state; vec3_t start, pos; int decalIndex, entityIndex; int modelIndex = 0; int flags; BF_ReadVec3Coord( msg, pos ); BF_ReadVec3Coord( msg, start ); decalIndex = BF_ReadWord( msg ); entityIndex = BF_ReadWord( msg ); flags = BF_ReadByte( msg ); state.sequence = BF_ReadShort( msg ); state.frame = BF_ReadShort( msg ); state.blending[0] = BF_ReadByte( msg ); state.blending[1] = BF_ReadByte( msg ); state.controller[0] = BF_ReadByte( msg ); state.controller[1] = BF_ReadByte( msg ); state.controller[2] = BF_ReadByte( msg ); state.controller[3] = BF_ReadByte( msg ); modelIndex = BF_ReadWord( msg ); state.body = BF_ReadByte( msg ); state.skin = BF_ReadByte( msg ); if( clgame.drawFuncs.R_StudioDecalShoot != NULL ) { int decalTexture = CL_DecalIndex( decalIndex ); cl_entity_t *ent = CL_GetEntityByIndex( entityIndex ); if( ent && !ent->model && modelIndex != 0 ) ent->model = Mod_Handle( modelIndex ); clgame.drawFuncs.R_StudioDecalShoot( decalTexture, ent, start, pos, flags, &state ); } }
/* ================== CL_ParsePacketEntities An svc_packetentities has just been parsed, deal with the rest of the data stream. ================== */ void CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ) { frame_t *newframe, *oldframe; int oldindex, newnum, oldnum; int oldpacket, newpacket; cl_entity_t *player; entity_state_t *oldent; int i, count; // save first uncompressed packet as timestamp if( cls.changelevel && !delta && cls.demorecording ) CL_WriteDemoJumpTime(); // first, allocate packet for new frame count = BF_ReadWord( msg ); newpacket = cl.parsecountmod; newframe = &cl.frames[newpacket]; // allocate parse entities newframe->first_entity = cls.next_client_entities; newframe->num_entities = 0; newframe->valid = true; // assume valid if( delta ) { int subtracted; oldpacket = BF_ReadByte( msg ); subtracted = ((( cls.netchan.incoming_sequence & 0xFF ) - oldpacket ) & 0xFF ); if( subtracted == 0 ) { Host_Error( "CL_DeltaPacketEntities: update too old, connection dropped.\n" ); return; } if( subtracted >= CL_UPDATE_MASK ) { // we can't use this, it is too old Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" ); CL_FlushEntityPacket( msg ); return; } oldframe = &cl.frames[oldpacket & CL_UPDATE_MASK]; if(( cls.next_client_entities - oldframe->first_entity ) > ( cls.num_client_entities - 128 )) { Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" ); CL_FlushEntityPacket( msg ); return; } } else { // this is a full update that we can start delta compressing from now oldframe = NULL; oldpacket = -1; // delta too old or is initial message cl.force_send_usercmd = true; // send reply cls.demowaiting = false; // we can start recording now } // mark current delta state cl.validsequence = cls.netchan.incoming_sequence; oldent = NULL; oldindex = 0; if( !oldframe ) { oldnum = MAX_ENTNUMBER; } else { if( oldindex >= oldframe->num_entities ) { oldnum = MAX_ENTNUMBER; } else { oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; oldnum = oldent->number; } } while( 1 ) { newnum = BF_ReadWord( msg ); if( !newnum ) break; // end of packet entities if( BF_CheckOverflow( msg )) Host_Error( "CL_ParsePacketEntities: read overflow\n" ); while( oldnum < newnum ) { // one or more entities from the old packet are unchanged CL_DeltaEntity( msg, newframe, oldnum, oldent, true ); oldindex++; if( oldindex >= oldframe->num_entities ) { oldnum = MAX_ENTNUMBER; } else { oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; oldnum = oldent->number; } } if( oldnum == newnum ) { // delta from previous state CL_DeltaEntity( msg, newframe, newnum, oldent, false ); oldindex++; if( oldindex >= oldframe->num_entities ) { oldnum = MAX_ENTNUMBER; } else { oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; oldnum = oldent->number; } continue; } if( oldnum > newnum ) { // delta from baseline ? CL_DeltaEntity( msg, newframe, newnum, NULL, false ); continue; } } // any remaining entities in the old frame are copied over while( oldnum != MAX_ENTNUMBER ) { // one or more entities from the old packet are unchanged CL_DeltaEntity( msg, newframe, oldnum, oldent, true ); oldindex++; if( oldindex >= oldframe->num_entities ) { oldnum = MAX_ENTNUMBER; } else { oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; oldnum = oldent->number; } } cl.frame = *newframe; if( !cl.frame.valid ) return; player = CL_GetLocalPlayer(); if( cls.state != ca_active ) { // client entered the game cls.state = ca_active; cl.force_refdef = true; cls.changelevel = false; // changelevel is done cls.changedemo = false; // changedemo is done SCR_MakeLevelShot(); // make levelshot if needs Cvar_SetFloat( "scr_loading", 0.0f ); // reset progress bar if(( cls.demoplayback || cls.disable_servercount != cl.servercount ) && cl.video_prepped ) SCR_EndLoadingPlaque(); // get rid of loading plaque } // update local player states clgame.dllFuncs.pfnTxferLocalOverrides( &player->curstate, &newframe->local.client ); // update state for all players for( i = 0; i < cl.maxclients; i++ ) { cl_entity_t *ent = CL_GetEntityByIndex( i + 1 ); if( !ent ) continue; clgame.dllFuncs.pfnProcessPlayerState( &newframe->playerstate[i], &ent->curstate ); newframe->playerstate[i].number = ent->index; } cl.frame = *newframe; }
void CL_UpdateEntityFields( cl_entity_t *ent ) { // parametric rockets code if( ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f ) { float lerp = ( cl.time - ent->curstate.starttime ) / ( ent->curstate.impacttime - ent->curstate.starttime ); vec3_t dir; lerp = bound( 0.0f, lerp, 1.0f ); // first we need to calc actual origin VectorLerp( ent->curstate.startpos, lerp, ent->curstate.endpos, ent->curstate.origin ); VectorSubtract( ent->curstate.endpos, ent->curstate.startpos, dir ); VectorAngles( dir, ent->curstate.angles ); // re-aim projectile } ent->model = Mod_Handle( ent->curstate.modelindex ); ent->curstate.msg_time = cl.time; CL_InterpolateModel( ent ); if( ent->player && RP_LOCALCLIENT( ent )) // stupid Half-Life bug ent->angles[PITCH] = -ent->angles[PITCH] / 3.0f; // make me lerp if( ent->model && ent->model->type == mod_brush && ent->curstate.animtime != 0.0f ) { float d, f = 0.0f; int i; // don't do it if the goalstarttime hasn't updated in a while. // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit // was increased to 1.0 s., which is 2x the max lag we are accounting for. if(( cl.time < ent->curstate.animtime + 1.0f ) && ( ent->curstate.animtime != ent->latched.prevanimtime )) f = ( cl.time - ent->curstate.animtime ) / ( ent->curstate.animtime - ent->latched.prevanimtime ); f = f - 1.0f; ent->origin[0] += ( ent->origin[0] - ent->latched.prevorigin[0] ) * f; ent->origin[1] += ( ent->origin[1] - ent->latched.prevorigin[1] ) * f; ent->origin[2] += ( ent->origin[2] - ent->latched.prevorigin[2] ) * f; for( i = 0; i < 3; i++ ) { float ang1, ang2; ang1 = ent->angles[i]; ang2 = ent->latched.prevangles[i]; d = ang1 - ang2; if( d > 180.0f ) d -= 360.0f; else if( d < -180.0f ) d += 360.0f; ent->angles[i] += d * f; } } else if( ent->curstate.eflags & EFLAG_SLERP ) { float d, f = 0.0f; cl_entity_t *m_pGround = NULL; int i; // don't do it if the goalstarttime hasn't updated in a while. // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit // was increased to 1.0 s., which is 2x the max lag we are accounting for. if(( cl.time < ent->curstate.animtime + 1.0f ) && ( ent->curstate.animtime != ent->latched.prevanimtime )) f = ( cl.time - ent->curstate.animtime ) / ( ent->curstate.animtime - ent->latched.prevanimtime ); f = f - 1.0f; if( ent->curstate.movetype == MOVETYPE_FLY ) { ent->origin[0] += ( ent->curstate.origin[0] - ent->latched.prevorigin[0] ) * f; ent->origin[1] += ( ent->curstate.origin[1] - ent->latched.prevorigin[1] ) * f; ent->origin[2] += ( ent->curstate.origin[2] - ent->latched.prevorigin[2] ) * f; for( i = 0; i < 3; i++ ) { float ang1, ang2; ang1 = ent->curstate.angles[i]; ang2 = ent->latched.prevangles[i]; d = ang1 - ang2; if( d > 180.0f ) d -= 360.0f; else if( d < -180.0f ) d += 360.0f; ent->angles[i] += d * f; } } else if( ent->curstate.movetype == MOVETYPE_STEP ) { vec3_t vecSrc, vecEnd; pmtrace_t trace; if( ent->model ) { CL_SetTraceHull( 0 ); // g-cont. player hull for better detect moving platforms VectorSet( vecSrc, ent->origin[0], ent->origin[1], ent->origin[2] + ent->model->maxs[2] ); VectorSet( vecEnd, vecSrc[0], vecSrc[1], vecSrc[2] - ent->model->mins[2] - 8.0f ); CL_PlayerTraceExt( vecSrc, vecEnd, PM_STUDIO_IGNORE, CL_PushMoveFilter, &trace ); m_pGround = CL_GetEntityByIndex( pfnIndexFromTrace( &trace )); } if( m_pGround && m_pGround->curstate.movetype == MOVETYPE_PUSH ) { qboolean applyVel, applyAvel; applyVel = !VectorCompare( m_pGround->curstate.origin, m_pGround->prevstate.origin ); applyAvel = !VectorCompare( m_pGround->curstate.angles, m_pGround->prevstate.angles ); if( applyVel || applyAvel ) { ent->origin[0] += ( m_pGround->curstate.origin[0] - m_pGround->prevstate.origin[0] ) * -1.0f; ent->origin[1] += ( m_pGround->curstate.origin[1] - m_pGround->prevstate.origin[1] ) * -1.0f; // ent->origin[2] += ( m_pGround->curstate.origin[2] - m_pGround->prevstate.origin[2] ) * -1.0f; ent->latched.prevorigin[2] = ent->origin[2]; } if( applyAvel ) { for( i = 0; i < 3; i++ ) { float ang1, ang2; ang1 = m_pGround->curstate.angles[i]; ang2 = m_pGround->prevstate.angles[i]; d = ang1 - ang2; if( d > 180.0f ) d -= 360.0f; else if( d < -180.0f ) d += 360.0f; ent->angles[i] += d * -1.0f; } } } // moved code from StudioSetupTransform here if( host.features & ENGINE_COMPUTE_STUDIO_LERP ) { ent->origin[0] += ( ent->curstate.origin[0] - ent->latched.prevorigin[0] ) * f; ent->origin[1] += ( ent->curstate.origin[1] - ent->latched.prevorigin[1] ) * f; ent->origin[2] += ( ent->curstate.origin[2] - ent->latched.prevorigin[2] ) * f; for( i = 0; i < 3; i++ ) { float ang1, ang2; ang1 = ent->angles[i]; ang2 = ent->latched.prevangles[i]; d = ang1 - ang2; if( d > 180.0f ) d -= 360.0f; else if( d < -180.0f ) d += 360.0f; ent->angles[i] += d * f; } } } } }
/* ================= CL_PredictMovement Sets cl.predicted_origin and cl.predicted_angles ================= */ void CL_PredictMovement() { int frame; int ack, outgoing_command; int current_command; int current_command_mod; cl_entity_t *player, *viewent; clientdata_t *cd; if( cls.state != ca_active ) return; if( cls.demoplayback && cl.refdef.cmd != NULL ) VectorCopy( cl.refdef.cmd->viewangles, cl.refdef.cl_viewangles ); // restore viewangles from cmd.angles if(cl.refdef.paused /*|| cls.key_dest == key_menu*/) //return; player = CL_GetLocalPlayer (); viewent = CL_GetEntityByIndex( cl.refdef.viewentity ); cd = &cl.frame.local.client; // unpredicted pure angled values converted into axis AngleVectors( cl.refdef.cl_viewangles, cl.refdef.forward, cl.refdef.right, cl.refdef.up ); if( !CL_IsPredicted( )) { // run commands even if client predicting is disabled - client expect it clgame.pmove->runfuncs = true; CL_PostRunCmd( cl.refdef.cmd, cls.lastoutgoingcommand ); return; }; ack = cls.netchan.incoming_acknowledged; outgoing_command = cls.netchan.outgoing_sequence; // if we are too far out of date, just freeze if(outgoing_command - ack >= CL_UPDATE_BACKUP) { //if(cl_showmiss->value) //Com_Printf ("exceeded CL_UPDATE_BACKUP\n"); return; }; ASSERT( cl.refdef.cmd != NULL ); // setup initial pmove state CL_SetupPMove( clgame.pmove, cd, &player->curstate, cl.refdef.cmd ); clgame.pmove->runfuncs = false; frame = 0; while(++ack < outgoing_command) { // we've run too far forward //if( frame >= CL_UPDATE_BACKUP - 1 ) // break; frame = ack & (CL_UPDATE_BACKUP - 1); // Incoming_acknowledged is the last usercmd the server acknowledged having acted upon current_command = ack + frame; current_command_mod = current_command & CL_UPDATE_MASK; // we've caught up to the current command // this was the most up to date command if(current_command >= outgoing_command) break; clgame.pmove->cmd = cl.cmds[current_command_mod]; // Call the client dll player movement function (and it will call the PM_Move()) clgame.dllFuncs.pfnPlayerMove(clgame.pmove, false); // run frames clgame.pmove->runfuncs = ( current_command > outgoing_command - 1 ) ? true : false; // Prevent to play sounds/etc more that once frame++; }; CL_PostRunCmd( cl.refdef.cmd, cls.lastoutgoingcommand ); // copy results out for rendering VectorCopy( clgame.pmove->view_ofs, cl.predicted_viewofs ); VectorCopy( clgame.pmove->origin, cl.predicted_origin ); VectorCopy( clgame.pmove->velocity, cl.predicted_velocity ); };
// Shoots a decal onto the surface of the BSP. position is the center of the decal in world coords void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, vec3_t saxis, float scale ) { decalinfo_t decalInfo; hull_t *hull; cl_entity_t *ent = NULL; model_t *model = NULL; int width, height; if( textureIndex <= 0 || textureIndex >= MAX_TEXTURES ) { MsgDev( D_ERROR, "Decal has invalid texture!\n" ); return; } if( entityIndex > 0 ) { ent = CL_GetEntityByIndex( entityIndex ); if( modelIndex > 0 ) model = Mod_Handle( modelIndex ); else if( ent != NULL ) model = Mod_Handle( ent->curstate.modelindex ); else return; } else if( modelIndex > 0 ) model = Mod_Handle( modelIndex ); else model = cl.worldmodel; if( !model ) return; if( model->type != mod_brush ) { MsgDev( D_ERROR, "Decals must hit mod_brush!\n" ); return; } decalInfo.m_pModel = model; hull = &model->hulls[0]; // always use #0 hull if( ent && !( flags & FDECAL_LOCAL_SPACE )) { vec3_t pos_l; // transform decal position in local bmodel space if( !VectorIsNull( ent->angles )) { matrix4x4 matrix; Matrix4x4_CreateFromEntity( matrix, ent->angles, ent->origin, 1.0f ); Matrix4x4_VectorITransform( matrix, pos, pos_l ); } else { VectorSubtract( pos, ent->origin, pos_l ); } VectorCopy( pos_l, decalInfo.m_Position ); flags |= FDECAL_LOCAL_SPACE; // decal position moved into local space } else { // pass position in global VectorCopy( pos, decalInfo.m_Position ); } // deal with the s axis if one was passed in if( saxis ) { flags |= FDECAL_USESAXIS; VectorCopy( saxis, decalInfo.m_SAxis ); } // this decal must use landmark for correct transition if(!( model->flags & MODEL_HAS_ORIGIN )) { flags |= FDECAL_USE_LANDMARK; } // more state used by R_DecalNode() decalInfo.m_iTexture = textureIndex; decalInfo.m_Entity = entityIndex; decalInfo.m_Flags = flags; R_GetDecalDimensions( textureIndex, &width, &height ); decalInfo.m_Size = width >> 1; if(( height >> 1 ) > decalInfo.m_Size ) decalInfo.m_Size = height >> 1; decalInfo.m_scale = bound( MIN_DECAL_SCALE, scale, MAX_DECAL_SCALE ); // compute the decal dimensions in world space decalInfo.m_decalWidth = width / decalInfo.m_scale; decalInfo.m_decalHeight = height / decalInfo.m_scale; R_DecalNode( model, &model->nodes[hull->firstclipnode], &decalInfo ); }
/* ========================================================================= FRAME PARSING ========================================================================= */ void CL_UpdateEntityFields( cl_entity_t *ent ) { VectorCopy( ent->curstate.origin, ent->origin ); VectorCopy( ent->curstate.angles, ent->angles ); ent->model = Mod_Handle( ent->curstate.modelindex ); ent->curstate.msg_time = cl.time; if( ent->player ) // stupid Half-Life bug ent->angles[PITCH] = -ent->angles[PITCH] / 3.0f; // make me lerp if( ent->model && ent->model->type == mod_brush && ent->curstate.animtime != 0.0f ) { float d, f = 0.0f; int i; // don't do it if the goalstarttime hasn't updated in a while. // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit // was increased to 1.0 s., which is 2x the max lag we are accounting for. if(( cl.time < ent->curstate.animtime + 1.0f ) && ( ent->curstate.animtime != ent->latched.prevanimtime )) f = ( cl.time - ent->curstate.animtime ) / ( ent->curstate.animtime - ent->latched.prevanimtime ); f = f - 1.0f; ent->origin[0] += ( ent->origin[0] - ent->latched.prevorigin[0] ) * f; ent->origin[1] += ( ent->origin[1] - ent->latched.prevorigin[1] ) * f; ent->origin[2] += ( ent->origin[2] - ent->latched.prevorigin[2] ) * f; for( i = 0; i < 3; i++ ) { float ang1, ang2; ang1 = ent->angles[i]; ang2 = ent->latched.prevangles[i]; d = ang1 - ang2; if( d > 180.0f ) d -= 360.0f; else if( d < -180.0f ) d += 360.0f; ent->angles[i] += d * f; } } else if( ent->curstate.eflags & EFLAG_SLERP ) { float d, f = 0.0f; cl_entity_t *m_pGround = NULL; int i; // don't do it if the goalstarttime hasn't updated in a while. // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit // was increased to 1.0 s., which is 2x the max lag we are accounting for. if(( cl.time < ent->curstate.animtime + 1.0f ) && ( ent->curstate.animtime != ent->latched.prevanimtime )) f = ( cl.time - ent->curstate.animtime ) / ( ent->curstate.animtime - ent->latched.prevanimtime ); f = f - 1.0f; if( ent->curstate.movetype == MOVETYPE_FLY ) { ent->origin[0] += ( ent->curstate.origin[0] - ent->latched.prevorigin[0] ) * f; ent->origin[1] += ( ent->curstate.origin[1] - ent->latched.prevorigin[1] ) * f; ent->origin[2] += ( ent->curstate.origin[2] - ent->latched.prevorigin[2] ) * f; for( i = 0; i < 3; i++ ) { float ang1, ang2; ang1 = ent->curstate.angles[i]; ang2 = ent->latched.prevangles[i]; d = ang1 - ang2; if( d > 180.0f ) d -= 360.0f; else if( d < -180.0f ) d += 360.0f; ent->angles[i] += d * f; } } else if( ent->curstate.movetype == MOVETYPE_STEP ) { vec3_t vecSrc, vecEnd; pmtrace_t trace; if( ent->model ) { CL_SetTraceHull( 0 ); // g-cont. player hull for better detect moving platforms VectorSet( vecSrc, ent->origin[0], ent->origin[1], ent->origin[2] + ent->model->maxs[2] ); VectorSet( vecEnd, vecSrc[0], vecSrc[1], vecSrc[2] - ent->model->mins[2] - 8.0f ); CL_PlayerTraceExt( vecSrc, vecEnd, PM_STUDIO_IGNORE, CL_PushMoveFilter, &trace ); m_pGround = CL_GetEntityByIndex( pfnIndexFromTrace( &trace )); } if( m_pGround && m_pGround->curstate.movetype == MOVETYPE_PUSH ) { qboolean applyVel, applyAvel; d = -1.0f; applyVel = !VectorCompare( m_pGround->curstate.origin, m_pGround->prevstate.origin ); applyAvel = !VectorCompare( m_pGround->curstate.angles, m_pGround->prevstate.angles ); if( applyVel || applyAvel ) { ent->origin[0] += ( m_pGround->curstate.origin[0] - m_pGround->prevstate.origin[0] ) * d; ent->origin[1] += ( m_pGround->curstate.origin[1] - m_pGround->prevstate.origin[1] ) * d; // ent->origin[2] += ( m_pGround->curstate.origin[2] - m_pGround->prevstate.origin[2] ) * d; ent->latched.prevorigin[2] = ent->origin[2]; } if( applyAvel ) { for( i = 0; i < 3; i++ ) { float ang1, ang2; ang1 = m_pGround->curstate.angles[i]; ang2 = m_pGround->prevstate.angles[i]; f = ang1 - ang2; if( d > 180.0f ) f -= 360.0f; else if( d < -180.0f ) f += 360.0f; ent->angles[i] += d * f; } } } } } }
/* ============= CL_ParseEvent ============= */ void CL_ParseEvent( sizebuf_t *msg ) { int event_index; int i, num_events; int packet_ent; event_args_t nullargs, args; qboolean has_update; entity_state_t *state; cl_entity_t *pEnt; float delay; Q_memset( &nullargs, 0, sizeof( nullargs )); num_events = BF_ReadUBitLong( msg, 5 ); // parse events queue for( i = 0 ; i < num_events; i++ ) { event_index = BF_ReadUBitLong( msg, MAX_EVENT_BITS ); Q_memset( &args, 0, sizeof( args )); has_update = false; if( BF_ReadOneBit( msg )) { packet_ent = BF_ReadUBitLong( msg, MAX_ENTITY_BITS ); if( BF_ReadOneBit( msg )) { MSG_ReadDeltaEvent( msg, &nullargs, &args ); has_update = true; } } else packet_ent = -1; if( packet_ent != -1 ) state = &cls.packet_entities[(cl.frame.first_entity+packet_ent)%cls.num_client_entities]; else state = NULL; // it's a client. Override some params if( args.entindex >= 1 && args.entindex <= cl.maxclients ) { if(( args.entindex - 1 ) == cl.playernum ) { if( state && !CL_IsPredicted( )) { // restore viewangles from angles args.angles[PITCH] = -state->angles[PITCH] * 3; args.angles[YAW] = state->angles[YAW]; args.angles[ROLL] = 0; // no roll } else { // get the predicted angles VectorCopy( cl.refdef.cl_viewangles, args.angles ); } VectorCopy( cl.frame.local.client.origin, args.origin ); VectorCopy( cl.frame.local.client.velocity, args.velocity ); } else if( state ) { // restore viewangles from angles args.angles[PITCH] = -state->angles[PITCH] * 3; args.angles[YAW] = state->angles[YAW]; args.angles[ROLL] = 0; // no roll // if we restore origin and velocity everytime, why don't do it here also? if( VectorIsNull( args.origin )) VectorCopy( state->origin, args.origin ); if( VectorIsNull( args.velocity )) VectorCopy( state->velocity, args.velocity ); } } else if( state ) { if( VectorIsNull( args.origin )) VectorCopy( state->origin, args.origin ); if( VectorIsNull( args.angles )) VectorCopy( state->angles, args.angles ); if( VectorIsNull( args.velocity )) VectorCopy( state->velocity, args.velocity ); } else if(( pEnt = CL_GetEntityByIndex( args.entindex )) != NULL ) { if( VectorIsNull( args.origin )) VectorCopy( pEnt->curstate.origin, args.origin ); if( VectorIsNull( args.angles )) VectorCopy( pEnt->curstate.angles, args.angles ); if( VectorIsNull( args.velocity )) VectorCopy( pEnt->curstate.velocity, args.velocity ); } if( BF_ReadOneBit( msg )) delay = (float)BF_ReadWord( msg ) * (1.0f / 100.0f); else delay = 0.0f; // g-cont. should we need find the event with same index? CL_QueueEvent( 0, event_index, delay, &args ); } }
/* ================= CL_PrepVideo Call before entering a new level, or after changing dlls ================= */ void CL_PrepVideo( void ) { string mdlname, mapname; int i, mdlcount, step; int map_checksum; // dummy if( !cl.model_precache[1][0] ) return; // no map loaded Cvar_SetFloat( "scr_loading", 0.0f ); // reset progress bar MsgDev( D_NOTE, "CL_PrepVideo: %s\n", clgame.mapname ); // let the render dll load the map Q_strncpy( mapname, cl.model_precache[1], MAX_STRING ); Mod_LoadWorld( mapname, (uint *)&map_checksum, false ); cl.worldmodel = Mod_Handle( 1 ); // get world pointer Cvar_SetFloat( "scr_loading", 25.0f ); SCR_UpdateScreen(); // make sure what map is valid if( !cls.demoplayback && map_checksum != cl.checksum ) Host_Error( "Local map version differs from server: %i != '%i'\n", map_checksum, cl.checksum ); for( i = 0, mdlcount = 0; i < MAX_MODELS && cl.model_precache[i+1][0]; i++ ) mdlcount++; // total num models step = mdlcount/10; for( i = 0; i < MAX_MODELS && cl.model_precache[i+1][0]; i++ ) { Q_strncpy( mdlname, cl.model_precache[i+1], MAX_STRING ); Mod_RegisterModel( mdlname, i+1 ); Cvar_SetFloat( "scr_loading", scr_loading->value + 75.0f / mdlcount ); if( step && !( i % step ) && ( cl_allow_levelshots->integer || cl.background ) ) SCR_UpdateScreen(); } // update right muzzleflash indexes CL_RegisterMuzzleFlashes (); // invalidate all decal indexes Q_memset( cl.decal_index, 0, sizeof( cl.decal_index )); CL_ClearWorld (); R_NewMap(); // tell the render about new map V_SetupOverviewState(); // set overview bounds // must be called after lightmap loading! clgame.dllFuncs.pfnVidInit(); // release unused SpriteTextures for( i = 1; i < MAX_IMAGES; i++ ) { if( !clgame.sprites[i].name[0] ) continue; // free slot if( clgame.sprites[i].needload != clgame.load_sequence ) Mod_UnloadSpriteModel( &clgame.sprites[i] ); } Mod_FreeUnused (); Q_memset( cl.playermodels, 0, sizeof( cl.playermodels ) ); Cvar_SetFloat( "scr_loading", 100.0f ); // all done if( host.decalList ) { // need to reapply all decals after restarting for( i = 0; i < host.numdecals; i++ ) { decallist_t *entry = &host.decalList[i]; cl_entity_t *pEdict = CL_GetEntityByIndex( entry->entityIndex ); int decalIndex = CL_DecalIndex( CL_DecalIndexFromName( entry->name )); int modelIndex = 0; if( pEdict ) modelIndex = pEdict->curstate.modelindex; CL_DecalShoot( decalIndex, entry->entityIndex, modelIndex, entry->position, entry->flags ); } Z_Free( host.decalList ); } host.decalList = NULL; host.numdecals = 0; if( host.soundList ) { // need to reapply all ambient sounds after restarting for( i = 0; i < host.numsounds; i++ ) { soundlist_t *entry = &host.soundList[i]; if( entry->looping && entry->entnum != -1 ) { MsgDev( D_NOTE, "Restarting sound %s...\n", entry->name ); S_AmbientSound( entry->origin, entry->entnum, S_RegisterSound( entry->name ), entry->volume, entry->attenuation, entry->pitch, 0 ); } } } host.soundList = NULL; host.numsounds = 0; if( host.developer <= 2 ) Con_ClearNotify(); // clear any lines of console text SCR_UpdateScreen (); cl.video_prepped = true; cl.force_refdef = true; }
/* ================= CL_PredictMovement Sets cl.predicted_origin and cl.predicted_angles ================= */ void CL_PredictMovement( void ) { int frame = 1; int ack, outgoing_command; int current_command; int current_command_mod; cl_entity_t *player, *viewent; clientdata_t *cd; if( cls.state != ca_active ) return; if( cls.demoplayback && cl.refdef.cmd != NULL ) { // restore viewangles from cmd.angles VectorCopy( cl.refdef.cmd->viewangles, cl.refdef.cl_viewangles ); } if( cl.refdef.paused || cls.key_dest == key_menu ) return; player = CL_GetLocalPlayer (); viewent = CL_GetEntityByIndex( cl.refdef.viewentity ); cd = &cl.frame.local.client; // unpredicted pure angled values converted into axis AngleVectors( cl.refdef.cl_viewangles, cl.refdef.forward, cl.refdef.right, cl.refdef.up ); if( !CL_IsPredicted( )) { // run commands even if client predicting is disabled - client expected it clgame.pmove->runfuncs = true; CL_PostRunCmd( cl.refdef.cmd, cls.lastoutgoingcommand ); return; } ack = cls.netchan.incoming_acknowledged; outgoing_command = cls.netchan.outgoing_sequence; ASSERT( cl.refdef.cmd != NULL ); // setup initial pmove state CL_SetupPMove( clgame.pmove, cd, &player->curstate, cl.refdef.cmd ); clgame.pmove->runfuncs = false; while( 1 ) { // we've run too far forward if( frame >= CL_UPDATE_BACKUP - 1 ) break; // Incoming_acknowledged is the last usercmd the server acknowledged having acted upon current_command = ack + frame; current_command_mod = current_command & CL_UPDATE_MASK; // we've caught up to the current command. if( current_command >= outgoing_command ) break; clgame.pmove->cmd = cl.cmds[current_command_mod]; // motor! clgame.dllFuncs.pfnPlayerMove( clgame.pmove, false ); // run frames clgame.pmove->runfuncs = ( current_command > outgoing_command - 1 ) ? true : false; frame++; } CL_PostRunCmd( cl.refdef.cmd, frame ); // copy results out for rendering VectorCopy( clgame.pmove->view_ofs, cl.predicted_viewofs ); VectorCopy( clgame.pmove->origin, cl.predicted_origin ); VectorCopy( clgame.pmove->velocity, cl.predicted_velocity ); }