This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/engine/client/cl_pred.c
2022-06-27 01:15:14 +03:00

242 lines
6.0 KiB
C

//=======================================================================
// Copyright XashXT Group 2008 ©
// cl_pred.c - client movement prediction
//=======================================================================
#include "common.h"
#include "client.h"
#include "const.h"
#include "pm_local.h"
#include "net_encode.h"
#define MAX_FORWARD 6
qboolean CL_IsPredicted( void )
{
cl_entity_t *player = CL_GetLocalPlayer();
if( !player )
return false;
if( !cl.frame.valid )
return false;
if( cls.netchan.outgoing_sequence - cls.netchan.incoming_sequence >= CL_UPDATE_BACKUP - 1 )
return false;
if( !cl_predict->integer )
return false;
return true;
}
/*
===========
CL_PostRunCmd
Done after running a player command.
===========
*/
void CL_PostRunCmd( usercmd_t *ucmd, int random_seed )
{
local_state_t *from, *to;
from = &cl.frames[cl.delta_sequence & CL_UPDATE_MASK].local;
to = &cl.frame.local;
clgame.dllFuncs.pfnPostRunCmd( from, to, ucmd, clgame.pmove->runfuncs, cl.time, random_seed );
}
/*
===================
CL_CheckPredictionError
===================
*/
void CL_CheckPredictionError( void )
{
int frame;
vec3_t delta;
float flen;
if( !CL_IsPredicted( )) return;
// calculate the last usercmd_t we sent that the server has processed
frame = cls.netchan.incoming_acknowledged;
frame &= CL_UPDATE_MASK;
// compare what the server returned with what we had predicted it to be
VectorSubtract( cl.frame.local.client.origin, cl.predicted_origins[frame], delta );
// save the prediction error for interpolation
flen = fabs( delta[0] ) + fabs( delta[1] ) + fabs( delta[2] );
if( flen > 80 )
{
// a teleport or something
VectorClear( cl.prediction_error );
}
else
{
if( cl_showmiss->integer && flen > 0.1f ) Msg( "prediction miss: %g\n", flen );
VectorCopy( cl.frame.local.client.origin, cl.predicted_origins[frame] );
// save for error itnerpolation
VectorCopy( delta, cl.prediction_error );
}
}
/*
===============
CL_SetIdealPitch
===============
*/
void CL_SetIdealPitch( cl_entity_t *ent )
{
float angleval, sinval, cosval;
pmtrace_t tr;
vec3_t top, bottom;
float z[MAX_FORWARD];
int i, j;
int step, dir, steps;
if( !( cl.frame.local.client.flags & FL_ONGROUND ))
return;
angleval = ent->angles[YAW] * M_PI * 2 / 360;
SinCos( angleval, &sinval, &cosval );
for( i = 0; i < MAX_FORWARD; i++ )
{
top[0] = ent->origin[0] + cosval * (i + 3) * 12;
top[1] = ent->origin[1] + sinval * (i + 3) * 12;
top[2] = ent->origin[2] + cl.frame.local.client.view_ofs[2];
bottom[0] = top[0];
bottom[1] = top[1];
bottom[2] = top[2] - 160;
// skip any monsters (only world and brush models)
tr = PM_PlayerTrace( clgame.pmove, top, bottom, PM_STUDIO_IGNORE, 2, -1, NULL );
if( tr.allsolid ) return; // looking at a wall, leave ideal the way is was
if( tr.fraction == 1.0f )
return; // near a dropoff
z[i] = top[2] + tr.fraction * (bottom[2] - top[2]);
}
dir = 0;
steps = 0;
for( j = 1; j < i; j++ )
{
step = z[j] - z[j-1];
if( step > -ON_EPSILON && step < ON_EPSILON )
continue;
if( dir && ( step-dir > ON_EPSILON || step-dir < -ON_EPSILON ))
return; // mixed changes
steps++;
dir = step;
}
if( !dir )
{
cl.refdef.idealpitch = 0;
return;
}
if( steps < 2 ) return;
cl.refdef.idealpitch = -dir * cl_idealpitchscale->value;
}
/*
=================
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( cl.refdef.paused || cls.key_dest == key_menu ) return;
player = CL_GetLocalPlayer ();
viewent = CL_GetEntityByIndex( cl.refdef.viewentity );
cd = &cl.frame.local.client;
CL_SetIdealPitch( player );
if( cls.demoplayback && viewent )
{
// restore viewangles from angles
cl.refdef.cl_viewangles[PITCH] = -viewent->angles[PITCH] * 3;
cl.refdef.cl_viewangles[YAW] = viewent->angles[YAW];
cl.refdef.cl_viewangles[ROLL] = 0; // roll will be computed in view.cpp
}
// 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;
VectorCopy( cl.refdef.cl_viewangles, cl.predicted_angles );
VectorCopy( cd->view_ofs, cl.predicted_viewofs );
CL_PostRunCmd( cl.refdef.cmd, cls.netchan.outgoing_sequence );
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[frame];
// motor!
clgame.dllFuncs.pfnPlayerMove( clgame.pmove, false ); // run frames
// save for debug checking
VectorCopy( clgame.pmove->origin, cl.predicted_origins[frame-1] );
clgame.pmove->runfuncs = true;
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->angles, cl.predicted_angles );
VectorCopy( clgame.pmove->velocity, cl.predicted_velocity );
}