From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/client/prediction.cpp | 3668 ++++++++++++++++++------------------- 1 file changed, 1834 insertions(+), 1834 deletions(-) (limited to 'mp/src/game/client/prediction.cpp') diff --git a/mp/src/game/client/prediction.cpp b/mp/src/game/client/prediction.cpp index 0c61e68a..7e00732c 100644 --- a/mp/src/game/client/prediction.cpp +++ b/mp/src/game/client/prediction.cpp @@ -1,1834 +1,1834 @@ -//========= 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)." ); - -#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 sz[ 32 ]; - Q_snprintf( sz, sizeof( sz ), "postnetworkdata%d", commands_acknowledged ); - PREDICTION_TRACKVALUECHANGESCOPE( sz ); -#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_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; - int step, dir, 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 - { - // 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 <= m_nCommandsPredicted ) - { - // 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 -} +//========= 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)." ); + +#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 sz[ 32 ]; + Q_snprintf( sz, sizeof( sz ), "postnetworkdata%d", commands_acknowledged ); + PREDICTION_TRACKVALUECHANGESCOPE( sz ); +#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_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; + int step, dir, 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 + { + // 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 <= m_nCommandsPredicted ) + { + // 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 +} -- cgit v1.2.3