/* ============== CL_PredictMove ============== */ void CL_PredictMove(void) { int i; float f; frame_t *from, *to = NULL; int oldphysent; if (cl_pushlatency.value > 0) Cvar_Set("pushlatency", "0"); if (cl.paused) return; cl.time = realtime - cls.latency - cl_pushlatency.value * 0.001; if (cl.time > realtime) cl.time = realtime; if (cl.intermission) return; if (!cl.validsequence) return; if (cls.netchan.outgoing_sequence - cls.netchan.incoming_sequence >= UPDATE_BACKUP - 1) return; VectorCopy(cl.viewangles, cl.simangles); // this is the last frame received from the server from = &cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK]; // we can now render a frame if (cls.state == ca_onserver) { // first update is the final signon stage char text[1024]; cls.state = ca_active; sprintf(text, "QuakeWorld: %s", cls.servername); #ifdef _WIN32 SetWindowText(mainwindow, text); #endif } if (cl_nopred.value) { VectorCopy(from->playerstate[cl.playernum].velocity, cl.simvel); VectorCopy(from->playerstate[cl.playernum].origin, cl.simorg); return; } // predict forward until cl.time <= to->senttime oldphysent = pmove.numphysent; CL_SetSolidPlayers(cl.playernum); // to = &cl.frames[cls.netchan.incoming_sequence & UPDATE_MASK]; for (i = 1; i < UPDATE_BACKUP - 1 && cls.netchan.incoming_sequence + i < cls.netchan.outgoing_sequence; i++) { to = &cl.frames[(cls.netchan.incoming_sequence + i) & UPDATE_MASK]; CL_PredictUsercmd(&from->playerstate[cl.playernum] , &to->playerstate[cl.playernum], &to->cmd, cl.spectator); if (to->senttime >= cl.time) break; from = to; } pmove.numphysent = oldphysent; if (i == UPDATE_BACKUP - 1 || !to) return; // net hasn't deliver packets in a long time... // now interpolate some fraction of the final frame if (to->senttime == from->senttime) f = 0; else { f = (cl.time - from->senttime) / (to->senttime - from->senttime); if (f < 0) f = 0; if (f > 1) f = 1; } for (i = 0; i < 3; i++) if (fabs(from->playerstate[cl.playernum].origin[i] - to->playerstate[cl.playernum].origin[i]) > 128) { // teleported, so don't lerp VectorCopy(to->playerstate[cl.playernum].velocity, cl.simvel); VectorCopy(to->playerstate[cl.playernum].origin, cl.simorg); return; } for (i = 0; i < 3; i++) { cl.simorg[i] = from->playerstate[cl.playernum].origin[i] + f * (to->playerstate[cl.playernum].origin[i] - from->playerstate[cl.playernum].origin[i]); cl.simvel[i] = from->playerstate[cl.playernum].velocity[i] + f * (to->playerstate[cl.playernum].velocity[i] - from->playerstate[cl.playernum].velocity[i]); } }
/* ================= CL_CreateCmd ================= */ void CL_CreateCmd( void ) { usercmd_t cmd = { 0 }; color24 color; vec3_t angles; qboolean active; int ms; ms = host.frametime * 1000; if( ms > 250 ) ms = 100; // time was unreasonable else if( ms <= 0 ) ms = 1; // keep time an actual // build list of all solid entities per next frame (exclude clients) CL_SetSolidEntities (); CL_SetSolidPlayers ( cl.playernum ); VectorCopy( cl.refdef.cl_viewangles, angles ); VectorCopy( cl.frame.local.client.origin, cl.data.origin ); VectorCopy( cl.refdef.cl_viewangles, cl.data.viewangles ); cl.data.iWeaponBits = cl.frame.local.client.weapons; if( cl.scr_fov < 1.0f || cl.scr_fov> 170.0f ) cl.scr_fov = 90.0f; cl.data.fov = cl.scr_fov; clgame.dllFuncs.pfnUpdateClientData( &cl.data, cl.time ); // grab changes VectorCopy( cl.data.viewangles, cl.refdef.cl_viewangles ); cl.frame.local.client.weapons = cl.data.iWeaponBits; cl.scr_fov = cl.data.fov; if( cl.scr_fov < 1.0f || cl.scr_fov> 170.0f ) cl.scr_fov = 90.0f; // allways dump the first ten messages, // because it may contain leftover inputs // from the last level if( ++cl.movemessages <= 10 ) { if( !cls.demoplayback ) { cl.refdef.cmd = &cl.cmds[cls.netchan.outgoing_sequence & CL_UPDATE_MASK]; *cl.refdef.cmd = cmd; } return; } active = ( cls.state == ca_active && !cl.refdef.paused && !cls.demoplayback ); clgame.dllFuncs.CL_CreateMove( cl.time - cl.oldtime, &cmd, active ); // after command generated in client, // add motion events from engine controls IN_EngineAppendMove( host.frametime, &cmd, active); R_LightForPoint( cl.frame.local.client.origin, &color, false, false, 128.0f ); cmd.lightlevel = (color.r + color.g + color.b) / 3; // never let client.dll calc frametime for player // because is potential backdoor for cheating cmd.msec = ms; cmd.lerp_msec = cl_interp->value * 1000; cmd.lerp_msec = bound( 0, cmd.lerp_msec, 250 ); V_ProcessOverviewCmds( &cmd ); V_ProcessShowTexturesCmds( &cmd ); if(( cl.background && !cls.demoplayback ) || gl_overview->integer || cls.changelevel ) { VectorCopy( angles, cl.refdef.cl_viewangles ); VectorCopy( angles, cmd.viewangles ); cmd.msec = 0; } // demo always have commands // so don't overwrite them if( !cls.demoplayback ) { int frame = cls.netchan.outgoing_sequence & CL_UPDATE_MASK; cl.refdef.cmd = &cl.cmds[frame]; *cl.refdef.cmd = cmd; cl.runfuncs[frame] = TRUE; } }
/* ============= CL_LinkPlayers Create visible entities in the correct position for all current players ============= */ void CL_LinkPlayers (void) { int j; player_info_t *info; player_state_t *state; player_state_t exact; double playertime; entity_t *ent; int msec; frame_t *frame; int oldphysent; playertime = realtime - cls.latency + 0.02; if (playertime > realtime) playertime = realtime; frame = &cl.frames[cl.parsecount&UPDATE_MASK]; for (j=0, info=cl.players, state=frame->playerstate ; j < MAX_CLIENTS ; j++, info++, state++) { if (state->messagenum != cl.parsecount) continue; // not present this frame // spawn light flashes, even ones coming from invisible objects // #ifdef GLQUAKE // if (!gl_flashblend.value || j != cl.playernum) { // #endif if ((state->effects & (EF_BLUE | EF_RED)) == (EF_BLUE | EF_RED)) CL_NewDlight (j, state->origin[0], state->origin[1], state->origin[2], 200 + (rand()&31), 0.1, 3); else if (state->effects & EF_BLUE) CL_NewDlight (j, state->origin[0], state->origin[1], state->origin[2], 200 + (rand()&31), 0.1, 1); else if (state->effects & EF_RED) CL_NewDlight (j, state->origin[0], state->origin[1], state->origin[2], 200 + (rand()&31), 0.1, 2); else if (state->effects & EF_BRIGHTLIGHT) CL_NewDlight (j, state->origin[0], state->origin[1], state->origin[2] + 16, 400 + (rand()&31), 0.1, 0); else if (state->effects & EF_DIMLIGHT) CL_NewDlight (j, state->origin[0], state->origin[1], state->origin[2], 200 + (rand()&31), 0.1, 0); // #ifdef GLQUAKE // } // #endif // the player object never gets added if (j == cl.playernum) continue; if (!state->modelindex) continue; if (!Cam_DrawPlayer(j)) continue; // grab an entity to fill in if (cl_numvisedicts == MAX_VISEDICTS) break; // object list is full ent = &cl_visedicts[cl_numvisedicts]; cl_numvisedicts++; ent->keynum = 0; ent->model = cl.model_precache[state->modelindex]; ent->skinnum = state->skinnum; ent->frame = state->frame; ent->colormap = info->translations; if (state->modelindex == cl_playerindex) ent->scoreboard = info; // use custom skin else ent->scoreboard = NULL; // // angles // ent->angles[PITCH] = -state->viewangles[PITCH]/3; ent->angles[YAW] = state->viewangles[YAW]; ent->angles[ROLL] = 0; ent->angles[ROLL] = V_CalcRoll (ent->angles, state->velocity)*4; // only predict half the move to minimize overruns msec = 500*(playertime - state->state_time); if (msec <= 0 || (!cl_predict_players.value && !cl_predict_players2.value)) { VectorCopy (state->origin, ent->origin); //Con_DPrintf ("nopredict\n"); } else { // predict players movement if (msec > 255) msec = 255; state->command.msec = msec; //Con_DPrintf ("predict: %i\n", msec); oldphysent = pmove.numphysent; CL_SetSolidPlayers (j); CL_PredictUsercmd (state, &exact, &state->command, false); pmove.numphysent = oldphysent; VectorCopy (exact.origin, ent->origin); } if (state->effects & EF_FLAG1) CL_AddFlagModels (ent, 0); else if (state->effects & EF_FLAG2) CL_AddFlagModels (ent, 1); } }
/* ================= CL_PredictMovement Sets cl.predicted_origin and cl.predicted_angles ================= */ void CL_PredictMovement( void ) { double time; int frame = 1; int ack, outgoing_command; int current_command; int current_command_mod; local_state_t *from = 0, *to = 0; 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; pfnSetUpPlayerPrediction( false, false ); // unpredicted pure angled values converted into axis AngleVectors( cl.refdef.cl_viewangles, cl.refdef.forward, cl.refdef.right, cl.refdef.up ); ASSERT( cl.refdef.cmd != NULL ); if( !cl_predict->integer || Host_IsLocalClient() ) { // fake prediction code // we need to perform cl_lw prediction while cl_predict is disabled // because cl_lw is enabled by default in Half-Life if( !cl_lw->integer ) return; ack = cls.netchan.incoming_acknowledged; outgoing_command = cls.netchan.outgoing_sequence; from = &cl.predict[cl.parsecountmod]; from->playerstate = cl.frame.playerstate[cl.playernum]; from->client = cl.frame.local.client; Q_memcpy( from->weapondata, cl.frame.local.weapondata, sizeof( from->weapondata )); time = cl.frame.time; 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; to = &cl.predict[( cl.parsecountmod + frame ) & CL_UPDATE_MASK]; CL_FakeUsercmd( from, to, &cl.cmds[current_command_mod], cl.runfuncs[current_command_mod], &time, cls.netchan.incoming_acknowledged + frame ); cl.runfuncs[current_command_mod] = false; from = to; frame++; } if( to ) { cl.predicted_viewmodel = to->client.viewmodel; cl.scr_fov = to->client.fov; if( cl.scr_fov < 1.0f || cl.scr_fov> 170.0f ) cl.scr_fov = 90.0f; } return; } if( !CL_IsPredicted( )) { local_state_t t1, t2; Q_memset( &t1, 0, sizeof( local_state_t )); Q_memset( &t2, 0, sizeof( local_state_t )); clgame.dllFuncs.pfnPostRunCmd( &t1, &t2, cl.refdef.cmd, false, cl.time, cls.lastoutgoingcommand ); cl.scr_fov = t2.client.fov; if( cl.scr_fov < 1.0f || cl.scr_fov> 170.0f ) cl.scr_fov = 90.0f; return; } ack = cls.netchan.incoming_acknowledged; outgoing_command = cls.netchan.outgoing_sequence; from = &cl.predict[cl.parsecountmod]; from->playerstate = cl.frame.playerstate[cl.playernum]; from->client = cl.frame.local.client; Q_memcpy( from->weapondata, cl.frame.local.weapondata, sizeof( from->weapondata )); time = cl.frame.time; CL_SetSolidEntities (); CL_SetSolidPlayers ( cl.playernum ); 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; to = &cl.predict[( cl.parsecountmod + frame ) & CL_UPDATE_MASK]; CL_RunUsercmd( from, to, &cl.cmds[current_command_mod], cl.runfuncs[current_command_mod], &time, cls.netchan.incoming_acknowledged + frame ); cl.runfuncs[current_command_mod] = false; from = to; frame++; } if( to ) { cl.predicted_viewmodel = to->client.viewmodel; cl.scr_fov = to->client.fov; if( cl.scr_fov < 1.0f || cl.scr_fov> 170.0f ) cl.scr_fov = 90.0f; VectorCopy( to->playerstate.origin, cl.predicted_origin ); VectorCopy( to->client.velocity, cl.predicted_velocity ); VectorCopy( to->client.view_ofs, cl.predicted_viewofs ); VectorCopy( to->client.punchangle, cl.predicted_punchangle ); } }