You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
571 lines
16 KiB
571 lines
16 KiB
//========= 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
|
|
|