/* * @brief The origin is typically calculated using client sided prediction, provided * the client is not viewing a demo, playing in 3rd person mode, or chasing * another player. */ static void Cl_UpdateOrigin(const player_state_t *from, const player_state_t *to) { if (Cl_UsePrediction()) { // use client sided prediction for (int32_t i = 0; i < 3; i++) { r_view.origin[i] = cl.predicted_state.origin[i] + cl.predicted_state.view_offset[i]; r_view.origin[i] -= (1.0 - cl.lerp) * cl.predicted_state.error[i]; } const uint32_t delta = cl.time - cl.predicted_state.step_time; const uint32_t interval = cl.predicted_state.step_interval; if (delta < interval) { // interpolate stair traversal const vec_t lerp = (interval - delta) / (vec_t) interval; r_view.origin[2] -= cl.predicted_state.step * lerp; } } else { // just use interpolated values from frame vec3_t origin; vec3_t from_offset, to_offset, offset; VectorLerp(from->pm_state.origin, to->pm_state.origin, cl.lerp, origin); UnpackVector(from->pm_state.view_offset, from_offset); UnpackVector(to->pm_state.view_offset, to_offset); VectorLerp(from_offset, to_offset, cl.lerp, offset); VectorAdd(origin, offset, r_view.origin); } // update the contents mask for e.g. under-water effects r_view.contents = Cl_PointContents(r_view.origin); }
/* * @brief Checks for client side prediction errors. Problems here can indicate * that Pm_Move or the protocol are not functioning correctly. */ void Cl_CheckPredictionError(void) { vec3_t delta; VectorClear(cl.predicted_state.error); if (!Cl_UsePrediction()) return; // calculate the last user_cmd_t we sent that the server has processed const uint32_t frame = (cls.net_chan.incoming_acknowledged & CMD_MASK); // compare what the server returned with what we had predicted it to be VectorSubtract(cl.frame.ps.pm_state.origin, cl.predicted_state.origins[frame], delta); const vec_t error = VectorLength(delta); if (error > 1.0) { Com_Debug("%s\n", vtos(delta)); if (error > 256.0) { // do not interpolate VectorClear(delta); } } VectorCopy(delta, cl.predicted_state.error); }
/* * @brief The angles are typically fetched from input, after factoring in client-side * prediction, unless the client is watching a demo or chase camera. */ static void Cl_UpdateAngles(const player_state_t *from, const player_state_t *to) { vec3_t old_angles, new_angles, angles; // start with the predicted angles, or interpolate the server states if (Cl_UsePrediction()) { VectorCopy(cl.predicted_state.view_angles, r_view.angles); } else { UnpackAngles(from->pm_state.view_angles, old_angles); UnpackAngles(to->pm_state.view_angles, new_angles); AngleLerp(old_angles, new_angles, cl.lerp, r_view.angles); } // add in the kick angles UnpackAngles(from->pm_state.kick_angles, old_angles); UnpackAngles(to->pm_state.kick_angles, new_angles); AngleLerp(old_angles, new_angles, cl.lerp, angles); VectorAdd(r_view.angles, angles, r_view.angles); // and lastly the delta angles UnpackAngles(from->pm_state.delta_angles, old_angles); UnpackAngles(to->pm_state.delta_angles, new_angles); VectorCopy(new_angles, angles); // check for small delta angles, and interpolate them if (!VectorCompare(old_angles, new_angles)) { int32_t i; for (i = 0; i < 3; i++) { const vec_t delta = fabs(new_angles[i] - old_angles[i]); if (delta > 5.0 && delta < 355.0) { break; } } if (i == 3) { AngleLerp(old_angles, new_angles, cl.lerp, angles); } } VectorAdd(r_view.angles, angles, r_view.angles); if (cl.frame.ps.pm_state.type == PM_DEAD) { // look only on x axis r_view.angles[0] = 0.0; r_view.angles[2] = 45.0; } // and finally set the view directional vectors AngleVectors(r_view.angles, r_view.forward, r_view.right, r_view.up); }
/* * @brief The angles are typically fetched from input, after factoring in client-side * prediction, unless the client is watching a demo or chase camera. */ static void Cl_UpdateAngles(const player_state_t *ps, const player_state_t *ops) { vec3_t old_angles, new_angles, angles; // start with the predicted angles, or interpolate the server states if (Cl_UsePrediction()) { VectorCopy(cl.predicted_state.view_angles, r_view.angles); } else { UnpackAngles(ops->pm_state.view_angles, old_angles); UnpackAngles(ps->pm_state.view_angles, new_angles); AngleLerp(old_angles, new_angles, cl.lerp, r_view.angles); } // add in the kick angles UnpackAngles(ops->pm_state.kick_angles, old_angles); UnpackAngles(ps->pm_state.kick_angles, new_angles); AngleLerp(old_angles, new_angles, cl.lerp, angles); VectorAdd(r_view.angles, angles, r_view.angles); // and lastly the delta angles UnpackAngles(ops->pm_state.delta_angles, old_angles); UnpackAngles(ps->pm_state.delta_angles, new_angles); VectorCopy(new_angles, angles); // check for small delta angles, and interpolate them if (!VectorCompare(new_angles, new_angles)) { VectorSubtract(old_angles, new_angles, angles); vec_t f = VectorLength(angles); if (f < 15.0) { AngleLerp(old_angles, new_angles, cl.lerp, angles); } } VectorAdd(r_view.angles, angles, r_view.angles); ClampAngles(r_view.angles); if (cl.frame.ps.pm_state.type == PM_DEAD) { // look only on x axis r_view.angles[0] = 0.0; r_view.angles[2] = 45.0; } // and finally set the view directional vectors AngleVectors(r_view.angles, r_view.forward, r_view.right, r_view.up); }
/* * @brief Entry point for client-side prediction. For each server frame, run * the player movement code with the user commands we've sent to the server * but have not yet received acknowledgment for. Store the resulting move so * that it may be interpolated into by Cl_UpdateView. * * Most of the work is passed off to the client game, which is responsible for * the implementation Pm_Move. */ void Cl_PredictMovement(void) { if (Cl_UsePrediction()) { const uint32_t current = cls.net_chan.outgoing_sequence; uint32_t ack = cls.net_chan.incoming_acknowledged; // if we are too far out of date, just freeze in place if (current - ack >= CMD_BACKUP) { Com_Debug("Exceeded CMD_BACKUP\n"); return; } GList *cmds = NULL; while (++ack <= current) { cmds = g_list_append(cmds, &cl.cmds[ack & CMD_MASK]); } cls.cgame->PredictMovement(cmds); g_list_free(cmds); } }