/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it 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. This program is distributed in the hope that it will be 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 along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "cg_local.h" int cg_numSolids; entity_state_t *cg_solidList[MAX_PARSE_ENTITIES]; /* =================== CG_CheckPredictionError =================== */ void CG_CheckPredictionError( void ) { int frame; int delta[3]; if( !cg_predict->integer || cg.demoPlaying || (cg.frame.playerState.pmove.pm_flags & PMF_NO_PREDICTION) ) return; trap_NET_GetCurrentState( &frame, NULL ); // calculate the last usercmd_t we sent that the server has processed frame = frame & CMD_MASK; // compare what the server returned with what we had predicted it to be VectorSubtract( cg.frame.playerState.pmove.origin, cg.predictedOrigins[frame], delta ); // save the prediction error for interpolation if( abs(delta[0]) > 128*16 || abs(delta[1]) > 128*16 || abs(delta[2]) > 128*16 ) { VectorClear( cg.predictionError ); // a teleport or something } else { if( cg_showMiss->integer && (delta[0] || delta[1] || delta[2]) ) CG_Printf( "prediction miss on %i: %i\n", cg.frame.serverFrame, delta[0] + delta[1] + delta[2] ); VectorCopy( cg.frame.playerState.pmove.origin, cg.predictedOrigins[frame] ); VectorScale( delta, (1.0/16.0), cg.predictionError ); // save for error interpolation } } /* ==================== CG_BuildSolidList ==================== */ void CG_BuildSolidList( void ) { int i; entity_state_t *ent; cg_numSolids = 0; for( i = 0; i < cg.frame.numEntities; i++ ) { ent = &cg.frame.parsedEntities[i & (MAX_PARSE_ENTITIES-1)]; if( ent->solid ) cg_solidList[cg_numSolids++] = ent; } } /* ==================== CG_ClipMoveToEntities ==================== */ void CG_ClipMoveToEntities( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int ignore, int contentmask, trace_t *tr ) { #define INTERPOLATEBOXES int i, x, zd, zu; trace_t trace; vec3_t origin, angles; entity_state_t *ent; struct cmodel_s *cmodel; vec3_t bmins, bmaxs; for( i = 0; i < cg_numSolids; i++ ) { ent = cg_solidList[i]; if( ent->number == ignore ) continue; if( !(contentmask & CONTENTS_CORPSE) && (ent->effects & EF_CORPSE) ) continue; if ( ent->solid == SOLID_BMODEL ) { // special value for bmodel cmodel = trap_CM_InlineModel( ent->modelindex ); if( !cmodel ) continue; #ifdef INTERPOLATEBOXES { int j; centity_t *cent = &cg_entities[ent->number]; VectorCopy( cent->ent.origin, origin ); for( j = 0; j < 3; j++ ) angles[j] = LerpAngle(cent->prev.angles[j], cent->current.angles[j], cg.lerpfrac ); } #else VectorCopy( ent->origin, origin ); VectorCopy( ent->angles, angles ); #endif } else { // encoded bbox x = 8 * (ent->solid & 31); zd = 8 * ((ent->solid>>5) & 31); zu = 8 * ((ent->solid>>10) & 63) - 32; bmins[0] = bmins[1] = -x; bmaxs[0] = bmaxs[1] = x; bmins[2] = -zd; bmaxs[2] = zu; #ifdef INTERPOLATEBOXES VectorCopy( cg_entities[ent->number].ent.origin, origin ); VectorClear( angles ); // boxes don't rotate #else VectorCopy( ent->origin, origin ); VectorClear( angles ); // boxes don't rotate #endif cmodel = trap_CM_ModelForBBox( bmins, bmaxs ); } trap_CM_TransformedBoxTrace( &trace, start, end, mins, maxs, cmodel, contentmask, origin, angles ); if( trace.allsolid || trace.fraction < tr->fraction ) { trace.ent = ent->number; *tr = trace; } else if( trace.startsolid ) { tr->startsolid = qtrue; } if( tr->allsolid ) return; } } /* ================ CG_Trace ================ */ void CG_Trace( trace_t *t, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int ignore, int contentmask ) { // check against world trap_CM_BoxTrace( t, start, end, mins, maxs, NULL, contentmask ); t->ent = 0; if( t->fraction == 0 ) return; // blocked by the world // check all other solid models CG_ClipMoveToEntities( start, mins, maxs, end, ignore, contentmask, t ); } /* ================ CG_PMTrace ================ */ void CG_PMTrace( trace_t *tr, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end ) { // wsw: pb disable player collision in race mode (thanks Medar) if( cg.frame.playerState.stats[STAT_GAMETYPE] == GAMETYPE_RACE ) CG_Trace( tr, start, mins, maxs, end, cgs.playerNum+1, MASK_DEADSOLID ); else CG_Trace( tr, start, mins, maxs, end, cgs.playerNum+1, MASK_PLAYERSOLID ); } /* ================ CG_PointContents ================ */ int CG_PointContents( vec3_t point ) { int i; entity_state_t *ent; struct cmodel_s *cmodel; int contents; contents = trap_CM_PointContents( point, NULL ); for( i = 0; i < cg_numSolids; i++ ) { ent = cg_solidList[i]; if( ent->solid != SOLID_BMODEL ) // special value for bmodel continue; cmodel = trap_CM_InlineModel( ent->modelindex ); if( cmodel ) contents |= trap_CM_TransformedPointContents ( point, cmodel, ent->origin, ent->angles ); } return contents; } int predictedSteps[CMD_BACKUP]; // for step smoothing /* ================= CG_PredictAddStep ================= */ void CG_PredictAddStep( int virtualtime, int predictiontime, int stepSize ) { float oldStep; int delta; int step; // check for stepping up before a previous step is completed delta = cg.realTime - cg.predictedStepTime; if (delta < PREDICTED_STEP_TIME) { oldStep = cg.predictedStep * ((float)(PREDICTED_STEP_TIME - delta) / (float)PREDICTED_STEP_TIME); } else { oldStep = 0; } step = stepSize + 1; cg.predictedStep = oldStep + step; cg.predictedStepTime = cg.realTime - ( predictiontime - virtualtime ); } /* ================= CG_PredictSmoothSteps ================= */ void CG_PredictSmoothSteps( void ) { int outgoing; int frame; usercmd_t cmd; int i; int virtualtime = 0, predictiontime = 0; cg.predictedStepTime = 0; cg.predictedStep = 0; trap_NET_GetCurrentState( NULL, &outgoing ); i = outgoing; while( predictiontime < PREDICTED_STEP_TIME ) { if( outgoing - i >= CMD_BACKUP ) break; frame = i & CMD_MASK; trap_NET_GetUserCmd( frame, &cmd ); predictiontime += cmd.msec; i--; } // run frames while( ++i <= outgoing ) { frame = i & CMD_MASK; trap_NET_GetUserCmd( frame, &cmd ); virtualtime += cmd.msec; if( predictedSteps[frame] ) { CG_PredictAddStep( virtualtime, predictiontime, predictedSteps[frame] ); } } } /* ================= CG_PredictMovement Sets cg.predictedVelocty, cg.predictedOrigin and cg.predictedAngles ================= */ void CG_PredictMovement( void ) { int ack, outgoing; int frame; usercmd_t cmd; pmove_t pm; int i; if( !cg_predict->integer || cg.demoPlaying || (cg.frame.playerState.pmove.pm_flags & PMF_NO_PREDICTION) ) { // just set angles trap_NET_GetUserCmd( trap_NET_GetCurrentUserCmdNum(), &cmd ); // the command being built but yet unsent VectorScale( cg.frame.playerState.pmove.velocity, (1.0/16.0), cg.predictedVelocity ); VectorScale( cg.frame.playerState.pmove.origin, (1.0/16.0), cg.predictedOrigin ); for( i = 0; i < 3; i++ ) cg.predictedAngles[i] = SHORT2ANGLE( cmd.angles[i] ) + SHORT2ANGLE( cg.frame.playerState.pmove.delta_angles[i] ); return; } trap_NET_GetCurrentState( &ack, &outgoing ); // if we are too far out of date, just freeze if( outgoing - ack >= CMD_BACKUP ) { if( cg_showMiss->integer ) CG_Printf( "exceeded CMD_BACKUP\n" ); return; } // copy current state to pmove memset( &pm, 0, sizeof(pm) ); pm.trace = CG_PMTrace; pm.pointcontents = CG_PointContents; pm.s = cg.frame.playerState.pmove; if( cg.demoPlaying ) pm.s.pm_type = PM_FREEZE; // wsw : jal : set max_walljump count for gametype pm.max_walljumps = GS_GameType_MaxWallJumps( cg.frame.playerState.stats[STAT_GAMETYPE] ); // run frames while( ++ack <= outgoing ) { frame = ack & CMD_MASK; trap_NET_GetUserCmd( frame, &pm.cmd ); Pmove( &pm ); //copy for stair smoothing predictedSteps[frame] = pm.step; // save for debug checking VectorCopy( pm.s.origin, cg.predictedOrigins[frame] ); // jal : does this still have any use? } // copy results out for rendering cg.groundEntity = pm.groundentity; VectorScale( pm.s.velocity, (1.0/16.0), cg.predictedVelocity ); VectorScale( pm.s.origin, (1.0/16.0), cg.predictedOrigin ); VectorCopy( pm.viewangles, cg.predictedAngles ); CG_PredictSmoothSteps(); }