|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================//
#include "cbase.h"
#include "tf_viewmodel.h"
#include "tf_shareddefs.h"
#include "tf_weapon_minigun.h"
#include "tf_weapon_invis.h"
#ifdef CLIENT_DLL
#include "c_tf_player.h"
// for spy material proxy
#include "tf_proxyentity.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imaterialvar.h"
#include "prediction.h"
#endif
#include "bone_setup.h" //temp
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
LINK_ENTITY_TO_CLASS( tf_viewmodel, CTFViewModel );
IMPLEMENT_NETWORKCLASS_ALIASED( TFViewModel, DT_TFViewModel )
BEGIN_NETWORK_TABLE( CTFViewModel, DT_TFViewModel ) END_NETWORK_TABLE()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
#ifdef CLIENT_DLL
CTFViewModel::CTFViewModel() : m_LagAnglesHistory("CPredictedViewModel::m_LagAnglesHistory") , m_bBodygroupsDirty( true ) { m_vLagAngles.Init(); m_LagAnglesHistory.Setup( &m_vLagAngles, 0 ); m_vLoweredWeaponOffset.Init(); } #else
CTFViewModel::CTFViewModel() { } #endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFViewModel::~CTFViewModel() { }
#ifdef CLIENT_DLL
void DrawEconEntityAttachedModels( CBaseAnimating *pEnt, CEconEntity *pAttachedModelSource, const ClientModelRenderInfo_t *pInfo, int iMatchDisplayFlags );
// TODO: Turning this off by setting interp 0.0 instead of 0.1 for now since we have a timing bug to resolve
ConVar cl_wpn_sway_interp( "cl_wpn_sway_interp", "0.0", FCVAR_CLIENTDLL | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); ConVar cl_wpn_sway_scale( "cl_wpn_sway_scale", "5.0", FCVAR_CLIENTDLL | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); #endif
//-----------------------------------------------------------------------------
// Purpose: Adds head bob for off hand models
//-----------------------------------------------------------------------------
void CTFViewModel::AddViewModelBob( CBasePlayer *owner, Vector& eyePosition, QAngle& eyeAngles ) { #ifdef CLIENT_DLL
// if we are an off hand view model (index 1) and we have a model, add head bob.
// (Head bob for main hand model added by the weapon itself.)
if ( ViewModelIndex() == 1 && GetModel() != null ) { CalcViewModelBobHelper( owner, &m_BobState ); AddViewModelBobHelper( eyePosition, eyeAngles, &m_BobState ); } #endif
}
void CTFViewModel::CalcViewModelLag( Vector& origin, QAngle& angles, QAngle& original_angles ) { #ifdef CLIENT_DLL
if ( prediction->InPrediction() ) { return; }
if ( cl_wpn_sway_interp.GetFloat() <= 0.0f ) { return; }
// Calculate our drift
Vector forward, right, up; AngleVectors( angles, &forward, &right, &up );
// Add an entry to the history.
m_vLagAngles = angles; m_LagAnglesHistory.NoteChanged( gpGlobals->curtime, cl_wpn_sway_interp.GetFloat(), false );
// Interpolate back 100ms.
m_LagAnglesHistory.Interpolate( gpGlobals->curtime, cl_wpn_sway_interp.GetFloat() );
// Now take the 100ms angle difference and figure out how far the forward vector moved in local space.
Vector vLaggedForward; QAngle angleDiff = m_vLagAngles - angles; AngleVectors( -angleDiff, &vLaggedForward, 0, 0 ); Vector vForwardDiff = Vector(1,0,0) - vLaggedForward;
// Now offset the origin using that.
vForwardDiff *= cl_wpn_sway_scale.GetFloat(); origin += forward*vForwardDiff.x + right*-vForwardDiff.y + up*vForwardDiff.z; #endif
}
#ifdef CLIENT_DLL
ConVar cl_gunlowerangle( "cl_gunlowerangle", "90", FCVAR_CLIENTDLL | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); ConVar cl_gunlowerspeed( "cl_gunlowerspeed", "2", FCVAR_CLIENTDLL | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_use_min_viewmodels( "tf_use_min_viewmodels", "0", FCVAR_ARCHIVE, "Use minimized viewmodels." );
ConVar tf_viewmodels_offset_override( "tf_viewmodels_offset_override", "", FCVAR_CHEAT, "If set, this will override the position of all viewmodels. Usage 'x y z'" ); #endif
void CTFViewModel::CalcViewModelView( CBasePlayer *owner, const Vector& eyePosition, const QAngle& eyeAngles ) { #if defined( CLIENT_DLL )
Vector vecNewOrigin = eyePosition; QAngle vecNewAngles = eyeAngles;
// Check for lowering the weapon
C_TFPlayer *pPlayer = ToTFPlayer( owner );
Assert( pPlayer );
bool bLowered = pPlayer->IsWeaponLowered();
QAngle vecLoweredAngles(0,0,0);
m_vLoweredWeaponOffset.x = Approach( bLowered ? cl_gunlowerangle.GetFloat() : 0, m_vLoweredWeaponOffset.x, cl_gunlowerspeed.GetFloat() ); vecLoweredAngles.x += m_vLoweredWeaponOffset.x;
vecNewAngles += vecLoweredAngles;
// we want to always enable this internally
bool bShouldUseMinMode = tf_use_min_viewmodels.GetBool();
// are we overriding vm offset?
const char *pszVMOffsetOverride = tf_viewmodels_offset_override.GetString(); bool bOverride = ( pszVMOffsetOverride && *pszVMOffsetOverride ); bShouldUseMinMode |= bOverride;
// alt view model
CTFWeaponBase *pWeapon = assert_cast< CTFWeaponBase* >( GetWeapon() ); if ( bShouldUseMinMode && pWeapon ) { static float s_inspectInterp = 1.f; if ( pWeapon->GetInspectStage() != CTFWeaponBase::INSPECT_INVALID ) { if ( pWeapon->GetInspectStage() == CTFWeaponBase::INSPECT_END ) { // use the last second of the anim
s_inspectInterp = Clamp( 1.f - ( pWeapon->GetInspectAnimTime() - gpGlobals->curtime ), 0.f, 1.f ); } else { s_inspectInterp = Clamp( s_inspectInterp - gpGlobals->frametime, 0.f, 1.f ); } } else { s_inspectInterp = Clamp( s_inspectInterp + gpGlobals->frametime, 0.f, 1.f ); }
Vector forward, right, up; AngleVectors( eyeAngles, &forward, &right, &up );
Vector viewmodelOffset; if ( bOverride ) { UTIL_StringToVector( viewmodelOffset.Base(), pszVMOffsetOverride ); } else { viewmodelOffset = pWeapon->GetViewmodelOffset(); } Vector vOffset = viewmodelOffset.x * forward + viewmodelOffset.y * right + viewmodelOffset.z * up; vOffset *= Gain( s_inspectInterp, 0.5f ); vecNewOrigin += vOffset; }
BaseClass::CalcViewModelView( owner, vecNewOrigin, vecNewAngles );
#endif
}
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: Don't render the weapon if its supposed to be lowered and we have
// finished the lowering animation
//-----------------------------------------------------------------------------
int CTFViewModel::DrawModel( int flags ) { // Check for lowering the weapon
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
Assert( pPlayer );
if ( m_bBodygroupsDirty ) { m_nBody = 0; pPlayer->RecalcBodygroupsIfDirty(); m_bBodygroupsDirty = false; }
bool bLowered = pPlayer->IsWeaponLowered();
if ( bLowered && fabs( m_vLoweredWeaponOffset.x - cl_gunlowerangle.GetFloat() ) < 0.1 ) { // fully lowered, stop drawing
return 1; }
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE && pLocalPlayer->GetObserverTarget() && pLocalPlayer->GetObserverTarget()->IsPlayer() ) { pPlayer = ToTFPlayer( pLocalPlayer->GetObserverTarget() ); }
if ( pPlayer != GetOwner() && pPlayer->GetViewModel() != GetMoveParent() ) { return 0; }
if ( pPlayer->IsAlive() == false ) { return 0; }
return BaseClass::DrawModel( flags ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFViewModel::OnPostInternalDrawModel( ClientModelRenderInfo_t *pInfo ) { if ( !BaseClass::OnPostInternalDrawModel( pInfo ) ) return false;
CTFWeaponBase *pWeapon = ( CTFWeaponBase * )GetOwningWeapon();
if ( pWeapon && !pWeapon->WantsToOverrideViewmodelAttachments() ) { // only need to draw the attached models if the weapon doesn't want to override the viewmodel attachments
// (used for Natascha's attachments, the Backburner, and the Kritzkrieg)
DrawEconEntityAttachedModels( this, pWeapon, pInfo, kAttachedModelDisplayFlag_ViewModel ); }
return true; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFViewModel::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ) { BaseClass::StandardBlendingRules( hdr, pos, q, currentTime, boneMask );
CTFWeaponBase *pWeapon = ( CTFWeaponBase * )GetOwningWeapon();
if ( !pWeapon ) return;
if ( pWeapon->GetWeaponID() == TF_WEAPON_MINIGUN ) { CTFMinigun *pMinigun = ( CTFMinigun * )pWeapon;
int iBarrelBone = Studio_BoneIndexByName( hdr, "v_minigun_barrel" );
// Assert( iBarrelBone != -1 );
if ( iBarrelBone != -1 ) { if ( hdr->boneFlags( iBarrelBone ) & boneMask ) { RadianEuler a; QuaternionAngles( q[iBarrelBone], a );
a.x = pMinigun->GetBarrelRotation();
AngleQuaternion( a, q[iBarrelBone] ); } } } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFViewModel::ProcessMuzzleFlashEvent() { CTFWeaponBase *pWeapon = ( CTFWeaponBase * )GetOwningWeapon();
if ( !pWeapon || C_BasePlayer::ShouldDrawLocalPlayer() ) return;
pWeapon->ProcessMuzzleFlashEvent(); }
//-----------------------------------------------------------------------------
// Purpose: Used for spy invisiblity material
//-----------------------------------------------------------------------------
int CTFViewModel::GetSkin() { int nSkin = BaseClass::GetSkin();
CTFWeaponBase *pWeapon = ( CTFWeaponBase * )GetOwningWeapon();
if ( !pWeapon ) return nSkin;
CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( pPlayer ) { // See if the item wants to override the skin
int iItemSkin = -1; CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem(); if ( pItem->IsValid() ) { iItemSkin = pItem->GetSkin( pPlayer->GetTeamNumber(), true ); }
if ( iItemSkin != -1 ) { nSkin = iItemSkin; } else if ( pWeapon->GetTFWpnData().m_bHasTeamSkins_Viewmodel ) { switch( pPlayer->GetTeamNumber() ) { case TF_TEAM_RED: nSkin = 0; break; case TF_TEAM_BLUE: nSkin = 1; break; } } }
return nSkin; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char* CTFViewModel::ModifyEventParticles( const char* token ) { CTFWeaponBase *pWeapon = (CTFWeaponBase*) GetOwningWeapon(); if ( pWeapon ) { return pWeapon->ModifyEventParticles( token ); } return BaseClass::ModifyEventParticles( token ); }
//-----------------------------------------------------------------------------
// Purpose: Used for spy invisiblity material
//-----------------------------------------------------------------------------
class CViewModelInvisProxy : public CBaseInvisMaterialProxy { public: virtual void OnBind( C_BaseEntity *pC_BaseEntity ); };
#define TF_VM_MIN_INVIS 0.22
#define TF_VM_MAX_INVIS 0.5
//-----------------------------------------------------------------------------
// Purpose:
// Input :
//-----------------------------------------------------------------------------
void CViewModelInvisProxy::OnBind( C_BaseEntity *pEnt ) { if ( !m_pPercentInvisible ) return;
bool bIsViewModel = false;
CTFPlayer *pPlayer = NULL; C_BaseEntity *pMoveParent = pEnt->GetMoveParent();
//Check if we have a move parent and if its a player
if ( pMoveParent ) { if ( pMoveParent->IsPlayer() ) { pPlayer = ToTFPlayer( pMoveParent ); } } //If its not a player then check for viewmodel.
if ( pPlayer == NULL ) { CBaseEntity *pEntParent = pMoveParent;
if ( pEntParent == NULL ) { pEntParent = pEnt; }
CTFViewModel *pVM = dynamic_cast<CTFViewModel *>( pEntParent );
if ( pVM ) { pPlayer = ToTFPlayer( pVM->GetOwner() ); bIsViewModel = true; } }
// do we have a player from viewmodel?
if ( !pPlayer ) { m_pPercentInvisible->SetFloatValue( 0.0f ); return; } float flPercentInvisible = pPlayer->GetPercentInvisible(); float flWeaponInvis = flPercentInvisible;
if ( bIsViewModel == true ) { // remap from 0.22 to 0.5
// but drop to 0.0 if we're not invis at all
flWeaponInvis = ( flPercentInvisible < 0.01 ) ? 0.0 : RemapVal( flPercentInvisible, 0.0, 1.0, TF_VM_MIN_INVIS, TF_VM_MAX_INVIS );
// Exaggerated blink effect on bump.
if ( pPlayer->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ) { flWeaponInvis = 0.3f; }
// Also exaggerate the effect if we're using motion cloak and our well has run dry.
CTFWeaponInvis *pWpn = (CTFWeaponInvis *) pPlayer->Weapon_OwnsThisID( TF_WEAPON_INVIS ); if ( pWpn && pWpn->HasMotionCloak() && (pPlayer->m_Shared.GetSpyCloakMeter() <= 0.f ) ) { flWeaponInvis = 0.3f; } }
m_pPercentInvisible->SetFloatValue( flWeaponInvis ); }
EXPOSE_INTERFACE( CViewModelInvisProxy, IMaterialProxy, "vm_invis" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: Generic invis proxy that can handle invis for both weapons & viewmodels.
// Makes the vm_invis & weapon_invis proxies obsolete, do not use them.
//-----------------------------------------------------------------------------
class CInvisProxy : public CBaseInvisMaterialProxy { public: virtual void OnBind( C_BaseEntity *pC_BaseEntity ) OVERRIDE; };
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CInvisProxy::OnBind( C_BaseEntity *pC_BaseEntity ) { if( !m_pPercentInvisible ) return;
C_BaseEntity *pEnt = pC_BaseEntity;
CTFPlayer *pPlayer = NULL;
// Check if we have a move parent and if it's a player
C_BaseEntity *pMoveParent = pEnt->GetMoveParent(); if ( pMoveParent && pMoveParent->IsPlayer() ) { pPlayer = ToTFPlayer( pMoveParent ); }
// If it's not a player then check for viewmodel.
if ( !pPlayer ) { CBaseEntity *pEntParent = pMoveParent ? pMoveParent : pEnt;
CTFViewModel *pVM = dynamic_cast<CTFViewModel *>( pEntParent ); if ( pVM ) { pPlayer = ToTFPlayer( pVM->GetOwner() ); } } if ( !pPlayer ) { if ( pEnt->IsPlayer() ) { pPlayer = dynamic_cast<C_TFPlayer*>( pEnt ); } else { IHasOwner *pOwnerInterface = dynamic_cast<IHasOwner*>( pEnt ); if ( pOwnerInterface ) { pPlayer = ToTFPlayer( pOwnerInterface->GetOwnerViaInterface() ); } } } if ( !pPlayer ) { m_pPercentInvisible->SetFloatValue( 0.0f ); return; }
// If we're the local player, use the old "vm_invis" code. Otherwise, use the "weapon_invis".
if ( pPlayer->IsLocalPlayer() ) { float flPercentInvisible = pPlayer->GetPercentInvisible(); float flWeaponInvis = flPercentInvisible;
// remap from 0.22 to 0.5
// but drop to 0.0 if we're not invis at all
flWeaponInvis = ( flPercentInvisible < 0.01 ) ? 0.0 : RemapVal( flPercentInvisible, 0.0, 1.0, TF_VM_MIN_INVIS, TF_VM_MAX_INVIS );
// Exaggerated blink effect on bump.
if ( pPlayer->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ) { flWeaponInvis = 0.3f; }
// Also exaggerate the effect if we're using motion cloak and our well has run dry.
CTFWeaponInvis *pWpn = (CTFWeaponInvis *) pPlayer->Weapon_OwnsThisID( TF_WEAPON_INVIS ); if ( pWpn && pWpn->HasMotionCloak() && (pPlayer->m_Shared.GetSpyCloakMeter() <= 0.f ) ) { flWeaponInvis = 0.3f; }
m_pPercentInvisible->SetFloatValue( flWeaponInvis ); } else { m_pPercentInvisible->SetFloatValue( pPlayer->GetEffectiveInvisibilityLevel() ); } }
// Generic invis proxy that can handle invis for both weapons & viewmodels.
// Makes the vm_invis & weapon_invis proxies obsolete, do not use them.
EXPOSE_INTERFACE( CInvisProxy, IMaterialProxy, "invis" IMATERIAL_PROXY_INTERFACE_VERSION );
#endif // CLIENT_DLL
|