//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "prediction.h" #include "igamemovement.h" #include "prediction_private.h" #include "ivrenderview.h" #include "iinput.h" #include "usercmd.h" #include #include #include #include "hud.h" #include "iclientvehicle.h" #include "in_buttons.h" #include "con_nprint.h" #include "hud_pdump.h" #include "datacache/imdlcache.h" #ifdef HL2_CLIENT_DLL #include "c_basehlplayer.h" #endif #include "tier0/vprof.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" IPredictionSystem *IPredictionSystem::g_pPredictionSystems = NULL; #if !defined( NO_ENTITY_PREDICTION ) ConVar cl_predictweapons ( "cl_predictweapons","1", FCVAR_USERINFO | FCVAR_NOT_CONNECTED, "Perform client side prediction of weapon effects." ); ConVar cl_lagcompensation ( "cl_lagcompensation","1", FCVAR_USERINFO | FCVAR_NOT_CONNECTED, "Perform server side lag compensation of weapon firing events." ); ConVar cl_showerror ( "cl_showerror", "0", 0, "Show prediction errors, 2 for above plus detailed field deltas." ); static ConVar cl_idealpitchscale ( "cl_idealpitchscale", "0.8", FCVAR_ARCHIVE ); static ConVar cl_predictionlist ( "cl_predictionlist", "0", FCVAR_CHEAT, "Show which entities are predicting\n" ); static ConVar cl_predictionentitydump( "cl_pdump", "-1", FCVAR_CHEAT, "Dump info about this entity to screen." ); static ConVar cl_predictionentitydumpbyclass( "cl_pclass", "", FCVAR_CHEAT, "Dump entity by prediction classname." ); static ConVar cl_pred_optimize( "cl_pred_optimize", "2", 0, "Optimize for not copying data if didn't receive a network update (1), and also for not repredicting if there were no errors (2)." ); #ifdef STAGING_ONLY // Do not ship this - testing a fix static ConVar cl_pred_optimize_prefer_server_data( "cl_pred_optimize_prefer_server_data", "0", 0, "In the case where we have both server data and predicted data up to the same tick, choose server data over predicted data." ); // #endif // STAGING_ONLY #endif extern IGameMovement *g_pGameMovement; extern CMoveData *g_pMoveData; void COM_Log( char *pszFile, const char *fmt, ...); typedescription_t *FindFieldByName( const char *fieldname, datamap_t *dmap ); #if !defined( NO_ENTITY_PREDICTION ) //----------------------------------------------------------------------------- // Purpose: For debugging, find predictable by classname // Input : *classname - // Output : static C_BaseEntity //----------------------------------------------------------------------------- static C_BaseEntity *FindPredictableByGameClass( const char *classname ) { // Walk backward due to deletion from UtlVector int c = predictables->GetPredictableCount(); int i; for ( i = 0; i < c; i++ ) { C_BaseEntity *ent = predictables->GetPredictable( i ); if ( !ent ) continue; // Don't do anything to truly predicted things (like player and weapons ) if ( !FClassnameIs( ent, classname ) ) continue; return ent; } return NULL; } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CPrediction::CPrediction( void ) { #if !defined( NO_ENTITY_PREDICTION ) m_bInPrediction = false; m_bFirstTimePredicted = false; m_nIncomingPacketNumber = 0; m_flIdealPitch = 0.0f; m_nPreviousStartFrame = -1; m_nCommandsPredicted = 0; m_nServerCommandsAcknowledged = 0; m_bPreviousAckHadErrors = false; #endif } CPrediction::~CPrediction( void ) { } void CPrediction::Init( void ) { #if !defined( NO_ENTITY_PREDICTION ) m_bOldCLPredictValue = cl_predict->GetInt(); #endif } void CPrediction::Shutdown( void ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPrediction::CheckError( int commands_acknowledged ) { #if !defined( NO_ENTITY_PREDICTION ) C_BasePlayer *player; Vector origin; Vector delta; float len; static int pos = 0; // Not in the game yet if ( !engine->IsInGame() ) return; // Not running prediction if ( !cl_predict->GetInt() ) return; player = C_BasePlayer::GetLocalPlayer(); if ( !player ) return; // Not predictable yet (flush entity packet?) if ( !player->IsIntermediateDataAllocated() ) return; origin = player->GetNetworkOrigin(); const void *slot = player->GetPredictedFrame( commands_acknowledged - 1 ); if ( !slot ) return; // Find the origin field in the database typedescription_t *td = FindFieldByName( "m_vecNetworkOrigin", player->GetPredDescMap() ); Assert( td ); if ( !td ) return; Vector predicted_origin; memcpy( (Vector *)&predicted_origin, (Vector *)( (byte *)slot + td->fieldOffset[ PC_DATA_PACKED ] ), sizeof( Vector ) ); // Compare what the server returned with what we had predicted it to be VectorSubtract ( predicted_origin, origin, delta ); len = VectorLength( delta ); if (len > MAX_PREDICTION_ERROR ) { // A teleport or something, clear out error len = 0; } else { if ( len > MIN_PREDICTION_EPSILON ) { player->NotePredictionError( delta ); if ( cl_showerror.GetInt() >= 1 ) { con_nprint_t np; np.fixed_width_font = true; np.color[0] = 1.0f; np.color[1] = 0.95f; np.color[2] = 0.7f; np.index = 20 + ( ++pos % 20 ); np.time_to_live = 2.0f; engine->Con_NXPrintf( &np, "pred error %6.3f units (%6.3f %6.3f %6.3f)", len, delta.x, delta.y, delta.z ); } } } #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPrediction::ShutdownPredictables( void ) { #if !defined( NO_ENTITY_PREDICTION ) // Transfer intermediate data from other predictables int c = predictables->GetPredictableCount(); int i; int shutdown_count = 0; int release_count = 0; for ( i = c - 1; i >= 0 ; i-- ) { C_BaseEntity *ent = predictables->GetPredictable( i ); if ( !ent ) continue; // Shutdown predictables if ( ent->GetPredictable() ) { ent->ShutdownPredictable(); shutdown_count++; } // Otherwise, release client created entities else { ent->Release(); release_count++; } } if ( ( release_count > 0 ) || ( shutdown_count > 0 ) ) { Msg( "Shutdown %i predictable entities and %i client-created entities\n", shutdown_count, release_count ); } // All gone now... Assert( predictables->GetPredictableCount() == 0 ); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPrediction::ReinitPredictables( void ) { #if !defined( NO_ENTITY_PREDICTION ) // Go through all entities and init any eligible ones int i; int c = ClientEntityList().GetHighestEntityIndex(); for ( i = 0; i <= c; i++ ) { C_BaseEntity *e = ClientEntityList().GetBaseEntity( i ); if ( !e ) continue; if ( e->GetPredictable() ) continue; e->CheckInitPredictable( "ReinitPredictables" ); } Msg( "Reinitialized %i predictable entities\n", predictables->GetPredictableCount() ); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPrediction::OnReceivedUncompressedPacket( void ) { #if !defined( NO_ENTITY_PREDICTION ) m_nCommandsPredicted = 0; m_nServerCommandsAcknowledged = 0; m_nPreviousStartFrame = -1; #endif } //----------------------------------------------------------------------------- // Purpose: // Input : commands_acknowledged - // current_world_update_packet - // Output : void CPrediction::PreEntityPacketReceived //----------------------------------------------------------------------------- void CPrediction::PreEntityPacketReceived ( int commands_acknowledged, int current_world_update_packet ) { #if !defined( NO_ENTITY_PREDICTION ) #if defined( _DEBUG ) char sz[ 32 ]; Q_snprintf( sz, sizeof( sz ), "preentitypacket%d", commands_acknowledged ); PREDICTION_TRACKVALUECHANGESCOPE( sz ); #endif VPROF( "CPrediction::PreEntityPacketReceived" ); // Cache off incoming packet # m_nIncomingPacketNumber = current_world_update_packet; // Don't screw up memory of current player from history buffers if not filling in history buffers // during prediction!!! if ( !cl_predict->GetInt() ) { ShutdownPredictables(); return; } C_BasePlayer *current = C_BasePlayer::GetLocalPlayer(); // No local player object? if ( !current ) return; // Transfer intermediate data from other predictables int c = predictables->GetPredictableCount(); int i; for ( i = 0; i < c; i++ ) { C_BaseEntity *ent = predictables->GetPredictable( i ); if ( !ent ) continue; if ( !ent->GetPredictable() ) continue; ent->PreEntityPacketReceived( commands_acknowledged ); } #endif } //----------------------------------------------------------------------------- // Purpose: Called for every packet received( could be multiple times per frame) //----------------------------------------------------------------------------- void CPrediction::PostEntityPacketReceived( void ) { #if !defined( NO_ENTITY_PREDICTION ) PREDICTION_TRACKVALUECHANGESCOPE( "postentitypacket" ); VPROF( "CPrediction::PostEntityPacketReceived" ); // Don't screw up memory of current player from history buffers if not filling in history buffers // during prediction!!! if ( !cl_predict->GetInt() ) return; C_BasePlayer *current = C_BasePlayer::GetLocalPlayer(); // No local player object? if ( !current ) return; // Transfer intermediate data from other predictables int c = predictables->GetPredictableCount(); int i; for ( i = 0; i < c; i++ ) { C_BaseEntity *ent = predictables->GetPredictable( i ); if ( !ent ) continue; if ( !ent->GetPredictable() ) continue; ent->PostEntityPacketReceived(); } #endif } //----------------------------------------------------------------------------- // Purpose: // Input : *ent - // Output : static bool //----------------------------------------------------------------------------- bool CPrediction::ShouldDumpEntity( C_BaseEntity *ent ) { #if !defined( NO_ENTITY_PREDICTION ) int dump_entity = cl_predictionentitydump.GetInt(); if ( dump_entity != -1 ) { bool dump = false; if ( ent->entindex() == -1 ) { dump = ( dump_entity == ent->entindex() ) ? true : false; } else { dump = ( ent->entindex() == dump_entity ) ? true : false; } if ( !dump ) { return false; } } else { if ( cl_predictionentitydumpbyclass.GetString()[ 0 ] == 0 ) return false; if ( !FClassnameIs( ent, cl_predictionentitydumpbyclass.GetString() ) ) return false; } return true; #else return false; #endif } //----------------------------------------------------------------------------- // Purpose: Called at the end of the frame if any packets were received // Input : error_check - // last_predicted - //----------------------------------------------------------------------------- void CPrediction::PostNetworkDataReceived( int commands_acknowledged ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::PostNetworkDataReceived" ); bool error_check = ( commands_acknowledged > 0 ) ? true : false; #if defined( _DEBUG ) char szDebug[32]; Q_snprintf( szDebug, sizeof( szDebug ), "postnetworkdata%d", commands_acknowledged ); PREDICTION_TRACKVALUECHANGESCOPE( szDebug ); #endif #ifndef _XBOX CPDumpPanel *dump = GetPDumpPanel(); #endif //Msg( "%i/%i ack %i commands/slot\n", // gpGlobals->framecount, // gpGlobals->tickcount, // commands_acknowledged - 1 ); m_nServerCommandsAcknowledged += commands_acknowledged; m_bPreviousAckHadErrors = false; bool entityDumped = false; C_BasePlayer *current = C_BasePlayer::GetLocalPlayer(); // No local player object? if ( !current ) return; // Don't screw up memory of current player from history buffers if not filling in history buffers // during prediction!!! if ( cl_predict->GetInt() ) { int showlist = cl_predictionlist.GetInt(); int totalsize = 0; int totalsize_intermediate = 0; con_nprint_t np; np.fixed_width_font = true; np.color[0] = 0.8f; np.color[1] = 1.0f; np.color[2] = 1.0f; np.time_to_live = 2.0f; // Transfer intermediate data from other predictables int c = predictables->GetPredictableCount(); int i; for ( i = 0; i < c; i++ ) { C_BaseEntity *ent = predictables->GetPredictable( i ); if ( !ent ) continue; if ( ent->GetPredictable() ) { if ( ent->PostNetworkDataReceived( m_nServerCommandsAcknowledged ) ) { m_bPreviousAckHadErrors = true; } } if ( showlist ) { char sz[32]; if ( ent->entindex() == -1 ) { Q_snprintf( sz, sizeof( sz ), "handle %u", (unsigned int)ent->GetClientHandle().ToInt() ); } else { Q_snprintf( sz, sizeof( sz ), "%i", ent->entindex() ); } np.index = i; if ( showlist >= 2 ) { int size = GetClassMap().GetClassSize( ent->GetClassname() ); int intermediate_size = ent->GetIntermediateDataSize() * ( MULTIPLAYER_BACKUP + 1 ); engine->Con_NXPrintf( &np, "%15s %30s (%5i / %5i bytes): %15s", sz, ent->GetClassname(), size, intermediate_size, ent->GetPredictable() ? "predicted" : "client created" ); totalsize += size; totalsize_intermediate += intermediate_size; } else { engine->Con_NXPrintf( &np, "%15s %30s: %15s", sz, ent->GetClassname(), ent->GetPredictable() ? "predicted" : "client created" ); } } #ifndef _XBOX if ( error_check && !entityDumped && dump && ShouldDumpEntity( ent ) ) { entityDumped = true; dump->DumpEntity( ent, m_nServerCommandsAcknowledged ); } #endif } if ( showlist >= 2 ) { np.index = i++; char sz1[32]; char sz2[32]; Q_strncpy( sz1, Q_pretifymem( (float)totalsize ), sizeof( sz1 ) ); Q_strncpy( sz2, Q_pretifymem( (float)totalsize_intermediate ), sizeof( sz2 ) ); engine->Con_NXPrintf( &np, "%15s %27s (%s / %s) %14s", "totals:", "", sz1, sz2, "" ); } // Zero out rest of list if ( showlist ) { while ( i < 20 ) { engine->Con_NPrintf( i, "" ); i++; } } if ( error_check ) { CheckError( m_nServerCommandsAcknowledged ); } } // Can also look at regular entities #ifndef _XBOX int dumpentindex = cl_predictionentitydump.GetInt(); if ( dump && error_check && !entityDumped && dumpentindex != -1 ) { int last_entity = ClientEntityList().GetHighestEntityIndex(); if ( dumpentindex >= 0 && dumpentindex <= last_entity ) { C_BaseEntity *ent = ClientEntityList().GetBaseEntity( dumpentindex ); if ( ent ) { dump->DumpEntity( ent, m_nServerCommandsAcknowledged ); entityDumped = true; } } } #endif if ( cl_predict->GetBool() != m_bOldCLPredictValue ) { if ( !m_bOldCLPredictValue ) { ReinitPredictables(); } m_nCommandsPredicted = 0; m_nServerCommandsAcknowledged = 0; m_nPreviousStartFrame = -1; } m_bOldCLPredictValue = cl_predict->GetInt(); #ifndef _XBOX if ( dump && error_check && !entityDumped ) { dump->Clear(); } #endif #endif } //----------------------------------------------------------------------------- // Purpose: Prepare for running prediction code // Input : *ucmd - // *from - // *pHelper - // &moveInput - //----------------------------------------------------------------------------- void CPrediction::SetupMove( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::SetupMove" ); move->m_bFirstRunOfFunctions = IsFirstTimePredicted(); move->m_nPlayerHandle = player->GetClientHandle(); move->m_vecVelocity = player->GetAbsVelocity(); move->SetAbsOrigin( player->GetNetworkOrigin() ); move->m_vecOldAngles = move->m_vecAngles; move->m_nOldButtons = player->m_Local.m_nOldButtons; move->m_flOldForwardMove = player->m_Local.m_flOldForwardMove; move->m_flClientMaxSpeed = player->m_flMaxspeed; move->m_vecAngles = ucmd->viewangles; move->m_vecViewAngles = ucmd->viewangles; move->m_nImpulseCommand = ucmd->impulse; move->m_nButtons = ucmd->buttons; CBaseEntity *pMoveParent = player->GetMoveParent(); if (!pMoveParent) { move->m_vecAbsViewAngles = move->m_vecViewAngles; } else { matrix3x4_t viewToParent, viewToWorld; AngleMatrix( move->m_vecViewAngles, viewToParent ); ConcatTransforms( pMoveParent->EntityToWorldTransform(), viewToParent, viewToWorld ); MatrixAngles( viewToWorld, move->m_vecAbsViewAngles ); } // Ingore buttons for movement if at controls if (player->GetFlags() & FL_ATCONTROLS) { move->m_flForwardMove = 0; move->m_flSideMove = 0; move->m_flUpMove = 0; } else { move->m_flForwardMove = ucmd->forwardmove; move->m_flSideMove = ucmd->sidemove; move->m_flUpMove = ucmd->upmove; } IClientVehicle *pVehicle = player->GetVehicle(); if (pVehicle) { pVehicle->SetupMove( player, ucmd, pHelper, move ); } // Copy constraint information if ( player->m_hConstraintEntity ) move->m_vecConstraintCenter = player->m_hConstraintEntity->GetAbsOrigin(); else move->m_vecConstraintCenter = player->m_vecConstraintCenter; move->m_flConstraintRadius = player->m_flConstraintRadius; move->m_flConstraintWidth = player->m_flConstraintWidth; move->m_flConstraintSpeedFactor = player->m_flConstraintSpeedFactor; #ifdef HL2_CLIENT_DLL // Convert to HL2 data. C_BaseHLPlayer *pHLPlayer = static_cast( player ); Assert( pHLPlayer ); CHLMoveData *pHLMove = static_cast( move ); Assert( pHLMove ); pHLMove->m_bIsSprinting = pHLPlayer->IsSprinting(); #endif #endif } //----------------------------------------------------------------------------- // Purpose: Finish running prediction code // Input : &move - // *to - //----------------------------------------------------------------------------- void CPrediction::FinishMove( C_BasePlayer *player, CUserCmd *ucmd, CMoveData *move ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::FinishMove" ); player->m_RefEHandle = move->m_nPlayerHandle; player->m_vecVelocity = move->m_vecVelocity; player->m_vecNetworkOrigin = move->GetAbsOrigin(); player->m_Local.m_nOldButtons = move->m_nButtons; // NOTE: Don't copy this. the movement code modifies its local copy but is not expecting to be authoritative //player->m_flMaxspeed = move->m_flClientMaxSpeed; m_hLastGround = player->GetGroundEntity(); player->SetLocalOrigin( move->GetAbsOrigin() ); IClientVehicle *pVehicle = player->GetVehicle(); if (pVehicle) { pVehicle->FinishMove( player, ucmd, move ); } // Sanity checks if ( player->m_hConstraintEntity ) Assert( move->m_vecConstraintCenter == player->m_hConstraintEntity->GetAbsOrigin() ); else Assert( move->m_vecConstraintCenter == player->m_vecConstraintCenter ); Assert( move->m_flConstraintRadius == player->m_flConstraintRadius ); Assert( move->m_flConstraintWidth == player->m_flConstraintWidth ); Assert( move->m_flConstraintSpeedFactor == player->m_flConstraintSpeedFactor ); #endif } //----------------------------------------------------------------------------- // Purpose: Called before any movement processing // Input : *player - // *cmd - //----------------------------------------------------------------------------- void CPrediction::StartCommand( C_BasePlayer *player, CUserCmd *cmd ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::StartCommand" ); CPredictableId::ResetInstanceCounters(); player->m_pCurrentCommand = cmd; C_BaseEntity::SetPredictionRandomSeed( cmd ); C_BaseEntity::SetPredictionPlayer( player ); #endif } //----------------------------------------------------------------------------- // Purpose: Called after any movement processing // Input : *player - //----------------------------------------------------------------------------- void CPrediction::FinishCommand( C_BasePlayer *player ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::FinishCommand" ); player->m_pCurrentCommand = NULL; C_BaseEntity::SetPredictionRandomSeed( NULL ); C_BaseEntity::SetPredictionPlayer( NULL ); #endif } //----------------------------------------------------------------------------- // Purpose: Called before player thinks // Input : *player - // thinktime - //----------------------------------------------------------------------------- void CPrediction::RunPreThink( C_BasePlayer *player ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::RunPreThink" ); // Run think functions on the player if ( !player->PhysicsRunThink() ) return; // Called every frame to let game rules do any specific think logic for the player // FIXME: Do we need to set up a client side version of the gamerules??? // g_pGameRules->PlayerThink( player ); player->PreThink(); #endif } //----------------------------------------------------------------------------- // Purpose: Runs the PLAYER's thinking code if time. There is some play in the exact time the think // function will be called, because it is called before any movement is done // in a frame. Not used for pushmove objects, because they must be exact. // Returns false if the entity removed itself. // Input : *ent - // frametime - // clienttimebase - // Output : void CPlayerMove::RunThink //----------------------------------------------------------------------------- void CPrediction::RunThink (C_BasePlayer *player, double frametime ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::RunThink" ); int thinktick = player->GetNextThinkTick(); if ( thinktick <= 0 || thinktick > player->m_nTickBase ) return; player->SetNextThink( TICK_NEVER_THINK ); // Think player->Think(); #endif } //----------------------------------------------------------------------------- // Purpose: Called after player movement // Input : *player - // thinktime - // frametime - //----------------------------------------------------------------------------- void CPrediction::RunPostThink( C_BasePlayer *player ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::RunPostThink" ); // Run post-think player->PostThink(); #endif } //----------------------------------------------------------------------------- // Purpose: Predicts a single movement command for player // Input : *moveHelper - // *player - // *u - //----------------------------------------------------------------------------- void CPrediction::RunCommand( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper *moveHelper ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::RunCommand" ); #if defined( _DEBUG ) char sz[ 32 ]; Q_snprintf( sz, sizeof( sz ), "runcommand%04d", ucmd->command_number ); PREDICTION_TRACKVALUECHANGESCOPE( sz ); #endif StartCommand( player, ucmd ); // Set globals appropriately gpGlobals->curtime = player->m_nTickBase * TICK_INTERVAL; gpGlobals->frametime = m_bEnginePaused ? 0 : TICK_INTERVAL; g_pGameMovement->StartTrackPredictionErrors( player ); // TODO // TODO: Check for impulse predicted? // Do weapon selection if ( ucmd->weaponselect != 0 ) { C_BaseCombatWeapon *weapon = dynamic_cast< C_BaseCombatWeapon * >( CBaseEntity::Instance( ucmd->weaponselect ) ); if ( weapon ) { player->SelectItem( weapon->GetName(), ucmd->weaponsubtype ); } } // Latch in impulse. IClientVehicle *pVehicle = player->GetVehicle(); if ( ucmd->impulse ) { // Discard impulse commands unless the vehicle allows them. // FIXME: UsingStandardWeapons seems like a bad filter for this. // The flashlight is an impulse command, for example. if ( !pVehicle || player->UsingStandardWeaponsInVehicle() ) { player->m_nImpulse = ucmd->impulse; } } // Get button states player->UpdateButtonState( ucmd->buttons ); // TODO // CheckMovingGround( player, ucmd->frametime ); // TODO // g_pMoveData->m_vecOldAngles = player->pl.v_angle; // Copy from command to player unless game .dll has set angle using fixangle // if ( !player->pl.fixangle ) { player->SetLocalViewAngles( ucmd->viewangles ); } // Call standard client pre-think RunPreThink( player ); // Call Think if one is set RunThink( player, TICK_INTERVAL ); // Setup input. { SetupMove( player, ucmd, moveHelper, g_pMoveData ); } // RUN MOVEMENT if ( !pVehicle ) { Assert( g_pGameMovement ); g_pGameMovement->ProcessMovement( player, g_pMoveData ); } else { pVehicle->ProcessMovement( player, g_pMoveData ); } FinishMove( player, ucmd, g_pMoveData ); RunPostThink( player ); g_pGameMovement->FinishTrackPredictionErrors( player ); FinishCommand( player ); if ( gpGlobals->frametime > 0 ) { player->m_nTickBase++; } #endif } //----------------------------------------------------------------------------- // Purpose: In the forward direction, creates rays straight down and determines the // height of the 'floor' hit for each forward test. Then, if the samples show that the // player is about to enter an up/down slope, sets *idealpitch to look up or down that slope // as appropriate //----------------------------------------------------------------------------- void CPrediction::SetIdealPitch ( C_BasePlayer *player, const Vector& origin, const QAngle& angles, const Vector& viewheight ) { #if !defined( NO_ENTITY_PREDICTION ) Vector forward; Vector top, bottom; float floor_height[MAX_FORWARD]; int i, j; float step, dir; int steps; trace_t tr; if ( player->GetGroundEntity() == NULL ) return; // Don't do this on the 360.. if ( IsX360() ) return; AngleVectors( angles, &forward ); forward[2] = 0; // Now move forward by 36, 48, 60, etc. units from the eye position and drop lines straight down // 160 or so units to see what's below for (i=0 ; i -ON_EPSILON && step < ON_EPSILON) continue; if (dir && ( step-dir > ON_EPSILON || step-dir < -ON_EPSILON ) ) return; // mixed changes steps++; dir = step; } if (!dir) { m_flIdealPitch = 0; return; } if (steps < 2) return; m_flIdealPitch = -dir * cl_idealpitchscale.GetFloat(); #endif } //----------------------------------------------------------------------------- // Purpose: Walk backward through predictables looking for ClientCreated entities // such as projectiles which were // 1) not actually ack'd by the server or // 2) were ack'd and made dormant and can now safely be removed // Input : last_command_packet - //----------------------------------------------------------------------------- void CPrediction::RemoveStalePredictedEntities( int sequence_number ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::RemoveStalePredictedEntities" ); int oldest_allowable_command = sequence_number; // Walk backward due to deletion from UtlVector int c = predictables->GetPredictableCount(); int i; for ( i = c - 1; i >= 0; i-- ) { C_BaseEntity *ent = predictables->GetPredictable( i ); if ( !ent ) continue; // Don't do anything to truly predicted things (like player and weapons ) if ( ent->GetPredictable() ) continue; // What's left should be things like projectiles that are just waiting to be "linked" // to their server counterpart and deleted Assert( ent->IsClientCreated() ); if ( !ent->IsClientCreated() ) continue; // Snag the PredictionContext PredictionContext *ctx = ent->m_pPredictionContext; if ( !ctx ) { continue; } // If it was ack'd then the server sent us the entity. // Leave it unless it wasn't made dormant this frame, in // which case it can be removed now if ( ent->m_PredictableID.GetAcknowledged() ) { // Hasn't become dormant yet!!! if ( !ent->IsDormantPredictable() ) { Assert( 0 ); continue; } // Still gets to live till next frame if ( ent->BecameDormantThisPacket() ) continue; C_BaseEntity *serverEntity = ctx->m_hServerEntity; if ( serverEntity ) { // Notify that it's going to go away serverEntity->OnPredictedEntityRemove( true, ent ); } } else { // Check context to see if it's too old? int command_entity_creation_happened = ctx->m_nCreationCommandNumber; // Give it more time to live...not time to kill it yet if ( command_entity_creation_happened > oldest_allowable_command ) continue; // If the client predicted the KILLME flag it's possible // that entity had such a short life that it actually // never was sent to us. In that case, just let it die a silent death if ( !ent->IsEFlagSet( EFL_KILLME ) ) { if ( cl_showerror.GetInt() != 0 ) { // It's bogus, server doesn't have a match, destroy it: Msg( "Removing unack'ed predicted entity: %s created %s(%i) id == %s : %p\n", ent->GetClassname(), ctx->m_pszCreationModule, ctx->m_nCreationLineNumber, ent->m_PredictableID.Describe(), ent ); } } // FIXME: Do we need an OnPredictedEntityRemove call with an "it's not valid" // flag of some kind } // This will remove it from predictables list and will also free the entity, etc. ent->Release(); } #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPrediction::RestoreOriginalEntityState( void ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::RestoreOriginalEntityState" ); PREDICTION_TRACKVALUECHANGESCOPE( "restore" ); Assert( C_BaseEntity::IsAbsRecomputationsEnabled() ); // Transfer intermediate data from other predictables int pc = predictables->GetPredictableCount(); int p; for ( p = 0; p < pc; p++ ) { C_BaseEntity *ent = predictables->GetPredictable( p ); if ( !ent ) continue; if ( ent->GetPredictable() ) { ent->RestoreData( "RestoreOriginalEntityState", C_BaseEntity::SLOT_ORIGINALDATA, PC_EVERYTHING ); } } #endif } //----------------------------------------------------------------------------- // Purpose: // Input : current_command - // curtime - // *cmd - // *tcmd - // *localPlayer - //----------------------------------------------------------------------------- void CPrediction::RunSimulation( int current_command, float curtime, CUserCmd *cmd, C_BasePlayer *localPlayer ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::RunSimulation" ); Assert( localPlayer ); C_CommandContext *ctx = localPlayer->GetCommandContext(); Assert( ctx ); ctx->needsprocessing = true; ctx->cmd = *cmd; ctx->command_number = current_command; IPredictionSystem::SuppressEvents( !IsFirstTimePredicted() ); int i; // Make sure simulation occurs at most once per entity per usercmd for ( i = 0; i < predictables->GetPredictableCount(); i++ ) { C_BaseEntity *entity = predictables->GetPredictable( i ); if ( entity ) { entity->m_nSimulationTick = -1; } } // Don't used cached numpredictables since entities can be created mid-prediction by the player for ( i = 0; i < predictables->GetPredictableCount(); i++ ) { // Always reset gpGlobals->curtime = curtime; gpGlobals->frametime = m_bEnginePaused ? 0 : TICK_INTERVAL; C_BaseEntity *entity = predictables->GetPredictable( i ); if ( !entity ) continue; bool islocal = ( localPlayer == entity ) ? true : false; // Local player simulates first, if this assert fires then the predictables list isn't sorted // correctly (or we started predicting C_World???) if ( islocal ) { Assert( i == 0 ); } // Player can't be this so cull other entities here if ( entity->GetFlags() & FL_STATICPROP ) { continue; } // Player is not actually in the m_SimulatedByThisPlayer list, of course if ( entity->IsPlayerSimulated() ) { continue; } if ( AddDataChangeEvent( entity, DATA_UPDATE_DATATABLE_CHANGED, &entity->m_DataChangeEventRef ) ) { entity->OnPreDataChanged( DATA_UPDATE_DATATABLE_CHANGED ); } // Certain entities can be created locally and if so created, should be // simulated until a network update arrives if ( entity->IsClientCreated() ) { // Only simulate these on new usercmds if ( !IsFirstTimePredicted() ) continue; entity->PhysicsSimulate(); } else { entity->PhysicsSimulate(); } // Don't update last networked data here!!! entity->OnLatchInterpolatedVariables( LATCH_SIMULATION_VAR | LATCH_ANIMATION_VAR | INTERPOLATE_OMIT_UPDATE_LAST_NETWORKED ); } // Always reset after running command IPredictionSystem::SuppressEvents( false ); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CPrediction::Untouch( void ) { #if !defined( NO_ENTITY_PREDICTION ) int numpredictables = predictables->GetPredictableCount(); // Loop through all entities again, checking their untouch if flagged to do so int i; for ( i = 0; i < numpredictables; i++ ) { C_BaseEntity *entity = predictables->GetPredictable( i ); if ( !entity ) continue; if ( !entity->GetCheckUntouch() ) continue; entity->PhysicsCheckForEntityUntouch(); } #endif } #if !defined( NO_ENTITY_PREDICTION ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void InvalidateEFlagsRecursive( C_BaseEntity *pEnt, int nDirtyFlags, int nChildFlags = 0 ) { pEnt->AddEFlags( nDirtyFlags ); nDirtyFlags |= nChildFlags; for (CBaseEntity *pChild = pEnt->FirstMoveChild(); pChild; pChild = pChild->NextMovePeer()) { InvalidateEFlagsRecursive( pChild, nDirtyFlags ); } } #endif void CPrediction::StorePredictionResults( int predicted_frame ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::StorePredictionResults" ); PREDICTION_TRACKVALUECHANGESCOPE( "save" ); int i; int numpredictables = predictables->GetPredictableCount(); // Now save off all of the results for ( i = 0; i < numpredictables; i++ ) { C_BaseEntity *entity = predictables->GetPredictable( i ); if ( !entity ) continue; // Certain entities can be created locally and if so created, should be // simulated until a network update arrives if ( !entity->GetPredictable() ) continue; // FIXME: The lack of this call inexplicably actually creates prediction errors InvalidateEFlagsRecursive( entity, EFL_DIRTY_ABSTRANSFORM | EFL_DIRTY_ABSVELOCITY | EFL_DIRTY_ABSANGVELOCITY ); entity->SaveData( "StorePredictionResults", predicted_frame, PC_EVERYTHING ); } #endif } //----------------------------------------------------------------------------- // Purpose: // Input : slots_to_remove - // previous_last_slot - //----------------------------------------------------------------------------- void CPrediction::ShiftIntermediateDataForward( int slots_to_remove, int number_of_commands_run ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::ShiftIntermediateDataForward" ); PREDICTION_TRACKVALUECHANGESCOPE( "shift" ); C_BasePlayer *current = C_BasePlayer::GetLocalPlayer(); // No local player object? if ( !current ) return; // Don't screw up memory of current player from history buffers if not filling in history buffers // during prediction!!! if ( !cl_predict->GetInt() ) return; int c = predictables->GetPredictableCount(); int i; for ( i = 0; i < c; i++ ) { C_BaseEntity *ent = predictables->GetPredictable( i ); if ( !ent ) continue; if ( !ent->GetPredictable() ) continue; ent->ShiftIntermediateDataForward( slots_to_remove, number_of_commands_run ); } #endif } //----------------------------------------------------------------------------- // Purpose: // Input : predicted_frame - //----------------------------------------------------------------------------- void CPrediction::RestoreEntityToPredictedFrame( int predicted_frame ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::RestoreEntityToPredictedFrame" ); PREDICTION_TRACKVALUECHANGESCOPE( "restoretopred" ); C_BasePlayer *current = C_BasePlayer::GetLocalPlayer(); // No local player object? if ( !current ) return; // Don't screw up memory of current player from history buffers if not filling in history buffers // during prediction!!! if ( !cl_predict->GetInt() ) return; int c = predictables->GetPredictableCount(); int i; for ( i = 0; i < c; i++ ) { C_BaseEntity *ent = predictables->GetPredictable( i ); if ( !ent ) continue; if ( !ent->GetPredictable() ) continue; ent->RestoreData( "RestoreEntityToPredictedFrame", predicted_frame, PC_EVERYTHING ); } #endif } //----------------------------------------------------------------------------- // Purpose: Computes starting destination for intermediate prediction data results and // does any fixups required by network optimization // Input : received_new_world_update - // incoming_acknowledged - // Output : int //----------------------------------------------------------------------------- int CPrediction::ComputeFirstCommandToExecute( bool received_new_world_update, int incoming_acknowledged, int outgoing_command ) { int destination_slot = 1; #if !defined( NO_ENTITY_PREDICTION ) int skipahead = 0; // If we didn't receive a new update ( or we received an update that didn't ack any new CUserCmds -- // so for the player it should be just like receiving no update ), just jump right up to the very // last command we created for this very frame since we probably wouldn't have had any errors without // being notified by the server of such a case. // NOTE: received_new_world_update only gets set to false if cl_pred_optimize >= 1 if ( !received_new_world_update || !m_nServerCommandsAcknowledged ) { // this is where we would normally start int start = incoming_acknowledged + 1; // outgoing_command is where we really want to start skipahead = MAX( 0, ( outgoing_command - start ) ); // Don't start past the last predicted command, though, or we'll get prediction errors skipahead = MIN( skipahead, m_nCommandsPredicted ); // Always restore since otherwise we might start prediction using an "interpolated" value instead of a purely predicted value RestoreEntityToPredictedFrame( skipahead - 1 ); //Msg( "%i/%i no world, skip to %i restore from slot %i\n", // gpGlobals->framecount, // gpGlobals->tickcount, // skipahead, // skipahead - 1 ); } else { #ifdef STAGING_ONLY int nPredictedLimit = cl_pred_optimize_prefer_server_data.GetBool() ? m_nCommandsPredicted - 1 : m_nCommandsPredicted; #else int nPredictedLimit = m_nCommandsPredicted; #endif // STAGING_ONLY // Otherwise, there is a second optimization, wherein if we did receive an update, but no // values differed (or were outside their epsilon) and the server actually acknowledged running // one or more commands, then we can revert the entity to the predicted state from last frame, // shift the # of commands worth of intermediate state off of front the intermediate state array, and // only predict the usercmd from the latest render frame. if ( cl_pred_optimize.GetInt() >= 2 && !m_bPreviousAckHadErrors && m_nCommandsPredicted > 0 && m_nServerCommandsAcknowledged <= nPredictedLimit ) { // Copy all of the previously predicted data back into entity so we can skip repredicting it // This is the final slot that we previously predicted RestoreEntityToPredictedFrame( m_nCommandsPredicted - 1 ); // Shift intermediate state blocks down by # of commands ack'd ShiftIntermediateDataForward( m_nServerCommandsAcknowledged, m_nCommandsPredicted ); // Only predict new commands (note, this should be the same number that we could compute // above based on outgoing_command - incoming_acknowledged - 1 skipahead = ( m_nCommandsPredicted - m_nServerCommandsAcknowledged ); //Msg( "%i/%i optimize2, skip to %i restore from slot %i\n", // gpGlobals->framecount, // gpGlobals->tickcount, // skipahead, // m_nCommandsPredicted - 1 ); } else { if ( m_bPreviousAckHadErrors ) { C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); // If an entity gets a prediction error, then we want to clear out its interpolated variables // so we don't mix different samples at the same timestamps. We subtract 1 tick interval here because // if we don't, we'll have 3 interpolation entries with the same timestamp as this predicted // frame, so we won't be able to interpolate (which leads to jerky movement in the player when // ANY entity like your gun gets a prediction error). float flPrev = gpGlobals->curtime; gpGlobals->curtime = pLocalPlayer->GetTimeBase() - TICK_INTERVAL; for ( int i = 0; i < predictables->GetPredictableCount(); i++ ) { C_BaseEntity *entity = predictables->GetPredictable( i ); if ( entity ) { entity->ResetLatched(); } } gpGlobals->curtime = flPrev; } } } destination_slot += skipahead; // Always reset these values now that we handled them m_nCommandsPredicted = 0; m_bPreviousAckHadErrors = false; m_nServerCommandsAcknowledged = 0; #endif return destination_slot; } //----------------------------------------------------------------------------- // Actually does the prediction work, returns false if an error occurred //----------------------------------------------------------------------------- bool CPrediction::PerformPrediction( bool received_new_world_update, C_BasePlayer *localPlayer, int incoming_acknowledged, int outgoing_command ) { MDLCACHE_CRITICAL_SECTION(); #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::PerformPrediction" ); // This makes sure , tahe we are allwoed to sample the world when it may not be ready to be sampled Assert( C_BaseEntity::IsAbsQueriesValid() ); Assert( C_BaseEntity::IsAbsRecomputationsEnabled() ); m_bInPrediction = true; // undo interpolation changes for entities we stand on C_BaseEntity *entity = localPlayer->GetGroundEntity(); while ( entity && entity->entindex() > 0) { entity->MoveToLastReceivedPosition(); // undo changes for moveparents too entity = entity->GetMoveParent(); } // Start at command after last one server has processed and // go until we get to targettime or we run out of new commands int i = ComputeFirstCommandToExecute( received_new_world_update, incoming_acknowledged, outgoing_command ); //Msg( "%i/%i tickbase %i\n", // gpGlobals->framecount, // gpGlobals->tickcount, // localPlayer->m_nTickBase ); //for ( int k = 1; k < i; k++ ) //{ // Msg( "%i/%i Skip final tick %i into slot %i\n", // gpGlobals->framecount, gpGlobals->tickcount, // localPlayer->m_nTickBase - i + k + 1, // k - 1 ); //} Assert( i >= 1 ); while ( true ) { // Incoming_acknowledged is the last usercmd the server acknowledged having acted upon int current_command = incoming_acknowledged + i; // We've caught up to the current command. if ( current_command > outgoing_command ) break; if ( i >= MULTIPLAYER_BACKUP ) break; CUserCmd *cmd = input->GetUserCmd( current_command ); if ( !cmd ) { break; } // Is this the first time predicting this m_bFirstTimePredicted = !cmd->hasbeenpredicted; // Set globals appropriately float curtime = ( localPlayer->m_nTickBase ) * TICK_INTERVAL; RunSimulation( current_command, curtime, cmd, localPlayer ); gpGlobals->curtime = curtime; gpGlobals->frametime = m_bEnginePaused ? 0 : TICK_INTERVAL; // Call untouch on any entities no longer predicted to be touching Untouch(); // Store intermediate data into appropriate slot StorePredictionResults( i - 1 ); // Note that I starts at 1 m_nCommandsPredicted = i; if ( current_command == outgoing_command ) { localPlayer->m_nFinalPredictedTick = localPlayer->m_nTickBase; } /* if ( 0 ) { localPlayer->m_nFinalPredictedTick = localPlayer->m_nTickBase; Msg( "%i/%i Latch final tick %i start == %i into slot %i\n", gpGlobals->framecount, gpGlobals->tickcount, localPlayer->m_nFinalPredictedTick, localPlayer->m_nFinalPredictedTick - i, i - 1 ); } */ /* Msg( "%i/%i Predicted command %i tickbase == %i first %s\n", gpGlobals->framecount, gpGlobals->tickcount, m_nCommandsPredicted, localPlayer->m_nTickBase, m_bFirstTimePredicted ? "yes" : "no" ); */ // Mark that we issued any needed sounds, of not done already cmd->hasbeenpredicted = true; // Copy the state over. i++; } // Msg( "%i : predicted %i commands forward, %i ack'd last frame, had errors %s\n", // gpGlobals->tickcount, // m_nCommandsPredicted, // m_nServerCommandsAcknowledged, // m_bPreviousAckHadErrors ? "true" : "false" ); m_bInPrediction = false; // Somehow we looped past the end of the list (severe lag), don't predict at all if ( i > MULTIPLAYER_BACKUP ) { return false; } #endif return true; } //----------------------------------------------------------------------------- // Purpose: // Input : startframe - // validframe - // incoming_acknowledged - // outgoing_command - //----------------------------------------------------------------------------- void CPrediction::Update( int startframe, bool validframe, int incoming_acknowledged, int outgoing_command ) { #if !defined( NO_ENTITY_PREDICTION ) VPROF_BUDGET( "CPrediction::Update", VPROF_BUDGETGROUP_PREDICTION ); m_bEnginePaused = engine->IsPaused(); bool received_new_world_update = true; // Still starting at same frame, so make sure we don't do extra prediction ,etc. if ( ( m_nPreviousStartFrame == startframe ) && cl_pred_optimize.GetBool() && cl_predict->GetInt() ) { received_new_world_update = false; } m_nPreviousStartFrame = startframe; // Save off current timer values, etc. CGlobalVarsBase saveVars(true); saveVars = *gpGlobals; _Update( received_new_world_update, validframe, incoming_acknowledged, outgoing_command ); // Restore current timer values, etc. *gpGlobals = saveVars; #endif } //----------------------------------------------------------------------------- // Do the dirty deed of predicting the local player //----------------------------------------------------------------------------- void CPrediction::_Update( bool received_new_world_update, bool validframe, int incoming_acknowledged, int outgoing_command ) { #if !defined( NO_ENTITY_PREDICTION ) C_BasePlayer *localPlayer = C_BasePlayer::GetLocalPlayer(); if ( !localPlayer ) return; // Always using current view angles no matter what // NOTE: ViewAngles are always interpreted as being *relative* to the player QAngle viewangles; engine->GetViewAngles( viewangles ); localPlayer->SetLocalAngles( viewangles ); if ( !validframe ) { return; } // If we are not doing prediction, copy authoritative value into velocity and angle. if ( !cl_predict->GetInt() ) { // When not predicting, we at least must make sure the player // view angles match the view angles... localPlayer->SetLocalViewAngles( viewangles ); return; } // This is cheesy, but if we have entities that are parented to attachments on other entities, then // it'll wind up needing to get a bone transform. { C_BaseAnimating::InvalidateBoneCaches(); C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, true ); // Remove any purely client predicted entities that were left "dangling" because the // server didn't acknowledge them or which can now safely be removed RemoveStalePredictedEntities( incoming_acknowledged ); // Restore objects back to "pristine" state from last network/world state update if ( received_new_world_update ) { RestoreOriginalEntityState(); } if ( !PerformPrediction( received_new_world_update, localPlayer, incoming_acknowledged, outgoing_command ) ) return; } // Overwrite predicted angles with the actual view angles localPlayer->SetLocalAngles( viewangles ); // This allows us to sample the world when it may not be ready to be sampled Assert( C_BaseEntity::IsAbsQueriesValid() ); // FIXME: What about hierarchy here?!? SetIdealPitch( localPlayer, localPlayer->GetLocalOrigin(), localPlayer->GetLocalAngles(), localPlayer->m_vecViewOffset ); #endif } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPrediction::IsFirstTimePredicted( void ) const { #if !defined( NO_ENTITY_PREDICTION ) return m_bFirstTimePredicted; #else return false; #endif } //----------------------------------------------------------------------------- // Purpose: // Input : org - //----------------------------------------------------------------------------- void CPrediction::GetViewOrigin( Vector& org ) { C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); if ( !player ) { org.Init(); } else { org = player->GetLocalOrigin(); } } //----------------------------------------------------------------------------- // Purpose: // Input : org - //----------------------------------------------------------------------------- void CPrediction::SetViewOrigin( Vector& org ) { C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); if ( !player ) return; player->SetLocalOrigin( org ); player->m_vecNetworkOrigin = org; player->m_iv_vecOrigin.Reset(); } //----------------------------------------------------------------------------- // Purpose: // Input : ang - //----------------------------------------------------------------------------- void CPrediction::GetViewAngles( QAngle& ang ) { C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); if ( !player ) { ang.Init(); } else { ang = player->GetLocalAngles(); } } //----------------------------------------------------------------------------- // Purpose: // Input : ang - //----------------------------------------------------------------------------- void CPrediction::SetViewAngles( QAngle& ang ) { C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); if ( !player ) return; player->SetViewAngles( ang ); player->m_iv_angRotation.Reset(); } //----------------------------------------------------------------------------- // Purpose: // Input : ang - //----------------------------------------------------------------------------- void CPrediction::GetLocalViewAngles( QAngle& ang ) { C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); if ( !player ) { ang.Init(); } else { ang = player->pl.v_angle; } } //----------------------------------------------------------------------------- // Purpose: // Input : ang - //----------------------------------------------------------------------------- void CPrediction::SetLocalViewAngles( QAngle& ang ) { C_BasePlayer *player = C_BasePlayer::GetLocalPlayer(); if ( !player ) return; player->SetLocalViewAngles( ang ); } #if !defined( NO_ENTITY_PREDICTION ) //----------------------------------------------------------------------------- // Purpose: For determining that predicted creation entities are un-acked and should // be deleted // Output : int //----------------------------------------------------------------------------- int CPrediction::GetIncomingPacketNumber( void ) const { return m_nIncomingPacketNumber; } #endif //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CPrediction::InPrediction( void ) const { #if !defined( NO_ENTITY_PREDICTION ) return m_bInPrediction; #else return false; #endif }