diff --git a/src/game/client/c_baseplayer.cpp b/src/game/client/c_baseplayer.cpp index 534d4a2c876..c75aade68a9 100644 --- a/src/game/client/c_baseplayer.cpp +++ b/src/game/client/c_baseplayer.cpp @@ -244,6 +244,7 @@ END_RECV_TABLE() RecvPropFloat ( RECVINFO(m_vecVelocity[2]), 0, RecvProxy_LocalVelocityZ ), RecvPropVector ( RECVINFO( m_vecBaseVelocity ) ), + RecvPropVector(RECVINFO(m_vecPreviouslyPreviouslyPredictedEyePosition)), RecvPropEHandle ( RECVINFO( m_hConstraintEntity)), RecvPropVector ( RECVINFO( m_vecConstraintCenter) ), @@ -399,6 +400,7 @@ BEGIN_PREDICTION_DATA( C_BasePlayer ) // DEFINE_FIELD( m_pEnvironmentLight, dlight_t* ), // DEFINE_FIELD( m_pBrightLight, dlight_t* ), DEFINE_PRED_FIELD( m_hLastWeapon, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD(m_vecPreviouslyPreviouslyPredictedEyePosition, FIELD_POSITION_VECTOR, FTYPEDESC_INSENDTABLE), DEFINE_PRED_FIELD( m_nTickBase, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), diff --git a/src/game/client/c_baseplayer.h b/src/game/client/c_baseplayer.h index 11a7fabca88..6bb0715ebd1 100644 --- a/src/game/client/c_baseplayer.h +++ b/src/game/client/c_baseplayer.h @@ -334,7 +334,10 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener float GetDeathTime( void ) { return m_flDeathTime; } void SetPreviouslyPredictedOrigin( const Vector &vecAbsOrigin ); + void SetPreviouslyPreviouslyPredictedEyePosition(const Vector& vecAbsOrigin); + const Vector &GetPreviouslyPredictedOrigin() const; + const Vector& GetPreviouslyPreviouslyPredictedEyePosition() const; // CS wants to allow small FOVs for zoomed-in AWPs. virtual float GetMinFOV() const; @@ -399,7 +402,13 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener void SetFiredWeapon( bool bFlag ) { m_bFiredWeapon = bFlag; } virtual bool CanUseFirstPersonCommand( void ){ return true; } - + void SetAttackInterpolationData(const QAngle& viewAngles, float interpolationAmount); + void GetAttackInterpolationData(QAngle& viewAngles, float& lerpTime); + bool HasAttackInterpolationData() const; + void ClearAttackInterpolationData(); + void SetInPostThink(bool inPostThink); + bool IsInPostThink() const; + Vector GetInterpolatedEyePosition(); protected: fogparams_t m_CurrentFog; EHANDLE m_hOldFogController; @@ -600,7 +609,8 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener float m_flPredictionErrorTime; Vector m_vecPreviouslyPredictedOrigin; // Used to determine if non-gamemovement game code has teleported, or tweaked the player's origin - + Vector m_vecPreviouslyPreviouslyPredictedEyePosition; // Used for attack interpolation + char m_szLastPlaceName[MAX_PLACE_NAME_LENGTH]; // received from the server // Texture names and surface data, used by CGameMovement @@ -623,6 +633,11 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener #endif private: + QAngle m_angAttackViewAngles; // Stored view angles during attack + float m_flAttackInterpolationAmount = 1.0f; // Interpolation amount when attack was issued + bool m_bHasAttackInterpolationData = false; // Whether we have valid data + float m_flAttackLerpTime = 1.0f; // Calculated lerp time for the attack + bool m_bInPostThink; // Flag for post-think state struct StepSoundCache_t { diff --git a/src/game/client/in_main.cpp b/src/game/client/in_main.cpp index 5d2c6b0ee22..f5b748c7b24 100644 --- a/src/game/client/in_main.cpp +++ b/src/game/client/in_main.cpp @@ -1061,6 +1061,31 @@ void CInput::ExtraMouseSample( float frametime, bool active ) cmd->buttons = GetButtonBits( 0 ); #endif + // Check if this is an attack frame + bool bIsAttackFrame = false; + + C_BaseCombatWeapon* pWeapon = NULL; + C_BasePlayer* pPlayer = CBasePlayer::GetLocalPlayer(); + if (pPlayer) + pWeapon = pPlayer->GetActiveWeapon(); + + if (pWeapon) { + // Check primary attack + if ((cmd->buttons & IN_ATTACK) && pWeapon->m_flNextPrimaryAttack <= (gpGlobals->curtime + (gpGlobals->interpolation_amount * gpGlobals->interval_per_tick))) { + bIsAttackFrame = true; + } + // Check secondary attack + else if ((cmd->buttons & IN_ATTACK2) && pWeapon->m_flNextSecondaryAttack <= (gpGlobals->curtime + (gpGlobals->interpolation_amount * gpGlobals->interval_per_tick))) { + bIsAttackFrame = true; + } + } + + if (bIsAttackFrame && !pPlayer->HasAttackInterpolationData()) { + // Store attack data + pPlayer->SetAttackInterpolationData(viewangles, gpGlobals->interpolation_amount); + } + + // Use new view angles if alive, otherwise user last angles we stored off. if ( g_iAlive ) { @@ -1161,6 +1186,7 @@ void CInput::CreateMove ( int sequence_number, float input_sample_frametime, boo ResetMouse(); } } + // Retreive view angles from engine ( could have been set in IN_AdjustAngles above ) engine->GetViewAngles( viewangles ); @@ -1271,6 +1297,42 @@ void CInput::CreateMove ( int sequence_number, float input_sample_frametime, boo cmd->random_seed = MD5_PseudoRandom( sequence_number ) & 0x7fffffff; + C_BasePlayer* pPlayer = C_BasePlayer::GetLocalPlayer(); + bool bUseAttackData = pPlayer && pPlayer->HasAttackInterpolationData(); + + // If we have attack data, use it + if (bUseAttackData) { + QAngle attackAngles; + float lerpTime; + + pPlayer->GetAttackInterpolationData(attackAngles, lerpTime); + + // Store original angles before applying stored attack angles + QAngle currentAngles = viewangles; + + // Apply attack data to command + cmd->viewangles = attackAngles; + cmd->lerp_time = lerpTime; + + // Fix movement commands for new viewangles + float deltaYaw = DEG2RAD(currentAngles[YAW] - cmd->viewangles[YAW]); + float s = sin(deltaYaw); + float c = cos(deltaYaw); + + // Store original movement values + float forwardmove = cmd->forwardmove; + float sidemove = cmd->sidemove; + + // Adjust movement values based on angle change + cmd->forwardmove = (c * forwardmove) - (s * sidemove); + cmd->sidemove = (s * forwardmove) + (c * sidemove); + + pPlayer->ClearAttackInterpolationData(); + } + else { + cmd->lerp_time = 1.0f; + } + //Msg("CreateMove lerp_time was: %f\n", cmd->lerp_time); HLTVCamera()->CreateMove( cmd ); #if defined( REPLAY_ENABLED ) ReplayCamera()->CreateMove( cmd ); diff --git a/src/game/client/prediction.cpp b/src/game/client/prediction.cpp index b73f41d9aef..3aff217c278 100644 --- a/src/game/client/prediction.cpp +++ b/src/game/client/prediction.cpp @@ -618,7 +618,7 @@ void CPrediction::SetupMove( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper * { move->m_bGameCodeMovedPlayer = true; } - + player->SetPreviouslyPreviouslyPredictedEyePosition(player->GetAbsOrigin() + player->GetViewOffset()); move->m_nPlayerHandle = player->GetClientHandle(); move->m_vecVelocity = player->GetAbsVelocity(); move->SetAbsOrigin( player->GetNetworkOrigin() ); @@ -830,8 +830,14 @@ void CPrediction::RunPostThink( C_BasePlayer *player ) #if !defined( NO_ENTITY_PREDICTION ) VPROF( "CPrediction::RunPostThink" ); + // Mark that we're in post-think + player->SetInPostThink(true); + // Run post-think player->PostThink(); + + // Clear post-think flag + player->SetInPostThink(false); #endif } diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index ffe892dfa0b..dacc5486b88 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -457,6 +457,7 @@ BEGIN_DATADESC( CBasePlayer ) DEFINE_FIELD( m_flForwardMove, FIELD_FLOAT ), DEFINE_FIELD( m_flSideMove, FIELD_FLOAT ), DEFINE_FIELD( m_vecPreviouslyPredictedOrigin, FIELD_POSITION_VECTOR ), + DEFINE_FIELD(m_vecPreviouslyPreviouslyPredictedEyePosition, FIELD_POSITION_VECTOR), DEFINE_FIELD( m_nNumCrateHudHints, FIELD_INTEGER ), @@ -8159,6 +8160,7 @@ void SendProxy_CropFlagsToPlayerFlagBitsLength( const SendProp *pProp, const voi SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 2), 32, SPROP_NOSCALE|SPROP_CHANGES_OFTEN ), SendPropVector ( SENDINFO( m_vecBaseVelocity ), 32, SPROP_NOSCALE ), + SendPropVector(SENDINFO(m_vecPreviouslyPreviouslyPredictedEyePosition), 32, SPROP_NOSCALE | SPROP_CHANGES_OFTEN), SendPropEHandle ( SENDINFO( m_hConstraintEntity)), SendPropVector ( SENDINFO( m_vecConstraintCenter), 0, SPROP_NOSCALE ), diff --git a/src/game/server/player.h b/src/game/server/player.h index 902e0e627ca..d6d441f5486 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -844,7 +844,13 @@ class CBasePlayer : public CBaseCombatCharacter // How long since this player last interacted with something the game considers an objective/target/goal float GetTimeSinceLastObjective( void ) const { return ( m_flLastObjectiveTime == -1.f ) ? 999.f : gpGlobals->curtime - m_flLastObjectiveTime; } void SetLastObjectiveTime( float flTime ) { m_flLastObjectiveTime = flTime; } - + void SetAttackInterpolationData(const QAngle& viewAngles, float interpolationAmount); + void GetAttackInterpolationData(QAngle& viewAngles, float& lerpTime); + bool HasAttackInterpolationData() const; + void ClearAttackInterpolationData(); + void SetInPostThink(bool inPostThink); + bool IsInPostThink() const; + Vector GetInterpolatedEyePosition(); // Used by gamemovement to check if the entity is stuck. int m_StuckLast; @@ -908,7 +914,10 @@ class CBasePlayer : public CBaseCombatCharacter void ClearZoomOwner( void ); void SetPreviouslyPredictedOrigin( const Vector &vecAbsOrigin ); + void SetPreviouslyPreviouslyPredictedEyePosition(const Vector& vecAbsOrigin); const Vector &GetPreviouslyPredictedOrigin() const; + const Vector& GetPreviouslyPreviouslyPredictedEyePosition() const; + float GetFOVTime( void ){ return m_flFOVTime; } void AdjustDrownDmg( int nAmount ); @@ -1191,8 +1200,10 @@ class CBasePlayer : public CBaseCombatCharacter int m_nVehicleViewSavedFrame; // Used to mark which frame was the last one the view was calculated for Vector m_vecPreviouslyPredictedOrigin; // Used to determine if non-gamemovement game code has teleported, or tweaked the player's origin + CNetworkVar( Vector, m_vecPreviouslyPreviouslyPredictedEyePosition); // Used for attack interpolation int m_nBodyPitchPoseParam; + CNetworkString( m_szLastPlaceName, MAX_PLACE_NAME_LENGTH ); char m_szNetworkIDString[MAX_NETWORKID_LENGTH]; @@ -1262,6 +1273,11 @@ class CBasePlayer : public CBaseCombatCharacter // used to prevent achievement announcement spam CUtlVector< float > m_flAchievementTimes; + QAngle m_angAttackViewAngles; // Stored view angles during attack + float m_flAttackInterpolationAmount = 1.0f; // Interpolation amount when attack was issued + bool m_bHasAttackInterpolationData = false; // Whether we have valid data + float m_flAttackLerpTime = 1.0f; // Calculated lerp time for the attack + bool m_bInPostThink; // Flag for post-think state public: virtual unsigned int PlayerSolidMask( bool brushOnly = false ) const; // returns the solid mask for the given player, so bots can have a more-restrictive set diff --git a/src/game/server/player_command.cpp b/src/game/server/player_command.cpp index a8469d6ff92..c6d01d84aaf 100644 --- a/src/game/server/player_command.cpp +++ b/src/game/server/player_command.cpp @@ -141,7 +141,7 @@ void CPlayerMove::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *p { move->m_bGameCodeMovedPlayer = true; } - + player->SetPreviouslyPreviouslyPredictedEyePosition(player->GetAbsOrigin() + player->GetViewOffset()); // Prepare the usercmd fields move->m_nImpulseCommand = ucmd->impulse; move->m_vecViewAngles = ucmd->viewangles; @@ -300,8 +300,14 @@ void CPlayerMove::RunPostThink( CBasePlayer *player ) { VPROF( "CPlayerMove::RunPostThink" ); + // Mark that we're in post-think + player->SetInPostThink(true); // Run post-think player->PostThink(); + + // Clear post-think flag + player->SetInPostThink(false); + player->ClearAttackInterpolationData(); } void CommentarySystem_PePlayerRunCommand( CBasePlayer *player, CUserCmd *ucmd ); @@ -446,7 +452,8 @@ void CPlayerMove::RunCommand ( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper VPROF_SCOPE_BEGIN( "moveHelper->ProcessImpacts" ); moveHelper->ProcessImpacts(); VPROF_SCOPE_END(); - + //Msg("CPlayerMove::RunCommand lerp_time was: %f\n", ucmd->lerp_time); + player->SetAttackInterpolationData(ucmd->viewangles, ucmd->lerp_time); RunPostThink( player ); g_pGameMovement->FinishTrackPredictionErrors( player ); diff --git a/src/game/server/player_lagcompensation.cpp b/src/game/server/player_lagcompensation.cpp index 5192a46bbff..0449dec3495 100644 --- a/src/game/server/player_lagcompensation.cpp +++ b/src/game/server/player_lagcompensation.cpp @@ -389,18 +389,20 @@ void CLagCompensationManager::StartLagCompensation( CBasePlayer *player, CUserCm correct+= nci->GetLatency( FLOW_OUTGOING ); } - // calc number of view interpolation ticks - 1 - int lerpTicks = TIME_TO_TICKS( player->m_fLerpTime ); - - // add view interpolation latency see C_BaseEntity::GetInterpolationAmount() - correct += TICKS_TO_TIME( lerpTicks ); - - // check bouns [0,sv_maxunlag] - correct = clamp( correct, 0.0f, sv_maxunlag.GetFloat() ); - + // add view interpolation latency directly using player's lerp time + correct += player->m_fLerpTime; + + // check bounds [0,sv_maxunlag] + correct = clamp(correct, 0.0f, sv_maxunlag.GetFloat()); + // adjust attack timing if we have valid interpolation data + float extraTime = 0.0f; + if (cmd->lerp_time > 0.0f && cmd->lerp_time <= 1.0f) + { + // Add fraction of a tick based on lerp_time + extraTime = cmd->lerp_time * TICK_INTERVAL; + } // correct tick send by player - int targettick = cmd->tick_count - lerpTicks; - + int targettick = cmd->tick_count - TIME_TO_TICKS(player->m_fLerpTime); // calc difference between tick send by player and our latency based tick float deltaTime = correct - TICKS_TO_TIME(gpGlobals->tickcount - targettick); @@ -410,7 +412,8 @@ void CLagCompensationManager::StartLagCompensation( CBasePlayer *player, CUserCm // DevMsg("StartLagCompensation: delta too big (%.3f)\n", deltaTime ); targettick = gpGlobals->tickcount - TIME_TO_TICKS( correct ); } - + // Add the interpolation amount + float targettime = TICKS_TO_TIME(targettick) + extraTime; // Iterate all active players const CBitVec *pEntityTransmitBits = engine->GetEntityTransmitBitsForClient( player->entindex() - 1 ); for ( int i = 1; i <= gpGlobals->maxClients; i++ ) @@ -433,7 +436,7 @@ void CLagCompensationManager::StartLagCompensation( CBasePlayer *player, CUserCm continue; // Move other player back in time - BacktrackPlayer( pPlayer, TICKS_TO_TIME( targettick ) ); + BacktrackPlayer(pPlayer, targettime); } } diff --git a/src/game/shared/baseplayer_shared.cpp b/src/game/shared/baseplayer_shared.cpp index abbd3196495..059d8d7ccfb 100644 --- a/src/game/shared/baseplayer_shared.cpp +++ b/src/game/shared/baseplayer_shared.cpp @@ -329,8 +329,13 @@ const QAngle &CBasePlayer::LocalEyeAngles() //----------------------------------------------------------------------------- // Actual Eye position + angles //----------------------------------------------------------------------------- -Vector CBasePlayer::EyePosition( ) +Vector CBasePlayer::EyePosition() { + if (IsInPostThink() && HasAttackInterpolationData()) + { + return GetInterpolatedEyePosition(); + } + if ( GetVehicle() != NULL ) { // Return the cached result @@ -2068,11 +2073,21 @@ void CBasePlayer::SetPreviouslyPredictedOrigin( const Vector &vecAbsOrigin ) m_vecPreviouslyPredictedOrigin = vecAbsOrigin; } +void CBasePlayer::SetPreviouslyPreviouslyPredictedEyePosition(const Vector& vecAbsOrigin) +{ + m_vecPreviouslyPreviouslyPredictedEyePosition = vecAbsOrigin; +} + const Vector &CBasePlayer::GetPreviouslyPredictedOrigin() const { return m_vecPreviouslyPredictedOrigin; } +const Vector& CBasePlayer::GetPreviouslyPreviouslyPredictedEyePosition() const +{ + return m_vecPreviouslyPreviouslyPredictedEyePosition; +} + bool fogparams_t::operator !=( const fogparams_t& other ) const { if ( this->enable != other.enable || @@ -2096,3 +2111,60 @@ bool fogparams_t::operator !=( const fogparams_t& other ) const return false; } +void CBasePlayer::SetAttackInterpolationData(const QAngle& viewAngles, float interpolationAmount) +{ + m_angAttackViewAngles = viewAngles; + m_flAttackInterpolationAmount = interpolationAmount; + m_bHasAttackInterpolationData = true; + + // Calculate lerp time, clamped between 0 and 1 + m_flAttackLerpTime = clamp(interpolationAmount, 0.0f, 1.0f); +} + +void CBasePlayer::GetAttackInterpolationData(QAngle& viewAngles, float& lerpTime) +{ + viewAngles = m_angAttackViewAngles; + lerpTime = m_flAttackLerpTime; +} + +bool CBasePlayer::HasAttackInterpolationData() const +{ + return m_bHasAttackInterpolationData; +} + +void CBasePlayer::ClearAttackInterpolationData() +{ + m_bHasAttackInterpolationData = false; +} + +void CBasePlayer::SetInPostThink(bool inPostThink) +{ + m_bInPostThink = inPostThink; +} + +bool CBasePlayer::IsInPostThink() const +{ + return m_bInPostThink; +} +Vector CBasePlayer::GetInterpolatedEyePosition() +{ + bool wasInPostThink = IsInPostThink(); + SetInPostThink(false); + Vector currentEyePosition = EyePosition(); + SetInPostThink(wasInPostThink); + + if (!IsInPostThink() || !HasAttackInterpolationData()) { + return currentEyePosition; + } + + // Interpolate between the last tick's position and the current position + Vector interpolatedPosition = GetPreviouslyPreviouslyPredictedEyePosition() + (currentEyePosition - GetPreviouslyPreviouslyPredictedEyePosition()) * m_flAttackLerpTime; + + // Calculate the distance between interpolated position and regular eye position + float distance = (interpolatedPosition - currentEyePosition).Length(); + + // Log the distance using Msg() + //ConDMsg("Interpolated eye position distance from regular eye position: %.3f units, m_flAttackLerpTime: %f\n", distance, m_flAttackLerpTime); + + return interpolatedPosition; +} diff --git a/src/game/shared/tf/tf_item_inventory.cpp b/src/game/shared/tf/tf_item_inventory.cpp index 2206f003a02..cab73fb0634 100644 --- a/src/game/shared/tf/tf_item_inventory.cpp +++ b/src/game/shared/tf/tf_item_inventory.cpp @@ -964,7 +964,7 @@ void CTFPlayerInventory::LoadLocalLoadout() m_LoadoutItems[iClass][iSlot] = uItemId; CEconItemView *pItem = GetInventoryItemByItemID(uItemId); - if (pItem) { + if (pItem && pItem->GetSOCData()) { pItem->GetSOCData()->Equip(iClass, iSlot); } } diff --git a/src/game/shared/usercmd.cpp b/src/game/shared/usercmd.cpp index 84ffa243141..f5a98c5c90f 100644 --- a/src/game/shared/usercmd.cpp +++ b/src/game/shared/usercmd.cpp @@ -169,6 +169,7 @@ void WriteUsercmd( bf_write *buf, const CUserCmd *to, const CUserCmd *from ) buf->WriteOneBit( 0 ); } + #if defined( HL2_CLIENT_DLL ) if ( to->entitygroundcontact.Count() != 0 ) { @@ -187,6 +188,16 @@ void WriteUsercmd( bf_write *buf, const CUserCmd *to, const CUserCmd *from ) buf->WriteOneBit( 0 ); } #endif + + if (to->lerp_time != from->lerp_time) + { + buf->WriteOneBit(1); + buf->WriteFloat(to->lerp_time); + } + else + { + buf->WriteOneBit(0); + } } //----------------------------------------------------------------------------- @@ -289,6 +300,7 @@ void ReadUsercmd( bf_read *buf, CUserCmd *move, CUserCmd *from ) move->mousedy = buf->ReadShort(); } + #if defined( HL2_DLL ) if ( buf->ReadOneBit() ) { @@ -303,4 +315,8 @@ void ReadUsercmd( bf_read *buf, CUserCmd *move, CUserCmd *from ) } } #endif + if (buf->ReadOneBit()) + { + move->lerp_time = buf->ReadFloat(); + } } diff --git a/src/game/shared/usercmd.h b/src/game/shared/usercmd.h index 0005bde99f5..8c6974e5453 100644 --- a/src/game/shared/usercmd.h +++ b/src/game/shared/usercmd.h @@ -56,7 +56,7 @@ class CUserCmd #endif mousedx = 0; mousedy = 0; - + lerp_time = 1.0f; hasbeenpredicted = false; #if defined( HL2_DLL ) || defined( HL2_CLIENT_DLL ) entitygroundcontact.RemoveAll(); @@ -84,7 +84,7 @@ class CUserCmd #endif mousedx = src.mousedx; mousedy = src.mousedy; - + lerp_time = src.lerp_time; hasbeenpredicted = src.hasbeenpredicted; #if defined( HL2_DLL ) || defined( HL2_CLIENT_DLL ) @@ -171,7 +171,8 @@ class CUserCmd #if defined( HL2_DLL ) || defined( HL2_CLIENT_DLL ) CUtlVector< CEntityGroundContact > entitygroundcontact; #endif - + // Attack lerp time + double lerp_time; }; void ReadUsercmd( bf_read *buf, CUserCmd *move, CUserCmd *from );