/* * @brief Sets the kick value based on recent events such as falling. Firing of * weapons may also set the kick value, and we factor that in here as well. */ static void G_ClientKickAngles(g_edict_t *ent) { int16_t *kick_angles = ent->client->ps.pm_state.kick_angles; vec3_t kick; UnpackAngles(kick_angles, kick); // add in any event-based feedback switch (ent->s.event) { case EV_CLIENT_LAND: kick[PITCH] += 2.5; break; case EV_CLIENT_FALL: kick[PITCH] += 5.0; break; case EV_CLIENT_FALL_FAR: kick[PITCH] += 10.0; break; default: break; } // and any velocity-based feedback vec_t forward = DotProduct(ent->locals.velocity, ent->client->locals.forward); kick[PITCH] += forward / 450.0; vec_t right = DotProduct(ent->locals.velocity, ent->client->locals.right); kick[ROLL] += right / 350.0; // now interpolate the kick angles towards neutral over time vec_t delta = VectorLength(kick); if (!delta) // no kick, we're done return; // we recover from kick at a rate based on the kick itself delta = 0.5 + delta * delta * gi.frame_seconds; int32_t i; for (i = 0; i < 3; i++) { // clear angles smaller than our delta to avoid oscillations if (fabs(kick[i]) <= delta) { kick[i] = 0.0; } else if (kick[i] > 0.0) { kick[i] -= delta; } else { kick[i] += delta; } } PackAngles(kick, kick_angles); }
/* * @brief Adds view kick in the specified direction to the specified client. */ void G_ClientDamageKick(g_entity_t *ent, const vec3_t dir, const vec_t kick) { vec3_t old_kick_angles, kick_angles; UnpackAngles(ent->client->ps.pm_state.kick_angles, old_kick_angles); VectorClear(kick_angles); kick_angles[PITCH] = DotProduct(dir, ent->client->locals.forward) * kick * KICK_SCALE; kick_angles[ROLL] = DotProduct(dir, ent->client->locals.right) * kick * KICK_SCALE; //gi.Print("kicked %s from %s at %1.2f\n", vtos(kick_angles), vtos(dir), kick); VectorAdd(old_kick_angles, kick_angles, kick_angles); PackAngles(kick_angles, ent->client->ps.pm_state.kick_angles); }
/* * @brief Run recent movement commands through the player movement code * locally, storing the resulting origin and angles so that they may be * interpolated to by Cl_UpdateView. */ void Cg_PredictMovement(const GList *cmds) { pm_move_t pm; // copy current state to into the move memset(&pm, 0, sizeof(pm)); pm.s = cgi.client->frame.ps.pm_state; pm.ground_entity = cgi.client->predicted_state.ground_entity; pm.PointContents = cgi.PointContents; pm.Trace = Cg_PredictMovement_Trace; pm.Debug = Cg_PredictMovement_Debug; const GList *e = cmds; // run frames while (e) { const cl_cmd_t *cmd = (cl_cmd_t *) e->data; if (cmd->cmd.msec) { pm.cmd = cmd->cmd; Pm_Move(&pm); // for each movement, check for stair interaction and interpolate if (pm.s.flags & PMF_ON_STAIRS) { cgi.client->predicted_state.step_time = cmd->time; cgi.client->predicted_state.step_interval = 120 * (fabs(pm.step) / 16.0); cgi.client->predicted_state.step = pm.step; } // save for debug checking const uint32_t frame = (intptr_t) (cmd - cgi.client->cmds); VectorCopy(pm.s.origin, cgi.client->predicted_state.origins[frame]); } e = e->next; } // copy results out for rendering UnpackVector(pm.s.origin, cgi.client->predicted_state.origin); UnpackVector(pm.s.view_offset, cgi.client->predicted_state.view_offset); UnpackAngles(pm.cmd.angles, cgi.client->predicted_state.view_angles); cgi.client->predicted_state.ground_entity = pm.ground_entity; }
/* * @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 Checks for client side prediction errors. These will occur under normal gameplay * conditions if the client is pushed by another entity on the server (projectile, platform, etc.). */ void Cl_CheckPredictionError(void) { if (!cls.cgame->UsePrediction()) { return; } cl_predicted_state_t *pr = &cl.predicted_state; if (cl.delta_frame) { // calculate the last cl_cmd_t we sent that the server has processed const uint32_t cmd = cls.net_chan.incoming_acknowledged & CMD_MASK; // subtract what the server returned with what we had predicted it to be VectorSubtract(cl.frame.ps.pm_state.origin, pr->origins[cmd], pr->error); // if the error is too large, it was likely a teleport or respawn, so ignore it const vec_t len = VectorLength(pr->error); if (len > 64.0) { Com_Debug(DEBUG_CLIENT, "Clear %s\n", vtos(pr->error)); VectorClear(pr->error); } else if (len > 0.1) { Com_Debug(DEBUG_CLIENT, "Error %s\n", vtos(pr->error)); } } else { Com_Debug(DEBUG_CLIENT, "No delta\n"); VectorCopy(cl.frame.ps.pm_state.origin, pr->view.origin); UnpackVector(cl.frame.ps.pm_state.view_offset, pr->view.offset); UnpackAngles(cl.frame.ps.pm_state.view_angles, pr->view.angles); VectorClear(pr->error); } }
/* * @brief Sets the kick value based on recent events such as falling. Firing of * weapons may also set the kick value, and we factor that in here as well. */ static void G_ClientKickAngles(g_entity_t *ent) { uint16_t *kick_angles = ent->client->ps.pm_state.kick_angles; // spectators and dead clients receive no kick angles if (ent->client->ps.pm_state.type != PM_NORMAL) { VectorClear(kick_angles); return; } vec3_t kick; UnpackAngles(kick_angles, kick); // un-clamp them so that we can work with small signed values near zero for (int32_t i = 0; i < 3; i++) { if (kick[i] > 180.0) kick[i] -= 360.0; } // add in any event-based feedback switch (ent->s.event) { case EV_CLIENT_LAND: kick[PITCH] += 2.5; break; case EV_CLIENT_FALL: kick[PITCH] += 5.0; break; case EV_CLIENT_FALL_FAR: kick[PITCH] += 10.0; break; default: break; } // and any velocity-based feedback vec_t forward = DotProduct(ent->locals.velocity, ent->client->locals.forward); kick[PITCH] += forward / 450.0; vec_t right = DotProduct(ent->locals.velocity, ent->client->locals.right); kick[ROLL] += right / 400.0; // now interpolate the kick angles towards neutral over time vec_t delta = VectorLength(kick); if (!delta) // no kick, we're done return; // we recover from kick at a rate based on the kick itself delta = 0.5 + delta * delta * gi.frame_seconds; for (int32_t i = 0; i < 3; i++) { // clear angles smaller than our delta to avoid oscillations if (fabs(kick[i]) <= delta) { kick[i] = 0.0; } else if (kick[i] > 0.0) { kick[i] -= delta; } else { kick[i] += delta; } } PackAngles(kick, kick_angles); }