2016-03-27 11:49:47 +02:00
|
|
|
/*
|
|
|
|
===========================================================================
|
2023-04-30 00:02:16 +02:00
|
|
|
Copyright (C) 2023 the OpenMoHAA team
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
This file is part of OpenMoHAA source code.
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
OpenMoHAA source code is free software; you can redistribute it
|
2016-03-27 11:49:47 +02:00
|
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
|
|
or (at your option) any later version.
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
OpenMoHAA source code is distributed in the hope that it will be
|
2016-03-27 11:49:47 +02:00
|
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
2023-04-30 00:02:16 +02:00
|
|
|
along with OpenMoHAA source code; if not, write to the Free Software
|
2016-03-27 11:49:47 +02:00
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
===========================================================================
|
|
|
|
*/
|
2023-04-30 00:02:16 +02:00
|
|
|
|
|
|
|
// DESCRIPTION:
|
|
|
|
// this file generates cg.predicted_player_state by either
|
2016-03-27 11:49:47 +02:00
|
|
|
// interpolating between snapshots from the server or locally predicting
|
2023-04-30 00:02:16 +02:00
|
|
|
// ahead the client's movement. It also handles local physics interaction,
|
|
|
|
// like fragments bouncing off walls
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
#include "cg_local.h"
|
|
|
|
|
|
|
|
static pmove_t cg_pmove;
|
|
|
|
|
|
|
|
static int cg_numSolidEntities;
|
|
|
|
static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT];
|
|
|
|
static int cg_numTriggerEntities;
|
2023-04-30 00:02:16 +02:00
|
|
|
//static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT];
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
====================
|
|
|
|
CG_BuildSolidList
|
|
|
|
|
|
|
|
When a new cg.snap has been set, this function builds a sublist
|
|
|
|
of the entities that are actually solid, to make for more
|
|
|
|
efficient collision detection
|
|
|
|
====================
|
|
|
|
*/
|
2023-04-30 00:02:16 +02:00
|
|
|
void CG_BuildSolidList( void )
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
int i;
|
|
|
|
centity_t *cent;
|
|
|
|
snapshot_t *snap;
|
|
|
|
entityState_t *ent;
|
|
|
|
|
|
|
|
cg_numSolidEntities = 0;
|
|
|
|
cg_numTriggerEntities = 0;
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport )
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
snap = cg.nextSnap;
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
snap = cg.snap;
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
for ( i = 0 ; i < snap->numEntities ; i++ )
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
cent = &cg_entities[ snap->entities[ i ].number ];
|
|
|
|
ent = ¢->currentState;
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
if ( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER )
|
|
|
|
{
|
|
|
|
/*
|
2016-03-27 11:49:47 +02:00
|
|
|
cg_triggerEntities[cg_numTriggerEntities] = cent;
|
|
|
|
cg_numTriggerEntities++;
|
2023-04-30 00:02:16 +02:00
|
|
|
*/
|
2016-03-27 11:49:47 +02:00
|
|
|
continue;
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
if ( cent->nextState.solid )
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
cg_solidEntities[cg_numSolidEntities] = cent;
|
|
|
|
cg_numSolidEntities++;
|
|
|
|
continue;
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
====================
|
|
|
|
CG_ClipMoveToEntities
|
|
|
|
|
|
|
|
====================
|
|
|
|
*/
|
2023-04-30 00:02:16 +02:00
|
|
|
static void CG_ClipMoveToEntities
|
|
|
|
(
|
|
|
|
const vec3_t start,
|
|
|
|
const vec3_t mins,
|
|
|
|
const vec3_t maxs,
|
|
|
|
const vec3_t end,
|
|
|
|
int skipNumber,
|
|
|
|
int mask,
|
|
|
|
trace_t *tr,
|
|
|
|
qboolean cylinder
|
|
|
|
)
|
|
|
|
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
trace_t trace;
|
2016-03-27 11:49:47 +02:00
|
|
|
entityState_t *ent;
|
2023-04-30 00:02:16 +02:00
|
|
|
clipHandle_t cmodel;
|
|
|
|
vec3_t bmins, bmaxs;
|
|
|
|
vec3_t origin, angles;
|
|
|
|
centity_t *cent;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
for ( i = 0 ; i < cg_numSolidEntities ; i++)
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
cent = cg_solidEntities[ i ];
|
|
|
|
ent = ¢->currentState;
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
if ( ent->number == skipNumber )
|
2016-03-27 11:49:47 +02:00
|
|
|
continue;
|
2023-04-30 00:02:16 +02:00
|
|
|
|
|
|
|
if ( ent->solid == SOLID_BMODEL )
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
// special value for bmodel
|
|
|
|
cmodel = cgi.CM_InlineModel( ent->modelindex );
|
2023-04-30 00:02:16 +02:00
|
|
|
if ( !cmodel )
|
|
|
|
continue;
|
2016-03-27 11:49:47 +02:00
|
|
|
VectorCopy( cent->lerpAngles, angles );
|
2023-04-30 00:02:16 +02:00
|
|
|
VectorCopy( cent->lerpOrigin, origin );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
IntegerToBoundingBox( ent->solid, bmins, bmaxs );
|
|
|
|
cmodel = cgi.CM_TempBoxModel( bmins, bmaxs, CONTENTS_BODY ); //CONTENTS_SOLID | CONTENTS_BODY );
|
2016-03-27 11:49:47 +02:00
|
|
|
VectorCopy( vec3_origin, angles );
|
|
|
|
VectorCopy( cent->lerpOrigin, origin );
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
cgi.CM_TransformedBoxTrace( &trace, start, end, mins, maxs, cmodel, mask, origin, angles, cylinder );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
if (trace.allsolid || trace.fraction < tr->fraction)
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
trace.entityNum = ent->number;
|
|
|
|
*tr = trace;
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
|
|
|
else if (trace.startsolid)
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
tr->startsolid = qtrue;
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CG_ShowTrace
|
|
|
|
(
|
|
|
|
trace_t *trace,
|
|
|
|
int passent,
|
|
|
|
const char *reason
|
|
|
|
)
|
|
|
|
|
|
|
|
{
|
|
|
|
char text[ 1024 ];
|
|
|
|
|
|
|
|
assert( reason );
|
|
|
|
assert( trace );
|
|
|
|
|
|
|
|
sprintf( text, "%0.2f : Pass (%d) Frac %f Hit (%d): '%s'\n",
|
|
|
|
( float )cg.time / 1000.0f, passent, trace->fraction, trace->entityNum, reason ? reason : "" );
|
|
|
|
|
|
|
|
if ( cg_traceinfo->integer == 3 )
|
|
|
|
{
|
|
|
|
cgi.DebugPrintf( text );
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
2023-04-30 00:02:16 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
cgi.DPrintf( text );
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CG_Trace
|
|
|
|
================
|
|
|
|
*/
|
2023-04-30 00:02:16 +02:00
|
|
|
void CG_Trace
|
|
|
|
(
|
|
|
|
trace_t *result,
|
|
|
|
const vec3_t start,
|
|
|
|
const vec3_t mins,
|
|
|
|
const vec3_t maxs,
|
|
|
|
const vec3_t end,
|
|
|
|
int skipNumber,
|
|
|
|
int mask,
|
|
|
|
qboolean cylinder,
|
|
|
|
qboolean cliptoentities,
|
|
|
|
const char * description
|
|
|
|
)
|
|
|
|
|
|
|
|
{
|
|
|
|
trace_t t;
|
|
|
|
|
|
|
|
cgi.CM_BoxTrace ( &t, start, end, mins, maxs, 0, mask, cylinder );
|
|
|
|
t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
|
|
|
|
|
|
|
|
// If starting in a solid make sure the world is set as the entitynum
|
|
|
|
|
|
|
|
if ( t.startsolid )
|
|
|
|
t.entityNum = ENTITYNUM_WORLD;
|
|
|
|
|
|
|
|
if ( cliptoentities )
|
|
|
|
{
|
|
|
|
// check all other solid models
|
|
|
|
CG_ClipMoveToEntities(start, mins, maxs, end, skipNumber, mask, &t, cylinder );
|
|
|
|
}
|
|
|
|
|
|
|
|
*result = t;
|
|
|
|
|
|
|
|
if ( cg_traceinfo->integer )
|
|
|
|
{
|
|
|
|
CG_ShowTrace( result, skipNumber, description );
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CG_PlayerTrace
|
|
|
|
================
|
|
|
|
*/
|
2023-04-30 00:02:16 +02:00
|
|
|
void CG_PlayerTrace
|
|
|
|
(
|
|
|
|
trace_t *result,
|
|
|
|
const vec3_t start,
|
|
|
|
const vec3_t mins,
|
|
|
|
const vec3_t maxs,
|
|
|
|
const vec3_t end,
|
|
|
|
int skipNumber,
|
|
|
|
int mask,
|
2023-04-30 01:42:57 +02:00
|
|
|
qboolean cylinder,
|
|
|
|
qboolean tracedeep
|
2023-04-30 00:02:16 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
{
|
|
|
|
CG_Trace( result, start, mins, maxs, end, skipNumber, mask, cylinder, qtrue, "PlayerTrace" );
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CG_PointContents
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
int CG_PointContents( const vec3_t point, int passEntityNum ) {
|
|
|
|
int i;
|
|
|
|
entityState_t *ent;
|
|
|
|
centity_t *cent;
|
|
|
|
clipHandle_t cmodel;
|
|
|
|
int contents;
|
|
|
|
|
|
|
|
contents = cgi.CM_PointContents (point, 0);
|
|
|
|
|
|
|
|
for ( i = 0 ; i < cg_numSolidEntities ; i++ ) {
|
|
|
|
cent = cg_solidEntities[ i ];
|
|
|
|
|
|
|
|
ent = ¢->currentState;
|
|
|
|
|
|
|
|
if ( ent->number == passEntityNum ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ent->solid != SOLID_BMODEL) { // special value for bmodel
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmodel = cgi.CM_InlineModel( ent->modelindex );
|
|
|
|
if ( !cmodel ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
contents |= cgi.CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles );
|
|
|
|
}
|
|
|
|
|
|
|
|
return contents;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
/*
|
|
|
|
========================
|
|
|
|
CG_InterpolatePlayerStateCamera
|
|
|
|
|
|
|
|
Generates cg.predicted_player_state by interpolating between
|
|
|
|
cg.snap->player_state and cg.nextFrame->player_state
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
static void CG_InterpolatePlayerStateCamera( void )
|
|
|
|
{
|
|
|
|
float f;
|
|
|
|
int i;
|
|
|
|
snapshot_t *prev, *next;
|
|
|
|
|
|
|
|
prev = cg.snap;
|
|
|
|
next = cg.nextSnap;
|
|
|
|
|
|
|
|
//
|
|
|
|
// copy in the current ones if nothing else
|
|
|
|
//
|
|
|
|
VectorCopy( cg.predicted_player_state.camera_angles, cg.camera_angles );
|
|
|
|
VectorCopy( cg.predicted_player_state.camera_origin, cg.camera_origin );
|
|
|
|
cg.camera_fov = cg.predicted_player_state.fov;
|
|
|
|
|
|
|
|
// if the next frame is a teleport, we can't lerp to it
|
|
|
|
if ( cg.nextFrameCameraCut )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
!next ||
|
|
|
|
next->serverTime <= prev->serverTime
|
|
|
|
)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime );
|
|
|
|
|
|
|
|
// interpolate fov
|
|
|
|
cg.camera_fov = prev->ps.fov + f * ( next->ps.fov - prev->ps.fov );
|
|
|
|
|
|
|
|
if ( !( cg.snap->ps.pm_flags & PMF_CAMERA_VIEW ) )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
|
|
{
|
|
|
|
cg.camera_origin[i] = prev->ps.camera_origin[i] + f * (next->ps.camera_origin[i] - prev->ps.camera_origin[i] );
|
|
|
|
cg.camera_angles[i] = LerpAngle( prev->ps.camera_angles[i], next->ps.camera_angles[i], f );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
/*
|
|
|
|
========================
|
|
|
|
CG_InterpolatePlayerState
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
Generates cg.predicted_player_state by interpolating between
|
2016-03-27 11:49:47 +02:00
|
|
|
cg.snap->player_state and cg.nextFrame->player_state
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
static void CG_InterpolatePlayerState( qboolean grabAngles ) {
|
|
|
|
float f;
|
|
|
|
int i;
|
|
|
|
playerState_t *out;
|
|
|
|
snapshot_t *prev, *next;
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
out = &cg.predicted_player_state;
|
2016-03-27 11:49:47 +02:00
|
|
|
prev = cg.snap;
|
|
|
|
next = cg.nextSnap;
|
|
|
|
|
|
|
|
*out = cg.snap->ps;
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
// interpolate the camera if necessary
|
|
|
|
CG_InterpolatePlayerStateCamera();
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
// if we are still allowing local input, short circuit the view angles
|
|
|
|
if ( grabAngles ) {
|
|
|
|
usercmd_t cmd;
|
|
|
|
int cmdNum;
|
|
|
|
|
|
|
|
cmdNum = cgi.GetCurrentCmdNumber();
|
|
|
|
cgi.GetUserCmd( cmdNum, &cmd );
|
|
|
|
|
|
|
|
PM_UpdateViewAngles( out, &cmd );
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the next frame is a teleport, we can't lerp to it
|
|
|
|
if ( cg.nextFrameTeleport ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !next || next->serverTime <= prev->serverTime ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
f = cg.frameInterpolation;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
i = next->ps.bobCycle;
|
|
|
|
if ( i < prev->ps.bobCycle ) {
|
|
|
|
i += 256; // handle wraparound
|
|
|
|
}
|
|
|
|
out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle );
|
|
|
|
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
|
|
out->origin[i] = prev->ps.origin[i] + f * (next->ps.origin[i] - prev->ps.origin[i] );
|
|
|
|
if ( !grabAngles ) {
|
|
|
|
out->viewangles[i] = LerpAngle(
|
|
|
|
prev->ps.viewangles[i], next->ps.viewangles[i], f );
|
|
|
|
}
|
|
|
|
out->velocity[i] = prev->ps.velocity[i] +
|
|
|
|
f * (next->ps.velocity[i] - prev->ps.velocity[i] );
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
CG_PredictPlayerState
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
Generates cg.predicted_player_state for the current cg.time
|
|
|
|
cg.predicted_player_state is guaranteed to be valid after exiting.
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
For demo playback, this will be an interpolation between two valid
|
|
|
|
playerState_t.
|
|
|
|
|
|
|
|
For normal gameplay, it will be the result of predicted usercmd_t on
|
|
|
|
top of the most recent playerState_t received from the server.
|
|
|
|
|
|
|
|
Each new snapshot will usually have one or more new usercmd over the last,
|
|
|
|
but we simulate all unacknowledged commands each time, not just the new ones.
|
|
|
|
This means that on an internet connection, quite a few pmoves may be issued
|
|
|
|
each frame.
|
|
|
|
|
|
|
|
OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t
|
|
|
|
differs from the predicted one. Would require saving all intermediate
|
|
|
|
playerState_t during prediction.
|
|
|
|
|
|
|
|
We detect prediction errors and allow them to be decayed off over several frames
|
|
|
|
to ease the jerk.
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void CG_PredictPlayerState( void ) {
|
2023-04-30 00:02:16 +02:00
|
|
|
int cmdNum, current;
|
2016-03-27 11:49:47 +02:00
|
|
|
playerState_t oldPlayerState;
|
2023-04-30 00:02:16 +02:00
|
|
|
qboolean moved;
|
|
|
|
usercmd_t oldestCmd;
|
|
|
|
usercmd_t latestCmd;
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
cg.hyperspace = qfalse; // will be set if touching a trigger_teleport
|
|
|
|
|
|
|
|
// if this is the first frame we must guarantee
|
2023-04-30 00:02:16 +02:00
|
|
|
// predicted_player_state is valid even if there is some
|
2016-03-27 11:49:47 +02:00
|
|
|
// other error condition
|
|
|
|
if ( !cg.validPPS ) {
|
|
|
|
cg.validPPS = qtrue;
|
2023-04-30 00:02:16 +02:00
|
|
|
cg.predicted_player_state = cg.snap->ps;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// demo playback just copies the moves
|
2023-04-30 00:02:16 +02:00
|
|
|
if (
|
|
|
|
cg.demoPlayback ||
|
|
|
|
( cg.snap->ps.pm_flags & PMF_NO_PREDICTION ) ||
|
|
|
|
( cg.snap->ps.pm_flags & PMF_FROZEN )
|
|
|
|
)
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
CG_InterpolatePlayerState( qfalse );
|
|
|
|
return;
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
// non-predicting local movement will grab the latest angles
|
2023-04-30 00:02:16 +02:00
|
|
|
//FIXME
|
|
|
|
// Noclip is jittery for some reason, so I'm disabling prediction while noclipping
|
2023-04-30 01:42:57 +02:00
|
|
|
if ( cg_nopredict->integer || cg_synchronousClients->integer || (cg.snap->ps.pm_type == PM_NOCLIP) ) {
|
2016-03-27 11:49:47 +02:00
|
|
|
CG_InterpolatePlayerState( qtrue );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepare for pmove
|
2023-04-30 00:02:16 +02:00
|
|
|
cg_pmove.ps = &cg.predicted_player_state;
|
|
|
|
cg_pmove.trace = CG_PlayerTrace;
|
|
|
|
cg_pmove.pointcontents = CG_PointContents;
|
|
|
|
|
|
|
|
if ( cg_pmove.ps->pm_type == PM_DEAD )
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
cg_pmove.tracemask = MASK_PLAYERSOLID;
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME
|
|
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR )
|
|
|
|
{
|
2016-03-27 11:49:47 +02:00
|
|
|
cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies
|
2023-04-30 00:02:16 +02:00
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0;
|
|
|
|
|
|
|
|
// save the state before the pmove so we can detect transitions
|
2023-04-30 00:02:16 +02:00
|
|
|
oldPlayerState = cg.predicted_player_state;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
current = cgi.GetCurrentCmdNumber();
|
|
|
|
|
|
|
|
// if we don't have the commands right after the snapshot, we
|
|
|
|
// can't accurately predict a current position, so just freeze at
|
|
|
|
// the last good position we had
|
|
|
|
cmdNum = current - CMD_BACKUP + 1;
|
|
|
|
cgi.GetUserCmd( cmdNum, &oldestCmd );
|
|
|
|
if ( oldestCmd.serverTime > cg.snap->ps.commandTime
|
|
|
|
&& oldestCmd.serverTime < cg.time ) { // special check for map_restart
|
|
|
|
if ( cg_showmiss->integer ) {
|
2023-04-30 00:02:16 +02:00
|
|
|
cgi.Printf ("exceeded PACKET_BACKUP on commands\n");
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the latest command so we can know which commands are from previous map_restarts
|
|
|
|
cgi.GetUserCmd( current, &latestCmd );
|
|
|
|
|
|
|
|
// get the most recent information we have, even if
|
|
|
|
// the server time is beyond our current cg.time,
|
|
|
|
// because predicted player positions are going to
|
|
|
|
// be ahead of everything else anyway
|
|
|
|
if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) {
|
2023-04-30 00:02:16 +02:00
|
|
|
cg.predicted_player_state = cg.nextSnap->ps;
|
2016-03-27 11:49:47 +02:00
|
|
|
cg.physicsTime = cg.nextSnap->serverTime;
|
|
|
|
} else {
|
2023-04-30 00:02:16 +02:00
|
|
|
cg.predicted_player_state = cg.snap->ps;
|
2016-03-27 11:49:47 +02:00
|
|
|
cg.physicsTime = cg.snap->serverTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
// run cmds
|
|
|
|
moved = qfalse;
|
|
|
|
for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) {
|
|
|
|
// get the command
|
|
|
|
cgi.GetUserCmd( cmdNum, &cg_pmove.cmd );
|
|
|
|
|
|
|
|
// don't do anything if the time is before the snapshot player time
|
2023-04-30 00:02:16 +02:00
|
|
|
if ( cg_pmove.cmd.serverTime <= cg.predicted_player_state.commandTime ) {
|
2016-03-27 11:49:47 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// don't do anything if the command was from a previous map_restart
|
|
|
|
if ( cg_pmove.cmd.serverTime > latestCmd.serverTime ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for a prediction error from last frame
|
|
|
|
// on a lan, this will often be the exact value
|
|
|
|
// from the snapshot, but on a wan we will have
|
|
|
|
// to predict several commands to get to the point
|
|
|
|
// we want to compare
|
2023-04-30 00:02:16 +02:00
|
|
|
if ( cg.predicted_player_state.commandTime == oldPlayerState.commandTime ) {
|
2016-03-27 11:49:47 +02:00
|
|
|
vec3_t delta;
|
|
|
|
float len;
|
|
|
|
|
|
|
|
if ( cg.thisFrameTeleport ) {
|
|
|
|
// a teleport will not cause an error decay
|
|
|
|
VectorClear( cg.predictedError );
|
2023-04-30 00:02:16 +02:00
|
|
|
cg.thisFrameTeleport = qfalse;
|
2016-03-27 11:49:47 +02:00
|
|
|
if ( cg_showmiss->integer ) {
|
2023-04-30 00:02:16 +02:00
|
|
|
cgi.Printf( "PredictionTeleport\n" );
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
vec3_t adjusted;
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
CG_AdjustPositionForMover( cg.predicted_player_state.origin,
|
|
|
|
cg.predicted_player_state.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted );
|
2016-03-27 11:49:47 +02:00
|
|
|
VectorSubtract( oldPlayerState.origin, adjusted, delta );
|
|
|
|
len = VectorLength( delta );
|
|
|
|
if ( len > 0.1 ) {
|
|
|
|
if ( cg_showmiss->integer ) {
|
2023-04-30 00:02:16 +02:00
|
|
|
cgi.Printf("Prediction miss: %f\n", len);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
if ( cg_errorDecay->integer ) {
|
|
|
|
int t;
|
|
|
|
float f;
|
|
|
|
|
|
|
|
t = cg.time - cg.predictedErrorTime;
|
|
|
|
f = ( cg_errorDecay->value - t ) / cg_errorDecay->value;
|
|
|
|
if ( f < 0 ) {
|
|
|
|
f = 0;
|
|
|
|
}
|
|
|
|
if ( f > 0 && cg_showmiss->integer ) {
|
2023-04-30 00:02:16 +02:00
|
|
|
cgi.Printf("Double prediction decay: %f\n", f);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
VectorScale( cg.predictedError, f, cg.predictedError );
|
|
|
|
} else {
|
|
|
|
VectorClear( cg.predictedError );
|
|
|
|
}
|
|
|
|
VectorAdd( delta, cg.predictedError, cg.predictedError );
|
|
|
|
cg.predictedErrorTime = cg.oldTime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
// if our feet are falling, don't try to move
|
|
|
|
if ( cg_pmove.ps->feetfalling && ( cg_pmove.waterlevel < 2 ) )
|
|
|
|
{
|
|
|
|
cg_pmove.cmd.forwardmove = 0;
|
|
|
|
cg_pmove.cmd.rightmove = 0;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
Pmove (&cg_pmove);
|
2023-04-30 00:02:16 +02:00
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
moved = qtrue;
|
|
|
|
|
|
|
|
// add push trigger movement effects
|
2023-04-30 00:02:16 +02:00
|
|
|
//CG_TouchTriggerPrediction();
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( cg_showmiss->integer > 1 ) {
|
2023-04-30 00:02:16 +02:00
|
|
|
cgi.Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time );
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-04-30 00:02:16 +02:00
|
|
|
// interpolate the camera if necessary
|
|
|
|
CG_InterpolatePlayerStateCamera();
|
|
|
|
|
|
|
|
// adjust for the movement of the groundentity
|
|
|
|
CG_AdjustPositionForMover( cg.predicted_player_state.origin,
|
|
|
|
cg.predicted_player_state.groundEntityNum,
|
|
|
|
cg.snap->serverTime, cg.time, cg.predicted_player_state.origin );
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
if ( !moved ) {
|
|
|
|
if ( cg_showmiss->integer ) {
|
2023-04-30 00:02:16 +02:00
|
|
|
cgi.Printf( "not moved\n" );
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// fire events and other transition triggered things
|
2023-04-30 00:02:16 +02:00
|
|
|
CG_TransitionPlayerState( &cg.predicted_player_state, &oldPlayerState );
|
|
|
|
|
|
|
|
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|