source-engine/game/client/tf2base/c_tf_player.cpp
2022-08-13 03:20:41 +03:00

3532 lines
99 KiB
C++

//====== Copyright © 1996-2003, Valve Corporation, All rights reserved. =======
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "c_tf_player.h"
#include "c_user_message_register.h"
#include "view.h"
#include "iclientvehicle.h"
#include "ivieweffects.h"
#include "input.h"
#include "IEffects.h"
#include "fx.h"
#include "c_basetempentity.h"
#include "hud_macros.h"
#include "engine/ivdebugoverlay.h"
#include "smoke_fog_overlay.h"
#include "playerandobjectenumerator.h"
#include "bone_setup.h"
#include "in_buttons.h"
#include "r_efx.h"
#include "dlight.h"
#include "shake.h"
#include "cl_animevent.h"
#include "tf_weaponbase.h"
#include "c_tf_playerresource.h"
#include "toolframework/itoolframework.h"
#include "tier1/KeyValues.h"
#include "tier0/vprof.h"
#include "prediction.h"
#include "effect_dispatch_data.h"
#include "c_te_effect_dispatch.h"
#include "tf_fx_muzzleflash.h"
#include "tf_gamerules.h"
#include "view_scene.h"
#include "c_baseobject.h"
#include "toolframework_client.h"
#include "soundenvelope.h"
#include "voice_status.h"
#include "clienteffectprecachesystem.h"
#include "functionproxy.h"
#include "toolframework_client.h"
#include "choreoevent.h"
#include "vguicenterprint.h"
#include "eventlist.h"
#include "tf_hud_statpanel.h"
#include "input.h"
#include "tf_weapon_medigun.h"
#include "tf_weapon_pipebomblauncher.h"
#include "tf_hud_mediccallers.h"
#include "in_main.h"
#include "c_team.h"
#include "collisionutils.h"
// for spy material proxy
#include "proxyentity.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imaterialvar.h"
#include "c_tf_team.h"
#if defined( CTFPlayer )
#undef CTFPlayer
#endif
#include "materialsystem/imesh.h" //for materials->FindMaterial
#include "iviewrender.h" //for view->
#include "cam_thirdperson.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar tf_playergib_forceup( "tf_playersgib_forceup", "1.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Upward added velocity for gibs." );
ConVar tf_playergib_force( "tf_playersgib_force", "500.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Gibs force." );
ConVar tf_playergib_maxspeed( "tf_playergib_maxspeed", "400", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Max gib speed." );
ConVar cl_autorezoom( "cl_autorezoom", "1", FCVAR_USERINFO | FCVAR_ARCHIVE, "When set to 1, sniper rifle will re-zoom after firing a zoomed shot." );
#define BDAY_HAT_MODEL "models/effects/bday_hat.mdl"
IMaterial *g_pHeadLabelMaterial[2] = { NULL, NULL };
void SetupHeadLabelMaterials( void );
extern CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position,
const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, int nSkin, const breakablepropparams_t &params );
const char *pszHeadLabelNames[] =
{
"effects/speech_voice_red",
"effects/speech_voice_blue"
};
#define TF_PLAYER_HEAD_LABEL_RED 0
#define TF_PLAYER_HEAD_LABEL_BLUE 1
CLIENTEFFECT_REGISTER_BEGIN( PrecacheInvuln )
CLIENTEFFECT_MATERIAL( "models/effects/invulnfx_blue.vmt" )
CLIENTEFFECT_MATERIAL( "models/effects/invulnfx_red.vmt" )
CLIENTEFFECT_REGISTER_END()
// -------------------------------------------------------------------------------- //
// Player animation event. Sent to the client when a player fires, jumps, reloads, etc..
// -------------------------------------------------------------------------------- //
class C_TEPlayerAnimEvent : public C_BaseTempEntity
{
public:
DECLARE_CLASS( C_TEPlayerAnimEvent, C_BaseTempEntity );
DECLARE_CLIENTCLASS();
virtual void PostDataUpdate( DataUpdateType_t updateType )
{
VPROF( "C_TEPlayerAnimEvent::PostDataUpdate" );
// Create the effect.
if ( m_iPlayerIndex == TF_PLAYER_INDEX_NONE )
return;
EHANDLE hPlayer = cl_entitylist->GetNetworkableHandle( m_iPlayerIndex );
if ( !hPlayer )
return;
C_TFPlayer *pPlayer = dynamic_cast< C_TFPlayer* >( hPlayer.Get() );
if ( pPlayer && !pPlayer->IsDormant() )
{
pPlayer->DoAnimationEvent( (PlayerAnimEvent_t)m_iEvent.Get(), m_nData );
}
}
public:
CNetworkVar( int, m_iPlayerIndex );
CNetworkVar( int, m_iEvent );
CNetworkVar( int, m_nData );
};
IMPLEMENT_CLIENTCLASS_EVENT( C_TEPlayerAnimEvent, DT_TEPlayerAnimEvent, CTEPlayerAnimEvent );
//-----------------------------------------------------------------------------
// Data tables and prediction tables.
//-----------------------------------------------------------------------------
BEGIN_RECV_TABLE_NOBASE( C_TEPlayerAnimEvent, DT_TEPlayerAnimEvent )
RecvPropInt( RECVINFO( m_iPlayerIndex ) ),
RecvPropInt( RECVINFO( m_iEvent ) ),
RecvPropInt( RECVINFO( m_nData ) )
END_RECV_TABLE()
//=============================================================================
//
// Ragdoll
//
// ----------------------------------------------------------------------------- //
// Client ragdoll entity.
// ----------------------------------------------------------------------------- //
ConVar cl_ragdoll_physics_enable( "cl_ragdoll_physics_enable", "1", 0, "Enable/disable ragdoll physics." );
ConVar cl_ragdoll_fade_time( "cl_ragdoll_fade_time", "15", FCVAR_CLIENTDLL );
ConVar cl_ragdoll_forcefade( "cl_ragdoll_forcefade", "0", FCVAR_CLIENTDLL );
ConVar cl_ragdoll_pronecheck_distance( "cl_ragdoll_pronecheck_distance", "64", FCVAR_GAMEDLL );
class C_TFRagdoll : public C_BaseFlex
{
public:
DECLARE_CLASS( C_TFRagdoll, C_BaseFlex );
DECLARE_CLIENTCLASS();
C_TFRagdoll();
~C_TFRagdoll();
virtual void OnDataChanged( DataUpdateType_t type );
IRagdoll* GetIRagdoll() const;
void ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName );
void ClientThink( void );
void StartFadeOut( float fDelay );
void EndFadeOut();
EHANDLE GetPlayerHandle( void )
{
if ( m_iPlayerIndex == TF_PLAYER_INDEX_NONE )
return NULL;
return cl_entitylist->GetNetworkableHandle( m_iPlayerIndex );
}
bool IsRagdollVisible();
float GetBurnStartTime() { return m_flBurnEffectStartTime; }
virtual void SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights );
private:
C_TFRagdoll( const C_TFRagdoll & ) {}
void Interp_Copy( C_BaseAnimatingOverlay *pSourceEntity );
void CreateTFRagdoll();
void CreateTFGibs( void );
private:
CNetworkVector( m_vecRagdollVelocity );
CNetworkVector( m_vecRagdollOrigin );
int m_iPlayerIndex;
float m_fDeathTime;
bool m_bFadingOut;
bool m_bGib;
bool m_bBurning;
int m_iTeam;
int m_iClass;
float m_flBurnEffectStartTime; // start time of burning, or 0 if not burning
};
IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_TFRagdoll, DT_TFRagdoll, CTFRagdoll )
RecvPropVector( RECVINFO(m_vecRagdollOrigin) ),
RecvPropInt( RECVINFO( m_iPlayerIndex ) ),
RecvPropVector( RECVINFO(m_vecForce) ),
RecvPropVector( RECVINFO(m_vecRagdollVelocity) ),
RecvPropInt( RECVINFO( m_nForceBone ) ),
RecvPropBool( RECVINFO( m_bGib ) ),
RecvPropBool( RECVINFO( m_bBurning ) ),
RecvPropInt( RECVINFO( m_iTeam ) ),
RecvPropInt( RECVINFO( m_iClass ) ),
END_RECV_TABLE()
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
C_TFRagdoll::C_TFRagdoll()
{
m_iPlayerIndex = TF_PLAYER_INDEX_NONE;
m_fDeathTime = -1;
m_bFadingOut = false;
m_bGib = false;
m_bBurning = false;
m_flBurnEffectStartTime = 0.0f;
m_iTeam = -1;
m_iClass = -1;
m_nForceBone = -1;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
C_TFRagdoll::~C_TFRagdoll()
{
PhysCleanupFrictionSounds( this );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSourceEntity -
//-----------------------------------------------------------------------------
void C_TFRagdoll::Interp_Copy( C_BaseAnimatingOverlay *pSourceEntity )
{
if ( !pSourceEntity )
return;
VarMapping_t *pSrc = pSourceEntity->GetVarMapping();
VarMapping_t *pDest = GetVarMapping();
// Find all the VarMapEntry_t's that represent the same variable.
for ( int i = 0; i < pDest->m_Entries.Count(); i++ )
{
VarMapEntry_t *pDestEntry = &pDest->m_Entries[i];
for ( int j=0; j < pSrc->m_Entries.Count(); j++ )
{
VarMapEntry_t *pSrcEntry = &pSrc->m_Entries[j];
if ( !Q_strcmp( pSrcEntry->watcher->GetDebugName(), pDestEntry->watcher->GetDebugName() ) )
{
pDestEntry->watcher->Copy( pSrcEntry->watcher );
break;
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Setup vertex weights for drawing
//-----------------------------------------------------------------------------
void C_TFRagdoll::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights )
{
// While we're dying, we want to mimic the facial animation of the player. Once they're dead, we just stay as we are.
EHANDLE hPlayer = GetPlayerHandle();
if ( ( hPlayer && hPlayer->IsAlive()) || !hPlayer )
{
BaseClass::SetupWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights );
}
else if ( hPlayer )
{
hPlayer->SetupWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTrace -
// iDamageType -
// *pCustomImpactName -
//-----------------------------------------------------------------------------
void C_TFRagdoll::ImpactTrace(trace_t *pTrace, int iDamageType, const char *pCustomImpactName)
{
VPROF( "C_TFRagdoll::ImpactTrace" );
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if( !pPhysicsObject )
return;
Vector vecDir;
VectorSubtract( pTrace->endpos, pTrace->startpos, vecDir );
if ( iDamageType == DMG_BLAST )
{
// Adjust the impact strength and apply the force at the center of mass.
vecDir *= 4000;
pPhysicsObject->ApplyForceCenter( vecDir );
}
else
{
// Find the apporx. impact point.
Vector vecHitPos;
VectorMA( pTrace->startpos, pTrace->fraction, vecDir, vecHitPos );
VectorNormalize( vecDir );
// Adjust the impact strength and apply the force at the impact point..
vecDir *= 4000;
pPhysicsObject->ApplyForceOffset( vecDir, vecHitPos );
}
m_pRagdoll->ResetRagdollSleepAfterTime();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
void C_TFRagdoll::CreateTFRagdoll()
{
// Get the player.
C_TFPlayer *pPlayer = NULL;
EHANDLE hPlayer = GetPlayerHandle();
if ( hPlayer )
{
pPlayer = dynamic_cast<C_TFPlayer*>( hPlayer.Get() );
}
TFPlayerClassData_t *pData = GetPlayerClassData( m_iClass );
if ( pData )
{
int nModelIndex = modelinfo->GetModelIndex( pData->GetModelName() );
SetModelIndex( nModelIndex );
if ( m_iTeam == TF_TEAM_RED )
{
m_nSkin = 0;
}
else
{
m_nSkin = 1;
}
}
#ifdef _DEBUG
DevMsg( 2, "CreateTFRagdoll %d %d\n", gpGlobals->framecount, pPlayer ? pPlayer->entindex() : 0 );
#endif
if ( pPlayer && !pPlayer->IsDormant() )
{
// Move my current model instance to the ragdoll's so decals are preserved.
pPlayer->SnatchModelInstance( this );
VarMapping_t *varMap = GetVarMapping();
// Copy all the interpolated vars from the player entity.
// The entity uses the interpolated history to get bone velocity.
if ( !pPlayer->IsLocalPlayer() && pPlayer->IsInterpolationEnabled() )
{
Interp_Copy( pPlayer );
SetAbsAngles( pPlayer->GetRenderAngles() );
GetRotationInterpolator().Reset();
m_flAnimTime = pPlayer->m_flAnimTime;
SetSequence( pPlayer->GetSequence() );
m_flPlaybackRate = pPlayer->GetPlaybackRate();
}
else
{
// This is the local player, so set them in a default
// pose and slam their velocity, angles and origin
SetAbsOrigin( /* m_vecRagdollOrigin : */ pPlayer->GetRenderOrigin() );
SetAbsAngles( pPlayer->GetRenderAngles() );
SetAbsVelocity( m_vecRagdollVelocity );
// Hack! Find a neutral standing pose or use the idle.
int iSeq = LookupSequence( "RagdollSpawn" );
if ( iSeq == -1 )
{
Assert( false );
iSeq = 0;
}
SetSequence( iSeq );
SetCycle( 0.0 );
Interp_Reset( varMap );
}
m_nBody = pPlayer->GetBody();
}
else
{
// Overwrite network origin so later interpolation will use this position.
SetNetworkOrigin( m_vecRagdollOrigin );
SetAbsOrigin( m_vecRagdollOrigin );
SetAbsVelocity( m_vecRagdollVelocity );
Interp_Reset( GetVarMapping() );
}
// Turn it into a ragdoll.
if ( cl_ragdoll_physics_enable.GetBool() )
{
// Make us a ragdoll..
m_nRenderFX = kRenderFxRagdoll;
matrix3x4_t boneDelta0[MAXSTUDIOBONES];
matrix3x4_t boneDelta1[MAXSTUDIOBONES];
matrix3x4_t currentBones[MAXSTUDIOBONES];
const float boneDt = 0.05f;
// We have to make sure that we're initting this client ragdoll off of the same model.
// GetRagdollInitBoneArrays uses the *player* Hdr, which may be a different model than
// the ragdoll Hdr, if we try to create a ragdoll in the same frame that the player
// changes their player model.
CStudioHdr *pRagdollHdr = GetModelPtr();
CStudioHdr *pPlayerHdr = NULL;
if ( pPlayer )
pPlayerHdr = pPlayer->GetModelPtr();
bool bChangedModel = false;
if ( pRagdollHdr && pPlayerHdr )
{
bChangedModel = pRagdollHdr->GetVirtualModel() != pPlayerHdr->GetVirtualModel();
Assert( !bChangedModel && "C_TFRagdoll::CreateTFRagdoll: Trying to create ragdoll with a different model than the player it's based on" );
}
if ( pPlayer && !pPlayer->IsDormant() && !bChangedModel )
{
pPlayer->GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt );
}
else
{
GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt );
}
InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt );
}
else
{
ClientLeafSystem()->SetRenderGroup( GetRenderHandle(), RENDER_GROUP_TRANSLUCENT_ENTITY );
}
if ( m_bBurning )
{
m_flBurnEffectStartTime = gpGlobals->curtime;
ParticleProp()->Create( "burningplayer_corpse", PATTACH_ABSORIGIN_FOLLOW );
}
// Fade out the ragdoll in a while
StartFadeOut( cl_ragdoll_fade_time.GetFloat() );
SetNextClientThink( gpGlobals->curtime + cl_ragdoll_fade_time.GetFloat() * 0.33f );
// Birthday mode.
if ( pPlayer && TFGameRules() && TFGameRules()->IsBirthday() )
{
AngularImpulse angularImpulse( RandomFloat( 0.0f, 120.0f ), RandomFloat( 0.0f, 120.0f ), 0.0 );
breakablepropparams_t breakParams( m_vecRagdollOrigin, GetRenderAngles(), m_vecRagdollVelocity, angularImpulse );
breakParams.impactEnergyScale = 1.0f;
pPlayer->DropPartyHat( breakParams, m_vecRagdollVelocity.GetForModify() );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFRagdoll::CreateTFGibs( void )
{
C_TFPlayer *pPlayer = NULL;
EHANDLE hPlayer = GetPlayerHandle();
if ( hPlayer )
{
pPlayer = dynamic_cast<C_TFPlayer*>( hPlayer.Get() );
}
if ( pPlayer && ( pPlayer->m_hFirstGib == NULL ) )
{
Vector vecVelocity = m_vecForce + m_vecRagdollVelocity;
VectorNormalize( vecVelocity );
pPlayer->CreatePlayerGibs( m_vecRagdollOrigin, vecVelocity, m_vecForce.Length() );
}
if ( pPlayer && TFGameRules() && TFGameRules()->IsBirthday() )
{
DispatchParticleEffect( "bday_confetti", pPlayer->GetAbsOrigin() + Vector(0,0,32), vec3_angle );
C_BaseEntity::EmitSound( "Game.HappyBirthday" );
}
EndFadeOut();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : type -
//-----------------------------------------------------------------------------
void C_TFRagdoll::OnDataChanged( DataUpdateType_t type )
{
BaseClass::OnDataChanged( type );
if ( type == DATA_UPDATE_CREATED )
{
bool bCreateRagdoll = true;
// Get the player.
EHANDLE hPlayer = GetPlayerHandle();
if ( hPlayer )
{
// If we're getting the initial update for this player (e.g., after resetting entities after
// lots of packet loss, then don't create gibs, ragdolls if the player and it's gib/ragdoll
// both show up on same frame.
if ( abs( hPlayer->GetCreationTick() - gpGlobals->tickcount ) < TIME_TO_TICKS( 1.0f ) )
{
bCreateRagdoll = false;
}
}
else if ( C_BasePlayer::GetLocalPlayer() )
{
// Ditto for recreation of the local player
if ( abs( C_BasePlayer::GetLocalPlayer()->GetCreationTick() - gpGlobals->tickcount ) < TIME_TO_TICKS( 1.0f ) )
{
bCreateRagdoll = false;
}
}
if ( bCreateRagdoll )
{
if ( m_bGib )
{
CreateTFGibs();
}
else
{
CreateTFRagdoll();
}
}
}
else
{
if ( !cl_ragdoll_physics_enable.GetBool() )
{
// Don't let it set us back to a ragdoll with data from the server.
m_nRenderFX = kRenderFxNone;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
// Output : IRagdoll*
//-----------------------------------------------------------------------------
IRagdoll* C_TFRagdoll::GetIRagdoll() const
{
return m_pRagdoll;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_TFRagdoll::IsRagdollVisible()
{
Vector vMins = Vector(-1,-1,-1); //WorldAlignMins();
Vector vMaxs = Vector(1,1,1); //WorldAlignMaxs();
Vector origin = GetAbsOrigin();
if( !engine->IsBoxInViewCluster( vMins + origin, vMaxs + origin) )
{
return false;
}
else if( engine->CullBox( vMins + origin, vMaxs + origin ) )
{
return false;
}
return true;
}
void C_TFRagdoll::ClientThink( void )
{
SetNextClientThink( CLIENT_THINK_ALWAYS );
if ( m_bFadingOut == true )
{
int iAlpha = GetRenderColor().a;
int iFadeSpeed = 600.0f;
iAlpha = MAX( iAlpha - ( iFadeSpeed * gpGlobals->frametime ), 0 );
SetRenderMode( kRenderTransAlpha );
SetRenderColorA( iAlpha );
if ( iAlpha == 0 )
{
EndFadeOut(); // remove clientside ragdoll
}
return;
}
// if the player is looking at us, delay the fade
if ( IsRagdollVisible() )
{
if ( cl_ragdoll_forcefade.GetBool() )
{
m_bFadingOut = true;
float flDelay = cl_ragdoll_fade_time.GetFloat() * 0.33f;
m_fDeathTime = gpGlobals->curtime + flDelay;
// If we were just fully healed, remove all decals
RemoveAllDecals();
}
StartFadeOut( cl_ragdoll_fade_time.GetFloat() * 0.33f );
return;
}
if ( m_fDeathTime > gpGlobals->curtime )
return;
EndFadeOut(); // remove clientside ragdoll
}
void C_TFRagdoll::StartFadeOut( float fDelay )
{
if ( !cl_ragdoll_forcefade.GetBool() )
{
m_fDeathTime = gpGlobals->curtime + fDelay;
}
SetNextClientThink( CLIENT_THINK_ALWAYS );
}
void C_TFRagdoll::EndFadeOut()
{
SetNextClientThink( CLIENT_THINK_NEVER );
ClearRagdoll();
SetRenderMode( kRenderNone );
UpdateVisibility();
}
//-----------------------------------------------------------------------------
// Purpose: Used for spy invisiblity material
//-----------------------------------------------------------------------------
class CSpyInvisProxy : public CEntityMaterialProxy
{
public:
CSpyInvisProxy( void );
virtual ~CSpyInvisProxy( void );
virtual bool Init( IMaterial *pMaterial, KeyValues* pKeyValues );
virtual void OnBind( C_BaseEntity *pC_BaseEntity );
virtual IMaterial * GetMaterial();
private:
IMaterialVar *m_pPercentInvisible;
IMaterialVar *m_pCloakColorTint;
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CSpyInvisProxy::CSpyInvisProxy( void )
{
m_pPercentInvisible = NULL;
m_pCloakColorTint = NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CSpyInvisProxy::~CSpyInvisProxy( void )
{
}
//-----------------------------------------------------------------------------
// Purpose: Get pointer to the color value
// Input : *pMaterial -
//-----------------------------------------------------------------------------
bool CSpyInvisProxy::Init( IMaterial *pMaterial, KeyValues* pKeyValues )
{
Assert( pMaterial );
// Need to get the material var
bool bInvis;
m_pPercentInvisible = pMaterial->FindVar( "$cloakfactor", &bInvis );
bool bTint;
m_pCloakColorTint = pMaterial->FindVar( "$cloakColorTint", &bTint );
return ( bInvis && bTint );
}
ConVar tf_teammate_max_invis( "tf_teammate_max_invis", "0.95", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
//-----------------------------------------------------------------------------
// Purpose:
// Input :
//-----------------------------------------------------------------------------
void CSpyInvisProxy::OnBind( C_BaseEntity *pEnt )
{
if( !m_pPercentInvisible || !m_pCloakColorTint )
return;
if ( !pEnt )
return;
C_TFPlayer *pPlayer = ToTFPlayer( pEnt );
if ( !pPlayer )
{
m_pPercentInvisible->SetFloatValue( 0.0 );
return;
}
m_pPercentInvisible->SetFloatValue( pPlayer->GetEffectiveInvisibilityLevel() );
float r, g, b;
switch( pPlayer->GetTeamNumber() )
{
case TF_TEAM_RED:
r = 1.0; g = 0.5; b = 0.4;
break;
case TF_TEAM_BLUE:
default:
r = 0.4; g = 0.5; b = 1.0;
break;
}
m_pCloakColorTint->SetVecValue( r, g, b );
}
IMaterial *CSpyInvisProxy::GetMaterial()
{
if ( !m_pPercentInvisible )
return NULL;
return m_pPercentInvisible->GetOwningMaterial();
}
EXPOSE_INTERFACE( CSpyInvisProxy, IMaterialProxy, "spy_invis" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: Used for invulnerability material
// Returns 1 if the player is invulnerable, and 0 if the player is losing / doesn't have invuln.
//-----------------------------------------------------------------------------
class CProxyInvulnLevel : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
C_TFPlayer *pPlayer = NULL;
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
if ( !pEntity )
return;
if ( pEntity->IsPlayer() )
{
pPlayer = dynamic_cast< C_TFPlayer* >( pEntity );
}
else
{
// See if it's a weapon
C_TFWeaponBase *pWeapon = dynamic_cast< C_TFWeaponBase* >( pEntity );
if ( pWeapon )
{
pPlayer = (C_TFPlayer*)pWeapon->GetOwner();
}
else
{
C_BaseViewModel *pVM = dynamic_cast< C_BaseViewModel* >( pEntity );
if ( pVM )
{
pPlayer = (C_TFPlayer*)pVM->GetOwner();
}
}
}
if ( pPlayer )
{
if ( pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE ) && !pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) )
{
m_pResult->SetFloatValue( 1.0 );
}
else
{
m_pResult->SetFloatValue( 0.0 );
}
}
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
};
EXPOSE_INTERFACE( CProxyInvulnLevel, IMaterialProxy, "InvulnLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: Used for burning material on player models
// Returns 0.0->1.0 for level of burn to show on player skin
//-----------------------------------------------------------------------------
class CProxyBurnLevel : public CResultProxy
{
public:
void OnBind( void *pC_BaseEntity )
{
Assert( m_pResult );
if ( !pC_BaseEntity )
return;
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity );
if ( !pEntity )
return;
// default to zero
float flBurnStartTime = 0;
C_TFPlayer *pPlayer = dynamic_cast< C_TFPlayer* >( pEntity );
if ( pPlayer )
{
// is the player burning?
if ( pPlayer->m_Shared.InCond( TF_COND_BURNING ) )
{
flBurnStartTime = pPlayer->m_flBurnEffectStartTime;
}
}
else
{
// is the ragdoll burning?
C_TFRagdoll *pRagDoll = dynamic_cast< C_TFRagdoll* >( pEntity );
if ( pRagDoll )
{
flBurnStartTime = pRagDoll->GetBurnStartTime();
}
}
float flResult = 0.0;
// if player/ragdoll is burning, set the burn level on the skin
if ( flBurnStartTime > 0 )
{
float flBurnPeakTime = flBurnStartTime + 0.3;
float flTempResult;
if ( gpGlobals->curtime < flBurnPeakTime )
{
// fade in from 0->1 in 0.3 seconds
flTempResult = RemapValClamped( gpGlobals->curtime, flBurnStartTime, flBurnPeakTime, 0.0, 1.0 );
}
else
{
// fade out from 1->0 in the remaining time until flame extinguished
flTempResult = RemapValClamped( gpGlobals->curtime, flBurnPeakTime, flBurnStartTime + TF_BURNING_FLAME_LIFE, 1.0, 0.0 );
}
// We have to do some more calc here instead of in materialvars.
flResult = 1.0 - abs( flTempResult - 1.0 );
}
m_pResult->SetFloatValue( flResult );
if ( ToolsEnabled() )
{
ToolFramework_RecordMaterialParams( GetMaterial() );
}
}
};
EXPOSE_INTERFACE( CProxyBurnLevel, IMaterialProxy, "BurnLevel" IMATERIAL_PROXY_INTERFACE_VERSION );
//-----------------------------------------------------------------------------
// Purpose: RecvProxy that converts the Player's object UtlVector to entindexes
//-----------------------------------------------------------------------------
void RecvProxy_PlayerObjectList( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
C_TFPlayer *pPlayer = (C_TFPlayer*)pStruct;
CBaseHandle *pHandle = (CBaseHandle*)(&(pPlayer->m_aObjects[pData->m_iElement]));
RecvProxy_IntToEHandle( pData, pStruct, pHandle );
}
void RecvProxyArrayLength_PlayerObjects( void *pStruct, int objectID, int currentArrayLength )
{
C_TFPlayer *pPlayer = (C_TFPlayer*)pStruct;
if ( pPlayer->m_aObjects.Count() != currentArrayLength )
{
pPlayer->m_aObjects.SetSize( currentArrayLength );
}
pPlayer->ForceUpdateObjectHudState();
}
// specific to the local player
BEGIN_RECV_TABLE_NOBASE( C_TFPlayer, DT_TFLocalPlayerExclusive )
RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ),
RecvPropArray2(
RecvProxyArrayLength_PlayerObjects,
RecvPropInt( "player_object_array_element", 0, SIZEOF_IGNORE, 0, RecvProxy_PlayerObjectList ),
MAX_OBJECTS_PER_PLAYER,
0,
"player_object_array" ),
RecvPropFloat( RECVINFO( m_angEyeAngles[0] ) ),
// RecvPropFloat( RECVINFO( m_angEyeAngles[1] ) ),
END_RECV_TABLE()
// all players except the local player
BEGIN_RECV_TABLE_NOBASE( C_TFPlayer, DT_TFNonLocalPlayerExclusive )
RecvPropVector( RECVINFO_NAME( m_vecNetworkOrigin, m_vecOrigin ) ),
RecvPropFloat( RECVINFO( m_angEyeAngles[0] ) ),
RecvPropFloat( RECVINFO( m_angEyeAngles[1] ) ),
END_RECV_TABLE()
IMPLEMENT_CLIENTCLASS_DT( C_TFPlayer, DT_TFPlayer, CTFPlayer )
RecvPropBool(RECVINFO(m_bSaveMeParity)),
// This will create a race condition will the local player, but the data will be the same so.....
RecvPropInt( RECVINFO( m_nWaterLevel ) ),
RecvPropEHandle( RECVINFO( m_hRagdoll ) ),
RecvPropDataTable( RECVINFO_DT( m_PlayerClass ), 0, &REFERENCE_RECV_TABLE( DT_TFPlayerClassShared ) ),
RecvPropDataTable( RECVINFO_DT( m_Shared ), 0, &REFERENCE_RECV_TABLE( DT_TFPlayerShared ) ),
RecvPropEHandle( RECVINFO(m_hItem ) ),
RecvPropDataTable( "tflocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_TFLocalPlayerExclusive) ),
RecvPropDataTable( "tfnonlocaldata", 0, 0, &REFERENCE_RECV_TABLE(DT_TFNonLocalPlayerExclusive) ),
RecvPropInt( RECVINFO( m_iSpawnCounter ) ),
END_RECV_TABLE()
BEGIN_PREDICTION_DATA( C_TFPlayer )
DEFINE_PRED_TYPEDESCRIPTION( m_Shared, CTFPlayerShared ),
DEFINE_PRED_FIELD( m_nSkin, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE ),
DEFINE_PRED_FIELD( m_nBody, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE ),
DEFINE_PRED_FIELD( m_nSequence, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
DEFINE_PRED_FIELD( m_flPlaybackRate, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
DEFINE_PRED_FIELD( m_flCycle, FIELD_FLOAT, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
DEFINE_PRED_ARRAY_TOL( m_flEncodedController, FIELD_FLOAT, MAXSTUDIOBONECTRLS, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE, 0.02f ),
DEFINE_PRED_FIELD( m_nNewSequenceParity, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
DEFINE_PRED_FIELD( m_nResetEventsParity, FIELD_INTEGER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE | FTYPEDESC_NOERRORCHECK ),
DEFINE_PRED_FIELD( m_nMuzzleFlashParity, FIELD_CHARACTER, FTYPEDESC_OVERRIDE | FTYPEDESC_PRIVATE ),
DEFINE_PRED_FIELD( m_hOffHandWeapon, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
END_PREDICTION_DATA()
// ------------------------------------------------------------------------------------------ //
// C_TFPlayer implementation.
// ------------------------------------------------------------------------------------------ //
C_TFPlayer::C_TFPlayer() :
m_iv_angEyeAngles( "C_TFPlayer::m_iv_angEyeAngles" )
{
m_PlayerAnimState = CreateTFPlayerAnimState( this );
m_Shared.Init( this );
m_iIDEntIndex = 0;
AddVar( &m_angEyeAngles, &m_iv_angEyeAngles, LATCH_SIMULATION_VAR );
m_pTeleporterEffect = NULL;
m_pBurningSound = NULL;
m_pBurningEffect = NULL;
m_flBurnEffectStartTime = 0;
m_flBurnEffectEndTime = 0;
m_pDisguisingEffect = NULL;
m_pSaveMeEffect = NULL;
m_aGibs.Purge();
m_bCigaretteSmokeActive = false;
m_hRagdoll.Set( NULL );
m_iPreviousMetal = 0;
m_bIsDisplayingNemesisIcon = false;
m_bWasTaunting = false;
m_angTauntPredViewAngles.Init();
m_angTauntEngViewAngles.Init();
m_flWaterImpactTime = 0.0f;
m_flWaterEntryTime = 0;
m_nOldWaterLevel = WL_NotInWater;
m_bWaterExitEffectActive = false;
m_bUpdateObjectHudState = false;
}
C_TFPlayer::~C_TFPlayer()
{
ShowNemesisIcon( false );
m_PlayerAnimState->Release();
}
C_TFPlayer* C_TFPlayer::GetLocalTFPlayer()
{
return ToTFPlayer( C_BasePlayer::GetLocalPlayer() );
}
const QAngle& C_TFPlayer::GetRenderAngles()
{
if ( IsRagdoll() )
{
return vec3_angle;
}
else
{
return m_PlayerAnimState->GetRenderAngles();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateOnRemove( void )
{
// Stop the taunt.
if ( m_bWasTaunting )
{
TurnOffTauntCam();
}
// HACK!!! ChrisG needs to fix this in the particle system.
ParticleProp()->OwnerSetDormantTo( true );
ParticleProp()->StopParticlesInvolving( this );
m_Shared.RemoveAllCond( this );
if ( IsLocalPlayer() )
{
CTFStatPanel *pStatPanel = GetStatPanel();
pStatPanel->OnLocalPlayerRemove( this );
}
BaseClass::UpdateOnRemove();
}
//-----------------------------------------------------------------------------
// Purpose: returns max health for this player
//-----------------------------------------------------------------------------
int C_TFPlayer::GetMaxHealth( void ) const
{
if ( g_PR )
{
C_TF_PlayerResource *tf_PR = dynamic_cast<C_TF_PlayerResource *>(g_PR);
if ( tf_PR )
{
int index = ( (C_BasePlayer *) this )->entindex();
return tf_PR->GetMaxHealth( index );
}
}
return 1;
}
//-----------------------------------------------------------------------------
// Deal with recording
//-----------------------------------------------------------------------------
void C_TFPlayer::GetToolRecordingState( KeyValues *msg )
{
#ifndef _XBOX
BaseClass::GetToolRecordingState( msg );
BaseEntityRecordingState_t *pBaseEntityState = (BaseEntityRecordingState_t*)msg->GetPtr( "baseentity" );
bool bDormant = IsDormant();
bool bDead = !IsAlive();
bool bSpectator = ( GetTeamNumber() == TEAM_SPECTATOR );
bool bNoRender = ( GetRenderMode() == kRenderNone );
bool bDeathCam = (GetObserverMode() == OBS_MODE_DEATHCAM);
bool bNoDraw = IsEffectActive(EF_NODRAW);
bool bVisible =
!bDormant &&
!bDead &&
!bSpectator &&
!bNoRender &&
!bDeathCam &&
!bNoDraw;
bool changed = m_bToolRecordingVisibility != bVisible;
// Remember state
m_bToolRecordingVisibility = bVisible;
pBaseEntityState->m_bVisible = bVisible;
if ( changed && !bVisible )
{
// If the entity becomes invisible this frame, we still want to record a final animation sample so that we have data to interpolate
// toward just before the logs return "false" for visiblity. Otherwise the animation will freeze on the last frame while the model
// is still able to render for just a bit.
pBaseEntityState->m_bRecordFinalVisibleSample = true;
}
#endif
}
void C_TFPlayer::UpdateClientSideAnimation()
{
// Update the animation data. It does the local check here so this works when using
// a third-person camera (and we don't have valid player angles).
if ( this == C_TFPlayer::GetLocalTFPlayer() )
m_PlayerAnimState->Update( EyeAngles()[YAW], m_angEyeAngles[PITCH] );
else
m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] );
BaseClass::UpdateClientSideAnimation();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::SetDormant( bool bDormant )
{
// If I'm burning, stop the burning sounds
if ( !IsDormant() && bDormant )
{
if ( m_pBurningSound)
{
StopBurningSound();
}
if ( m_bIsDisplayingNemesisIcon )
{
ShowNemesisIcon( false );
}
}
if ( IsDormant() && !bDormant )
{
m_bUpdatePartyHat = true;
}
// Deliberately skip base combat weapon
C_BaseEntity::SetDormant( bDormant );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::OnPreDataChanged( DataUpdateType_t updateType )
{
BaseClass::OnPreDataChanged( updateType );
m_iOldHealth = m_iHealth;
m_iOldPlayerClass = m_PlayerClass.GetClassIndex();
m_iOldState = m_Shared.GetCond();
m_iOldSpawnCounter = m_iSpawnCounter;
m_bOldSaveMeParity = m_bSaveMeParity;
m_nOldWaterLevel = GetWaterLevel();
m_iOldTeam = GetTeamNumber();
C_TFPlayerClass *pClass = GetPlayerClass();
m_iOldClass = pClass ? pClass->GetClassIndex() : TF_CLASS_UNDEFINED;
m_bDisguised = m_Shared.InCond( TF_COND_DISGUISED );
m_iOldDisguiseTeam = m_Shared.GetDisguiseTeam();
m_iOldDisguiseClass = m_Shared.GetDisguiseClass();
m_Shared.OnPreDataChanged();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::OnDataChanged( DataUpdateType_t updateType )
{
// C_BaseEntity assumes we're networking the entity's angles, so pretend that it
// networked the same value we already have.
SetNetworkAngles( GetLocalAngles() );
BaseClass::OnDataChanged( updateType );
if ( updateType == DATA_UPDATE_CREATED )
{
SetNextClientThink( CLIENT_THINK_ALWAYS );
InitInvulnerableMaterial();
}
else
{
if ( m_iOldTeam != GetTeamNumber() || m_iOldDisguiseTeam != m_Shared.GetDisguiseTeam() )
{
InitInvulnerableMaterial();
m_bUpdatePartyHat = true;
}
}
UpdateVisibility();
// Check for full health and remove decals.
if ( ( m_iHealth > m_iOldHealth && m_iHealth >= GetMaxHealth() ) || m_Shared.InCond( TF_COND_INVULNERABLE ) )
{
// If we were just fully healed, remove all decals
RemoveAllDecals();
}
// Detect class changes
if ( m_iOldPlayerClass != m_PlayerClass.GetClassIndex() )
{
OnPlayerClassChange();
}
bool bJustSpawned = false;
if ( m_iOldSpawnCounter != m_iSpawnCounter )
{
ClientPlayerRespawn();
bJustSpawned = true;
m_bUpdatePartyHat = true;
}
if ( m_bSaveMeParity != m_bOldSaveMeParity )
{
// Player has triggered a save me command
CreateSaveMeEffect();
}
if ( m_Shared.InCond( TF_COND_BURNING ) && !m_pBurningSound )
{
StartBurningSound();
}
// See if we should show or hide nemesis icon for this player
bool bShouldDisplayNemesisIcon = ShouldShowNemesisIcon();
if ( bShouldDisplayNemesisIcon != m_bIsDisplayingNemesisIcon )
{
ShowNemesisIcon( bShouldDisplayNemesisIcon );
}
m_Shared.OnDataChanged();
if ( m_bDisguised != m_Shared.InCond( TF_COND_DISGUISED ) )
{
m_flDisguiseEndEffectStartTime = MAX( m_flDisguiseEndEffectStartTime, gpGlobals->curtime );
}
int nNewWaterLevel = GetWaterLevel();
if ( nNewWaterLevel != m_nOldWaterLevel )
{
if ( ( m_nOldWaterLevel == WL_NotInWater ) && ( nNewWaterLevel > WL_NotInWater ) )
{
// Set when we do a transition to/from partially in water to completely out
m_flWaterEntryTime = gpGlobals->curtime;
}
// If player is now up to his eyes in water and has entered the water very recently (not just bobbing eyes in and out), play a bubble effect.
if ( ( nNewWaterLevel == WL_Eyes ) && ( gpGlobals->curtime - m_flWaterEntryTime ) < 0.5f )
{
CNewParticleEffect *pEffect = ParticleProp()->Create( "water_playerdive", PATTACH_ABSORIGIN_FOLLOW );
ParticleProp()->AddControlPoint( pEffect, 1, NULL, PATTACH_WORLDORIGIN, NULL, WorldSpaceCenter() );
}
// If player was up to his eyes in water and is now out to waist level or less, play a water drip effect
else if ( m_nOldWaterLevel == WL_Eyes && ( nNewWaterLevel < WL_Eyes ) && !bJustSpawned )
{
CNewParticleEffect *pWaterExitEffect = ParticleProp()->Create( "water_playeremerge", PATTACH_ABSORIGIN_FOLLOW );
ParticleProp()->AddControlPoint( pWaterExitEffect, 1, this, PATTACH_ABSORIGIN_FOLLOW );
m_bWaterExitEffectActive = true;
}
}
if ( IsLocalPlayer() )
{
if ( updateType == DATA_UPDATE_CREATED )
{
SetupHeadLabelMaterials();
GetClientVoiceMgr()->SetHeadLabelOffset( 50 );
}
if ( m_iOldTeam != GetTeamNumber() )
{
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_changeteam" );
if ( event )
{
gameeventmanager->FireEventClientSide( event );
}
if ( IsX360() )
{
const char *pTeam = NULL;
switch( GetTeamNumber() )
{
case TF_TEAM_RED:
pTeam = "red";
break;
case TF_TEAM_BLUE:
pTeam = "blue";
break;
case TEAM_SPECTATOR:
pTeam = "spectate";
break;
}
if ( pTeam )
{
engine->ChangeTeam( pTeam );
}
}
}
if ( !IsPlayerClass(m_iOldClass) )
{
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_changeclass" );
if ( event )
{
event->SetInt( "updateType", updateType );
gameeventmanager->FireEventClientSide( event );
}
}
if ( m_iOldClass == TF_CLASS_SPY &&
( m_bDisguised != m_Shared.InCond( TF_COND_DISGUISED ) || m_iOldDisguiseClass != m_Shared.GetDisguiseClass() ) )
{
IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_changedisguise" );
if ( event )
{
event->SetBool( "disguised", m_Shared.InCond( TF_COND_DISGUISED ) );
gameeventmanager->FireEventClientSide( event );
}
}
// If our metal amount changed, send a game event
int iCurrentMetal = GetAmmoCount( TF_AMMO_METAL );
if ( iCurrentMetal != m_iPreviousMetal )
{
//msg
IGameEvent *event = gameeventmanager->CreateEvent( "player_account_changed" );
if ( event )
{
event->SetInt( "old_account", m_iPreviousMetal );
event->SetInt( "new_account", iCurrentMetal );
gameeventmanager->FireEventClientSide( event );
}
m_iPreviousMetal = iCurrentMetal;
}
}
// Some time in this network transmit we changed the size of the object array.
// recalc the whole thing and update the hud
if ( m_bUpdateObjectHudState )
{
IGameEvent *event = gameeventmanager->CreateEvent( "building_info_changed" );
if ( event )
{
event->SetInt( "building_type", -1 );
gameeventmanager->FireEventClientSide( event );
}
m_bUpdateObjectHudState = false;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::InitInvulnerableMaterial( void )
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pLocalPlayer )
return;
const char *pszMaterial = NULL;
int iVisibleTeam = GetTeamNumber();
// if this player is disguised and on the other team, use disguise team
if ( m_Shared.InCond( TF_COND_DISGUISED ) && !InSameTeam( pLocalPlayer ) )
{
iVisibleTeam = m_Shared.GetDisguiseTeam();
}
switch ( iVisibleTeam )
{
case TF_TEAM_BLUE:
pszMaterial = "models/effects/invulnfx_blue.vmt";
break;
case TF_TEAM_RED:
pszMaterial = "models/effects/invulnfx_red.vmt";
break;
default:
break;
}
if ( pszMaterial )
{
m_InvulnerableMaterial.Init( pszMaterial, TEXTURE_GROUP_CLIENT_EFFECTS );
}
else
{
m_InvulnerableMaterial.Shutdown();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::StartBurningSound( void )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
if ( !m_pBurningSound )
{
CLocalPlayerFilter filter;
m_pBurningSound = controller.SoundCreate( filter, entindex(), "Player.OnFire" );
}
controller.Play( m_pBurningSound, 0.0, 100 );
controller.SoundChangeVolume( m_pBurningSound, 1.0, 0.1 );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::StopBurningSound( void )
{
if ( m_pBurningSound )
{
CSoundEnvelopeController::GetController().SoundDestroy( m_pBurningSound );
m_pBurningSound = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::OnAddTeleported( void )
{
if ( !m_pTeleporterEffect )
{
char *pEffect = NULL;
switch( GetTeamNumber() )
{
case TF_TEAM_BLUE:
pEffect = "player_recent_teleport_blue";
break;
case TF_TEAM_RED:
pEffect = "player_recent_teleport_red";
break;
default:
break;
}
if ( pEffect )
{
m_pTeleporterEffect = ParticleProp()->Create( pEffect, PATTACH_ABSORIGIN_FOLLOW );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::OnRemoveTeleported( void )
{
if ( m_pTeleporterEffect )
{
ParticleProp()->StopEmission( m_pTeleporterEffect );
m_pTeleporterEffect = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::OnPlayerClassChange( void )
{
// Init the anim movement vars
m_PlayerAnimState->SetRunSpeed( GetPlayerClass()->GetMaxSpeed() );
m_PlayerAnimState->SetWalkSpeed( GetPlayerClass()->GetMaxSpeed() * 0.5 );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::InitPhonemeMappings()
{
CStudioHdr *pStudio = GetModelPtr();
if ( pStudio )
{
char szBasename[MAX_PATH];
Q_StripExtension( pStudio->pszName(), szBasename, sizeof( szBasename ) );
char szExpressionName[MAX_PATH];
Q_snprintf( szExpressionName, sizeof( szExpressionName ), "%s/phonemes/phonemes", szBasename );
if ( FindSceneFile( szExpressionName ) )
{
SetupMappings( szExpressionName );
}
else
{
BaseClass::InitPhonemeMappings();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::ResetFlexWeights( CStudioHdr *pStudioHdr )
{
if ( !pStudioHdr || pStudioHdr->numflexdesc() == 0 )
return;
// Reset the flex weights to their starting position.
LocalFlexController_t iController;
for ( iController = LocalFlexController_t(0); iController < pStudioHdr->numflexcontrollers(); ++iController )
{
SetFlexWeight( iController, 0.0f );
}
// Reset the prediction interpolation values.
m_iv_flexWeight.Reset();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CStudioHdr *C_TFPlayer::OnNewModel( void )
{
CStudioHdr *hdr = BaseClass::OnNewModel();
// Initialize the gibs.
InitPlayerGibs();
InitializePoseParams();
// Init flexes, cancel any scenes we're playing
ClearSceneEvents( NULL, false );
// Reset the flex weights.
ResetFlexWeights( hdr );
// Reset the players animation states, gestures
if ( m_PlayerAnimState )
{
m_PlayerAnimState->OnNewModel();
}
if ( hdr )
{
InitPhonemeMappings();
}
if ( IsPlayerClass( TF_CLASS_SPY ) )
{
m_iSpyMaskBodygroup = FindBodygroupByName( "spyMask" );
}
else
{
m_iSpyMaskBodygroup = -1;
}
m_bUpdatePartyHat = true;
return hdr;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdatePartyHat( void )
{
if ( TFGameRules() && TFGameRules()->IsBirthday() && !IsLocalPlayer() && IsAlive() &&
GetTeamNumber() >= FIRST_GAME_TEAM && !IsPlayerClass(TF_CLASS_UNDEFINED) )
{
if ( m_hPartyHat )
{
m_hPartyHat->Release();
}
m_hPartyHat = C_PlayerAttachedModel::Create( BDAY_HAT_MODEL, this, LookupAttachment("partyhat"), vec3_origin, PAM_PERMANENT, 0 );
// C_PlayerAttachedModel::Create can return NULL!
if ( m_hPartyHat )
{
int iVisibleTeam = GetTeamNumber();
if ( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() )
{
iVisibleTeam = m_Shared.GetDisguiseTeam();
}
m_hPartyHat->m_nSkin = iVisibleTeam - 2;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Is this player an enemy to the local player
//-----------------------------------------------------------------------------
bool C_TFPlayer::IsEnemyPlayer( void )
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( !pLocalPlayer )
return false;
switch( pLocalPlayer->GetTeamNumber() )
{
case TF_TEAM_RED:
return ( GetTeamNumber() == TF_TEAM_BLUE );
case TF_TEAM_BLUE:
return ( GetTeamNumber() == TF_TEAM_RED );
default:
break;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Displays a nemesis icon on this player to the local player
//-----------------------------------------------------------------------------
void C_TFPlayer::ShowNemesisIcon( bool bShow )
{
if ( bShow )
{
const char *pszEffect = NULL;
switch ( GetTeamNumber() )
{
case TF_TEAM_RED:
pszEffect = "particle_nemesis_red";
break;
case TF_TEAM_BLUE:
pszEffect = "particle_nemesis_blue";
break;
default:
return; // shouldn't get called if we're not on a team; bail out if it does
}
ParticleProp()->Create( pszEffect, PATTACH_POINT_FOLLOW, "head" );
}
else
{
// stop effects for both team colors (to make sure we remove effects in event of team change)
ParticleProp()->StopParticlesNamed( "particle_nemesis_red", true );
ParticleProp()->StopParticlesNamed( "particle_nemesis_blue", true );
}
m_bIsDisplayingNemesisIcon = bShow;
}
#define TF_TAUNT_PITCH 0
#define TF_TAUNT_YAW 1
#define TF_TAUNT_DIST 2
#define TF_TAUNT_MAXYAW 135
#define TF_TAUNT_MINYAW -135
#define TF_TAUNT_MAXPITCH 90
#define TF_TAUNT_MINPITCH 0
#define TF_TAUNT_IDEALLAG 4.0f
static Vector TF_TAUNTCAM_HULL_MIN( -9.0f, -9.0f, -9.0f );
static Vector TF_TAUNTCAM_HULL_MAX( 9.0f, 9.0f, 9.0f );
static ConVar tf_tauntcam_yaw( "tf_tauntcam_yaw", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
static ConVar tf_tauntcam_pitch( "tf_tauntcam_pitch", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
static ConVar tf_tauntcam_dist( "tf_tauntcam_dist", "110", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::TurnOnTauntCam( void )
{
if ( !IsLocalPlayer() )
return;
// Save the old view angles.
engine->GetViewAngles( m_angTauntEngViewAngles );
prediction->GetViewAngles( m_angTauntPredViewAngles );
m_TauntCameraData.m_flPitch = tf_tauntcam_pitch.GetFloat();
m_TauntCameraData.m_flYaw = tf_tauntcam_yaw.GetFloat();
m_TauntCameraData.m_flDist = tf_tauntcam_dist.GetFloat();
m_TauntCameraData.m_flLag = 4.0f;
m_TauntCameraData.m_vecHullMin.Init( -9.0f, -9.0f, -9.0f );
m_TauntCameraData.m_vecHullMax.Init( 9.0f, 9.0f, 9.0f );
QAngle vecCameraOffset( tf_tauntcam_pitch.GetFloat(), tf_tauntcam_yaw.GetFloat(), tf_tauntcam_dist.GetFloat() );
g_ThirdPersonManager.SetDesiredCameraOffset( Vector( tf_tauntcam_dist.GetFloat(), 0.0f, 0.0f ) );
g_ThirdPersonManager.SetOverridingThirdPerson( true );
::input->CAM_ToThirdPerson();
ThirdPersonSwitch( true );
::input->CAM_SetCameraThirdData( &m_TauntCameraData, vecCameraOffset );
if ( m_hItem )
{
m_hItem->UpdateVisibility();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::TurnOffTauntCam( void )
{
if ( !IsLocalPlayer() )
return;
Vector vecOffset = g_ThirdPersonManager.GetCameraOffsetAngles();
tf_tauntcam_pitch.SetValue( vecOffset[PITCH] - m_angTauntPredViewAngles[PITCH] );
tf_tauntcam_yaw.SetValue( vecOffset[YAW] - m_angTauntPredViewAngles[YAW] );
g_ThirdPersonManager.SetDesiredCameraOffset( vec3_origin );
g_ThirdPersonManager.SetOverridingThirdPerson( false );
::input->CAM_ToFirstPerson();
ThirdPersonSwitch( false );
::input->CAM_SetCameraThirdData( NULL, vec3_angle );
// Reset the old view angles.
engine->SetViewAngles( m_angTauntEngViewAngles );
prediction->SetViewAngles( m_angTauntPredViewAngles );
// Force the feet to line up with the view direction post taunt.
m_PlayerAnimState->m_bForceAimYaw = true;
if ( GetViewModel() )
{
GetViewModel()->UpdateVisibility();
}
if ( m_hItem )
{
m_hItem->UpdateVisibility();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::HandleTaunting( void )
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
// Clear the taunt slot.
if ( !m_bWasTaunting && m_Shared.InCond( TF_COND_TAUNTING ) )
{
m_bWasTaunting = true;
// Handle the camera for the local player.
if ( pLocalPlayer )
{
TurnOnTauntCam();
}
}
if ( m_bWasTaunting && !m_Shared.InCond( TF_COND_TAUNTING ) )
{
m_bWasTaunting = false;
// Clear the vcd slot.
m_PlayerAnimState->ResetGestureSlot( GESTURE_SLOT_VCD );
// Handle the camera for the local player.
if ( pLocalPlayer )
{
TurnOffTauntCam();
}
}
}
void C_TFPlayer::ClientThink()
{
// Pass on through to the base class.
BaseClass::ClientThink();
UpdateIDTarget();
UpdateLookAt();
// Handle invisibility.
m_Shared.InvisibilityThink();
m_Shared.ConditionThink();
// Clear our healer, it'll be reset by the medigun client think if we're being healed
m_hHealer = NULL;
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
// Ugh, this check is getting ugly
// Start smoke if we're not invisible or disguised
if ( IsPlayerClass( TF_CLASS_SPY ) && IsAlive() && // only on spy model
( !m_Shared.InCond( TF_COND_DISGUISED ) || !IsEnemyPlayer() ) && // disguise doesn't show for teammates
GetPercentInvisible() <= 0 && // don't start if invis
( pLocalPlayer != this ) && // don't show to local player
!( pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE && pLocalPlayer->GetObserverTarget() == this ) ) // not if we're spectating this player first person
{
if ( !m_bCigaretteSmokeActive )
{
int iSmokeAttachment = LookupAttachment( "cig_smoke" );
ParticleProp()->Create( "cig_smoke", PATTACH_POINT_FOLLOW, iSmokeAttachment );
m_bCigaretteSmokeActive = true;
}
}
else // stop the smoke otherwise if its active
{
if ( m_bCigaretteSmokeActive )
{
ParticleProp()->StopParticlesNamed( "cig_smoke", false );
m_bCigaretteSmokeActive = false;
}
}
if ( m_bWaterExitEffectActive && !IsAlive() )
{
ParticleProp()->StopParticlesNamed( "water_playeremerge", false );
m_bWaterExitEffectActive = false;
}
if ( m_bUpdatePartyHat )
{
UpdatePartyHat();
m_bUpdatePartyHat = false;
}
if ( m_pSaveMeEffect )
{
// Kill the effect if either
// a) the player is dead
// b) the enemy disguised spy is now invisible
if ( !IsAlive() ||
( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() && ( GetPercentInvisible() > 0 ) ) )
{
ParticleProp()->StopEmissionAndDestroyImmediately( m_pSaveMeEffect );
m_pSaveMeEffect = NULL;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateLookAt( void )
{
bool bFoundViewTarget = false;
Vector vForward;
AngleVectors( GetLocalAngles(), &vForward );
Vector vMyOrigin = GetAbsOrigin();
Vector vecLookAtTarget = vec3_origin;
for( int iClient = 1; iClient <= gpGlobals->maxClients; ++iClient )
{
CBaseEntity *pEnt = UTIL_PlayerByIndex( iClient );
if ( !pEnt || !pEnt->IsPlayer() )
continue;
if ( !pEnt->IsAlive() )
continue;
if ( pEnt == this )
continue;
Vector vDir = pEnt->GetAbsOrigin() - vMyOrigin;
if ( vDir.Length() > 300 )
continue;
VectorNormalize( vDir );
if ( DotProduct( vForward, vDir ) < 0.0f )
continue;
vecLookAtTarget = pEnt->EyePosition();
bFoundViewTarget = true;
break;
}
if ( bFoundViewTarget == false )
{
// no target, look forward
vecLookAtTarget = GetAbsOrigin() + vForward * 512;
}
// orient eyes
m_viewtarget = vecLookAtTarget;
/*
// blinking
if (m_blinkTimer.IsElapsed())
{
m_blinktoggle = !m_blinktoggle;
m_blinkTimer.Start( RandomFloat( 1.5f, 4.0f ) );
}
*/
/*
// Figure out where we want to look in world space.
QAngle desiredAngles;
Vector to = vecLookAtTarget - EyePosition();
VectorAngles( to, desiredAngles );
// Figure out where our body is facing in world space.
QAngle bodyAngles( 0, 0, 0 );
bodyAngles[YAW] = GetLocalAngles()[YAW];
float flBodyYawDiff = bodyAngles[YAW] - m_flLastBodyYaw;
m_flLastBodyYaw = bodyAngles[YAW];
// Set the head's yaw.
float desired = AngleNormalize( desiredAngles[YAW] - bodyAngles[YAW] );
desired = clamp( -desired, m_headYawMin, m_headYawMax );
m_flCurrentHeadYaw = ApproachAngle( desired, m_flCurrentHeadYaw, 130 * gpGlobals->frametime );
// Counterrotate the head from the body rotation so it doesn't rotate past its target.
m_flCurrentHeadYaw = AngleNormalize( m_flCurrentHeadYaw - flBodyYawDiff );
SetPoseParameter( m_headYawPoseParam, m_flCurrentHeadYaw );
// Set the head's yaw.
desired = AngleNormalize( desiredAngles[PITCH] );
desired = clamp( desired, m_headPitchMin, m_headPitchMax );
m_flCurrentHeadPitch = ApproachAngle( -desired, m_flCurrentHeadPitch, 130 * gpGlobals->frametime );
m_flCurrentHeadPitch = AngleNormalize( m_flCurrentHeadPitch );
SetPoseParameter( m_headPitchPoseParam, m_flCurrentHeadPitch );
*/
}
//-----------------------------------------------------------------------------
// Purpose: Try to steer away from any players and objects we might interpenetrate
//-----------------------------------------------------------------------------
#define TF_AVOID_MAX_RADIUS_SQR 5184.0f // Based on player extents and max buildable extents.
#define TF_OO_AVOID_MAX_RADIUS_SQR 0.00019f
ConVar tf_max_separation_force ( "tf_max_separation_force", "256", FCVAR_DEVELOPMENTONLY );
extern ConVar cl_forwardspeed;
extern ConVar cl_backspeed;
extern ConVar cl_sidespeed;
void C_TFPlayer::AvoidPlayers( CUserCmd *pCmd )
{
// Turn off the avoid player code.
if ( !tf_avoidteammates.GetBool() )
return;
// Don't test if the player doesn't exist or is dead.
if ( IsAlive() == false )
return;
C_Team *pTeam = ( C_Team * )GetTeam();
if ( !pTeam )
return;
// Up vector.
static Vector vecUp( 0.0f, 0.0f, 1.0f );
Vector vecTFPlayerCenter = GetAbsOrigin();
Vector vecTFPlayerMin = GetPlayerMins();
Vector vecTFPlayerMax = GetPlayerMaxs();
float flZHeight = vecTFPlayerMax.z - vecTFPlayerMin.z;
vecTFPlayerCenter.z += 0.5f * flZHeight;
VectorAdd( vecTFPlayerMin, vecTFPlayerCenter, vecTFPlayerMin );
VectorAdd( vecTFPlayerMax, vecTFPlayerCenter, vecTFPlayerMax );
// Find an intersecting player or object.
int nAvoidPlayerCount = 0;
C_TFPlayer *pAvoidPlayerList[MAX_PLAYERS];
C_TFPlayer *pIntersectPlayer = NULL;
CBaseObject *pIntersectObject = NULL;
float flAvoidRadius = 0.0f;
Vector vecAvoidCenter, vecAvoidMin, vecAvoidMax;
for ( int i = 0; i < pTeam->GetNumPlayers(); ++i )
{
C_TFPlayer *pAvoidPlayer = static_cast< C_TFPlayer * >( pTeam->GetPlayer( i ) );
if ( pAvoidPlayer == NULL )
continue;
// Is the avoid player me?
if ( pAvoidPlayer == this )
continue;
// Save as list to check against for objects.
pAvoidPlayerList[nAvoidPlayerCount] = pAvoidPlayer;
++nAvoidPlayerCount;
// Check to see if the avoid player is dormant.
if ( pAvoidPlayer->IsDormant() )
continue;
// Is the avoid player solid?
if ( pAvoidPlayer->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
continue;
Vector t1, t2;
vecAvoidCenter = pAvoidPlayer->GetAbsOrigin();
vecAvoidMin = pAvoidPlayer->GetPlayerMins();
vecAvoidMax = pAvoidPlayer->GetPlayerMaxs();
flZHeight = vecAvoidMax.z - vecAvoidMin.z;
vecAvoidCenter.z += 0.5f * flZHeight;
VectorAdd( vecAvoidMin, vecAvoidCenter, vecAvoidMin );
VectorAdd( vecAvoidMax, vecAvoidCenter, vecAvoidMax );
if ( IsBoxIntersectingBox( vecTFPlayerMin, vecTFPlayerMax, vecAvoidMin, vecAvoidMax ) )
{
// Need to avoid this player.
if ( !pIntersectPlayer )
{
pIntersectPlayer = pAvoidPlayer;
break;
}
}
}
// We didn't find a player - look for objects to avoid.
if ( !pIntersectPlayer )
{
for ( int iPlayer = 0; iPlayer < nAvoidPlayerCount; ++iPlayer )
{
// Stop when we found an intersecting object.
if ( pIntersectObject )
break;
C_TFTeam *pTeam = (C_TFTeam*)GetTeam();
for ( int iObject = 0; iObject < pTeam->GetNumObjects(); ++iObject )
{
CBaseObject *pAvoidObject = pTeam->GetObject( iObject );
if ( !pAvoidObject )
continue;
// Check to see if the object is dormant.
if ( pAvoidObject->IsDormant() )
continue;
// Is the object solid.
if ( pAvoidObject->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
continue;
// If we shouldn't avoid it, see if we intersect it.
if ( pAvoidObject->ShouldPlayersAvoid() )
{
vecAvoidCenter = pAvoidObject->WorldSpaceCenter();
vecAvoidMin = pAvoidObject->WorldAlignMins();
vecAvoidMax = pAvoidObject->WorldAlignMaxs();
VectorAdd( vecAvoidMin, vecAvoidCenter, vecAvoidMin );
VectorAdd( vecAvoidMax, vecAvoidCenter, vecAvoidMax );
if ( IsBoxIntersectingBox( vecTFPlayerMin, vecTFPlayerMax, vecAvoidMin, vecAvoidMax ) )
{
// Need to avoid this object.
pIntersectObject = pAvoidObject;
break;
}
}
}
}
}
// Anything to avoid?
if ( !pIntersectPlayer && !pIntersectObject )
{
m_Shared.SetSeparation( false );
m_Shared.SetSeparationVelocity( vec3_origin );
return;
}
// Calculate the push strength and direction.
Vector vecDelta;
// Avoid a player - they have precedence.
if ( pIntersectPlayer )
{
VectorSubtract( pIntersectPlayer->WorldSpaceCenter(), vecTFPlayerCenter, vecDelta );
Vector vRad = pIntersectPlayer->WorldAlignMaxs() - pIntersectPlayer->WorldAlignMins();
vRad.z = 0;
flAvoidRadius = vRad.Length();
}
// Avoid a object.
else
{
VectorSubtract( pIntersectObject->WorldSpaceCenter(), vecTFPlayerCenter, vecDelta );
Vector vRad = pIntersectObject->WorldAlignMaxs() - pIntersectObject->WorldAlignMins();
vRad.z = 0;
flAvoidRadius = vRad.Length();
}
float flPushStrength = RemapValClamped( vecDelta.Length(), flAvoidRadius, 0, 0, tf_max_separation_force.GetInt() ); //flPushScale;
//Msg( "PushScale = %f\n", flPushStrength );
// Check to see if we have enough push strength to make a difference.
if ( flPushStrength < 0.01f )
return;
Vector vecPush;
if ( GetAbsVelocity().Length2DSqr() > 0.1f )
{
Vector vecVelocity = GetAbsVelocity();
vecVelocity.z = 0.0f;
CrossProduct( vecUp, vecVelocity, vecPush );
VectorNormalize( vecPush );
}
else
{
// We are not moving, but we're still intersecting.
QAngle angView = pCmd->viewangles;
angView.x = 0.0f;
AngleVectors( angView, NULL, &vecPush, NULL );
}
// Move away from the other player/object.
Vector vecSeparationVelocity;
if ( vecDelta.Dot( vecPush ) < 0 )
{
vecSeparationVelocity = vecPush * flPushStrength;
}
else
{
vecSeparationVelocity = vecPush * -flPushStrength;
}
// Don't allow the max push speed to be greater than the max player speed.
float flMaxPlayerSpeed = MaxSpeed();
float flCropFraction = 1.33333333f;
if ( ( GetFlags() & FL_DUCKING ) && ( GetGroundEntity() != NULL ) )
{
flMaxPlayerSpeed *= flCropFraction;
}
float flMaxPlayerSpeedSqr = flMaxPlayerSpeed * flMaxPlayerSpeed;
if ( vecSeparationVelocity.LengthSqr() > flMaxPlayerSpeedSqr )
{
vecSeparationVelocity.NormalizeInPlace();
VectorScale( vecSeparationVelocity, flMaxPlayerSpeed, vecSeparationVelocity );
}
QAngle vAngles = pCmd->viewangles;
vAngles.x = 0;
Vector currentdir;
Vector rightdir;
AngleVectors( vAngles, &currentdir, &rightdir, NULL );
Vector vDirection = vecSeparationVelocity;
VectorNormalize( vDirection );
float fwd = currentdir.Dot( vDirection );
float rt = rightdir.Dot( vDirection );
float forward = fwd * flPushStrength;
float side = rt * flPushStrength;
//Msg( "fwd: %f - rt: %f - forward: %f - side: %f\n", fwd, rt, forward, side );
m_Shared.SetSeparation( true );
m_Shared.SetSeparationVelocity( vecSeparationVelocity );
pCmd->forwardmove += forward;
pCmd->sidemove += side;
// Clamp the move to within legal limits, preserving direction. This is a little
// complicated because we have different limits for forward, back, and side
//Msg( "PRECLAMP: forwardmove=%f, sidemove=%f\n", pCmd->forwardmove, pCmd->sidemove );
float flForwardScale = 1.0f;
if ( pCmd->forwardmove > fabs( cl_forwardspeed.GetFloat() ) )
{
flForwardScale = fabs( cl_forwardspeed.GetFloat() ) / pCmd->forwardmove;
}
else if ( pCmd->forwardmove < -fabs( cl_backspeed.GetFloat() ) )
{
flForwardScale = fabs( cl_backspeed.GetFloat() ) / fabs( pCmd->forwardmove );
}
float flSideScale = 1.0f;
if ( fabs( pCmd->sidemove ) > fabs( cl_sidespeed.GetFloat() ) )
{
flSideScale = fabs( cl_sidespeed.GetFloat() ) / fabs( pCmd->sidemove );
}
float flScale = MIN( flForwardScale, flSideScale );
pCmd->forwardmove *= flScale;
pCmd->sidemove *= flScale;
//Msg( "Pforwardmove=%f, sidemove=%f\n", pCmd->forwardmove, pCmd->sidemove );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : flInputSampleTime -
// *pCmd -
//-----------------------------------------------------------------------------
bool C_TFPlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd )
{
static QAngle angMoveAngle( 0.0f, 0.0f, 0.0f );
bool bNoTaunt = true;
if ( m_Shared.InCond( TF_COND_TAUNTING ) )
{
// show centerprint message
pCmd->forwardmove = 0.0f;
pCmd->sidemove = 0.0f;
pCmd->upmove = 0.0f;
pCmd->buttons = 0;
pCmd->weaponselect = 0;
VectorCopy( angMoveAngle, pCmd->viewangles );
bNoTaunt = false;
}
else
{
VectorCopy( pCmd->viewangles, angMoveAngle );
}
BaseClass::CreateMove( flInputSampleTime, pCmd );
AvoidPlayers( pCmd );
return bNoTaunt;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
{
if ( IsLocalPlayer() )
{
if ( !prediction->IsFirstTimePredicted() )
return;
}
MDLCACHE_CRITICAL_SECTION();
m_PlayerAnimState->DoAnimationEvent( event, nData );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
Vector C_TFPlayer::GetObserverCamOrigin( void )
{
if ( !IsAlive() )
{
if ( m_hFirstGib )
{
IPhysicsObject *pPhysicsObject = m_hFirstGib->VPhysicsGetObject();
if( pPhysicsObject )
{
Vector vecMassCenter = pPhysicsObject->GetMassCenterLocalSpace();
Vector vecWorld;
m_hFirstGib->CollisionProp()->CollisionToWorldSpace( vecMassCenter, &vecWorld );
return (vecWorld);
}
return m_hFirstGib->GetRenderOrigin();
}
IRagdoll *pRagdoll = GetRepresentativeRagdoll();
if ( pRagdoll )
return pRagdoll->GetRagdollOrigin();
}
return BaseClass::GetObserverCamOrigin();
}
//-----------------------------------------------------------------------------
// Purpose: Consider the viewer and other factors when determining resulting
// invisibility
//-----------------------------------------------------------------------------
float C_TFPlayer::GetEffectiveInvisibilityLevel( void )
{
float flPercentInvisible = GetPercentInvisible();
// If this is a teammate of the local player or viewer is observer,
// dont go above a certain max invis
if ( !IsEnemyPlayer() )
{
float flMax = tf_teammate_max_invis.GetFloat();
if ( flPercentInvisible > flMax )
{
flPercentInvisible = flMax;
}
}
else
{
// If this player just killed me, show them slightly
// less than full invis in the deathcam and freezecam
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer )
{
int iObserverMode = pLocalPlayer->GetObserverMode();
if ( ( iObserverMode == OBS_MODE_FREEZECAM || iObserverMode == OBS_MODE_DEATHCAM ) &&
pLocalPlayer->GetObserverTarget() == this )
{
float flMax = tf_teammate_max_invis.GetFloat();
if ( flPercentInvisible > flMax )
{
flPercentInvisible = flMax;
}
}
}
}
return flPercentInvisible;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_TFPlayer::DrawModel( int flags )
{
// If we're a dead player with a fresh ragdoll, don't draw
if ( m_nRenderFX == kRenderFxRagdoll )
return 0;
// Don't draw the model at all if we're fully invisible
if ( GetEffectiveInvisibilityLevel() >= 1.0f )
{
if ( m_hPartyHat && ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ) && !m_hPartyHat->IsEffectActive( EF_NODRAW ) )
{
m_hPartyHat->SetEffects( EF_NODRAW );
}
return 0;
}
else
{
if ( m_hPartyHat && ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ) && m_hPartyHat->IsEffectActive( EF_NODRAW ) )
{
m_hPartyHat->RemoveEffects( EF_NODRAW );
}
}
CMatRenderContextPtr pRenderContext( materials );
bool bDoEffect = false;
float flAmountToChop = 0.0;
if ( m_Shared.InCond( TF_COND_DISGUISING ) )
{
flAmountToChop = ( gpGlobals->curtime - m_flDisguiseEffectStartTime ) *
( 1.0 / TF_TIME_TO_DISGUISE );
}
else
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
{
float flETime = gpGlobals->curtime - m_flDisguiseEffectStartTime;
if ( ( flETime > 0.0 ) && ( flETime < TF_TIME_TO_SHOW_DISGUISED_FINISHED_EFFECT ) )
{
flAmountToChop = 1.0 - ( flETime * ( 1.0/TF_TIME_TO_SHOW_DISGUISED_FINISHED_EFFECT ) );
}
}
bDoEffect = ( flAmountToChop > 0.0 ) && ( ! IsLocalPlayer() );
#if ( SHOW_DISGUISE_EFFECT == 0 )
bDoEffect = false;
#endif
bDoEffect = false;
if ( bDoEffect )
{
Vector vMyOrigin = GetAbsOrigin();
BoxDeformation_t mybox;
mybox.m_ClampMins = vMyOrigin - Vector(100,100,100);
mybox.m_ClampMaxes = vMyOrigin + Vector(500,500,72 * ( 1 - flAmountToChop ) );
pRenderContext->PushDeformation( &mybox );
}
int ret = BaseClass::DrawModel( flags );
if ( bDoEffect )
pRenderContext->PopDeformation();
return ret;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::ProcessMuzzleFlashEvent()
{
CBasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
// Reenable when the weapons have muzzle flash attachments in the right spot.
bool bInToolRecordingMode = ToolsEnabled() && clienttools->IsInRecordingMode();
if ( this == pLocalPlayer && !bInToolRecordingMode )
return; // don't show own world muzzle flash for localplayer
if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE )
{
// also don't show in 1st person spec mode
if ( pLocalPlayer->GetObserverTarget() == this )
return;
}
C_TFWeaponBase *pWeapon = m_Shared.GetActiveTFWeapon();
if ( !pWeapon )
return;
pWeapon->ProcessMuzzleFlashEvent();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_TFPlayer::GetIDTarget() const
{
return m_iIDEntIndex;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::SetForcedIDTarget( int iTarget )
{
m_iForcedIDTarget = iTarget;
}
//-----------------------------------------------------------------------------
// Purpose: Update this client's targetid entity
//-----------------------------------------------------------------------------
void C_TFPlayer::UpdateIDTarget()
{
if ( !IsLocalPlayer() )
return;
// don't show IDs if mp_fadetoblack is on
if ( GetTeamNumber() > TEAM_SPECTATOR && mp_fadetoblack.GetBool() && !IsAlive() )
{
m_iIDEntIndex = 0;
return;
}
if ( m_iForcedIDTarget )
{
m_iIDEntIndex = m_iForcedIDTarget;
return;
}
// If we're in deathcam, ID our killer
if ( (GetObserverMode() == OBS_MODE_DEATHCAM || GetObserverMode() == OBS_MODE_CHASE) && GetObserverTarget() && GetObserverTarget() != GetLocalTFPlayer() )
{
m_iIDEntIndex = GetObserverTarget()->entindex();
return;
}
// Clear old target and find a new one
m_iIDEntIndex = 0;
trace_t tr;
Vector vecStart, vecEnd;
VectorMA( MainViewOrigin(), MAX_TRACE_LENGTH, MainViewForward(), vecEnd );
VectorMA( MainViewOrigin(), 10, MainViewForward(), vecStart );
// If we're in observer mode, ignore our observer target. Otherwise, ignore ourselves.
if ( IsObserver() )
{
UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, GetObserverTarget(), COLLISION_GROUP_NONE, &tr );
}
else
{
UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
}
if ( !tr.startsolid && tr.DidHitNonWorldEntity() )
{
C_BaseEntity *pEntity = tr.m_pEnt;
if ( pEntity && ( pEntity != this ) )
{
m_iIDEntIndex = pEntity->entindex();
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Display appropriate hints for the target we're looking at
//-----------------------------------------------------------------------------
void C_TFPlayer::DisplaysHintsForTarget( C_BaseEntity *pTarget )
{
// If the entity provides hints, ask them if they have one for this player
ITargetIDProvidesHint *pHintInterface = dynamic_cast<ITargetIDProvidesHint*>(pTarget);
if ( pHintInterface )
{
pHintInterface->DisplayHintTo( this );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_TFPlayer::GetRenderTeamNumber( void )
{
return m_nSkin;
}
static Vector WALL_MIN(-WALL_OFFSET,-WALL_OFFSET,-WALL_OFFSET);
static Vector WALL_MAX(WALL_OFFSET,WALL_OFFSET,WALL_OFFSET);
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CalcDeathCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov)
{
CBaseEntity * killer = GetObserverTarget();
// Swing to face our killer within half the death anim time
float interpolation = ( gpGlobals->curtime - m_flDeathTime ) / (TF_DEATH_ANIMATION_TIME * 0.5);
interpolation = clamp( interpolation, 0.0f, 1.0f );
interpolation = SimpleSpline( interpolation );
m_flObserverChaseDistance += gpGlobals->frametime*48.0f;
m_flObserverChaseDistance = clamp(m_flObserverChaseDistance, CHASE_CAM_DISTANCE_MIN, CHASE_CAM_DISTANCE_MAX);
QAngle aForward = eyeAngles = EyeAngles();
Vector origin = EyePosition();
IRagdoll *pRagdoll = GetRepresentativeRagdoll();
if ( pRagdoll )
{
origin = pRagdoll->GetRagdollOrigin();
origin.z += VEC_DEAD_VIEWHEIGHT.z; // look over ragdoll, not through
}
if ( killer && (killer != this) )
{
Vector vKiller = killer->EyePosition() - origin;
QAngle aKiller; VectorAngles( vKiller, aKiller );
InterpolateAngles( aForward, aKiller, eyeAngles, interpolation );
};
Vector vForward; AngleVectors( eyeAngles, &vForward );
VectorNormalize( vForward );
VectorMA( origin, -m_flObserverChaseDistance, vForward, eyeOrigin );
trace_t trace; // clip against world
C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace
UTIL_TraceHull( origin, eyeOrigin, WALL_MIN, WALL_MAX, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace );
C_BaseEntity::PopEnableAbsRecomputations();
if (trace.fraction < 1.0)
{
eyeOrigin = trace.endpos;
m_flObserverChaseDistance = VectorLength(origin - eyeOrigin);
}
fov = GetFOV();
}
//-----------------------------------------------------------------------------
// Purpose: Do nothing multiplayer_animstate takes care of animation.
// Input : playerAnim -
//-----------------------------------------------------------------------------
void C_TFPlayer::SetAnimation( PLAYER_ANIM playerAnim )
{
return;
}
float C_TFPlayer::GetMinFOV() const
{
// Min FOV for Sniper Rifle
return 20;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const QAngle& C_TFPlayer::EyeAngles()
{
if ( IsLocalPlayer() && g_nKillCamMode == OBS_MODE_NONE )
{
return BaseClass::EyeAngles();
}
else
{
return m_angEyeAngles;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &color -
//-----------------------------------------------------------------------------
void C_TFPlayer::GetTeamColor( Color &color )
{
color[3] = 255;
if ( GetTeamNumber() == TF_TEAM_RED )
{
color[0] = 159;
color[1] = 55;
color[2] = 34;
}
else if ( GetTeamNumber() == TF_TEAM_BLUE )
{
color[0] = 76;
color[1] = 109;
color[2] = 129;
}
else
{
color[0] = 255;
color[1] = 255;
color[2] = 255;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : bCopyEntity -
// Output : C_BaseAnimating *
//-----------------------------------------------------------------------------
C_BaseAnimating *C_TFPlayer::BecomeRagdollOnClient()
{
// Let the C_TFRagdoll take care of this.
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
// Output : IRagdoll*
//-----------------------------------------------------------------------------
IRagdoll* C_TFPlayer::GetRepresentativeRagdoll() const
{
if ( m_hRagdoll.Get() )
{
C_TFRagdoll *pRagdoll = static_cast<C_TFRagdoll*>( m_hRagdoll.Get() );
if ( !pRagdoll )
return NULL;
return pRagdoll->GetIRagdoll();
}
else
{
return NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::InitPlayerGibs( void )
{
// Clear out the gib list and create a new one.
m_aGibs.Purge();
BuildGibList( m_aGibs, GetModelIndex(), 1.0f, COLLISION_GROUP_NONE );
if ( TFGameRules() && TFGameRules()->IsBirthday() )
{
for ( int i = 0; i < m_aGibs.Count(); i++ )
{
if ( RandomFloat(0,1) < 0.75 )
{
Q_strncpy( m_aGibs[i].modelName, g_pszBDayGibs[ RandomInt(0,ARRAYSIZE(g_pszBDayGibs)-1) ] , sizeof(m_aGibs[i].modelName) );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &vecOrigin -
// &vecVelocity -
// &vecImpactVelocity -
//-----------------------------------------------------------------------------
void C_TFPlayer::CreatePlayerGibs( const Vector &vecOrigin, const Vector &vecVelocity, float flImpactScale )
{
// Make sure we have Gibs to create.
if ( m_aGibs.Count() == 0 )
return;
AngularImpulse angularImpulse( RandomFloat( 0.0f, 120.0f ), RandomFloat( 0.0f, 120.0f ), 0.0 );
Vector vecBreakVelocity = vecVelocity;
vecBreakVelocity.z += tf_playergib_forceup.GetFloat();
VectorNormalize( vecBreakVelocity );
vecBreakVelocity *= tf_playergib_force.GetFloat();
// Cap the impulse.
float flSpeed = vecBreakVelocity.Length();
if ( flSpeed > tf_playergib_maxspeed.GetFloat() )
{
VectorScale( vecBreakVelocity, tf_playergib_maxspeed.GetFloat() / flSpeed, vecBreakVelocity );
}
breakablepropparams_t breakParams( vecOrigin, GetRenderAngles(), vecBreakVelocity, angularImpulse );
breakParams.impactEnergyScale = 1.0f;//
// Break up the player.
m_hSpawnedGibs.Purge();
m_hFirstGib = CreateGibsFromList( m_aGibs, GetModelIndex(), NULL, breakParams, this, -1 , false, true, &m_hSpawnedGibs );
DropPartyHat( breakParams, vecBreakVelocity );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::DropPartyHat( breakablepropparams_t &breakParams, Vector &vecBreakVelocity )
{
if ( m_hPartyHat )
{
breakmodel_t breakModel;
Q_strncpy( breakModel.modelName, BDAY_HAT_MODEL, sizeof(breakModel.modelName) );
breakModel.health = 1;
breakModel.fadeTime = RandomFloat(5,10);
breakModel.fadeMinDist = 0.0f;
breakModel.fadeMaxDist = 0.0f;
breakModel.burstScale = breakParams.defBurstScale;
breakModel.collisionGroup = COLLISION_GROUP_DEBRIS;
breakModel.isRagdoll = false;
breakModel.isMotionDisabled = false;
breakModel.placementName[0] = 0;
breakModel.placementIsBone = false;
breakModel.offset = GetAbsOrigin() - m_hPartyHat->GetAbsOrigin();
BreakModelCreateSingle( this, &breakModel, m_hPartyHat->GetAbsOrigin(), m_hPartyHat->GetAbsAngles(), vecBreakVelocity, breakParams.angularVelocity, m_hPartyHat->m_nSkin, breakParams );
m_hPartyHat->Release();
}
}
//-----------------------------------------------------------------------------
// Purpose: How many buildables does this player own
//-----------------------------------------------------------------------------
int C_TFPlayer::GetObjectCount( void )
{
return m_aObjects.Count();
}
//-----------------------------------------------------------------------------
// Purpose: Get a specific buildable that this player owns
//-----------------------------------------------------------------------------
C_BaseObject *C_TFPlayer::GetObject( int index )
{
return m_aObjects[index].Get();
}
//-----------------------------------------------------------------------------
// Purpose: Get a specific buildable that this player owns
//-----------------------------------------------------------------------------
C_BaseObject *C_TFPlayer::GetObjectOfType( int iObjectType )
{
int iCount = m_aObjects.Count();
for ( int i=0;i<iCount;i++ )
{
C_BaseObject *pObj = m_aObjects[i].Get();
if ( !pObj )
continue;
if ( pObj->IsDormant() || pObj->IsMarkedForDeletion() )
continue;
if ( pObj->GetType() == iObjectType )
{
return pObj;
}
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : collisionGroup -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_TFPlayer::ShouldCollide( int collisionGroup, int contentsMask ) const
{
if ( ( ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT ) && tf_avoidteammates.GetBool() ) ||
collisionGroup == TFCOLLISION_GROUP_ROCKETS )
{
switch( GetTeamNumber() )
{
case TF_TEAM_RED:
if ( !( contentsMask & CONTENTS_REDTEAM ) )
return false;
break;
case TF_TEAM_BLUE:
if ( !( contentsMask & CONTENTS_BLUETEAM ) )
return false;
break;
}
}
return BaseClass::ShouldCollide( collisionGroup, contentsMask );
}
float C_TFPlayer::GetPercentInvisible( void )
{
return m_Shared.GetPercentInvisible();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_TFPlayer::GetSkin()
{
C_TFPlayer *pLocalPlayer = GetLocalTFPlayer();
if ( !pLocalPlayer )
return 0;
int iVisibleTeam = GetTeamNumber();
// if this player is disguised and on the other team, use disguise team
if ( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() )
{
iVisibleTeam = m_Shared.GetDisguiseTeam();
}
int nSkin;
switch( iVisibleTeam )
{
case TF_TEAM_RED:
nSkin = 0;
break;
case TF_TEAM_BLUE:
nSkin = 1;
break;
default:
nSkin = 0;
break;
}
// 3 and 4 are invulnerable
if ( m_Shared.InCond( TF_COND_INVULNERABLE ) )
{
nSkin += 2;
}
else if ( m_Shared.InCond( TF_COND_DISGUISED ) && !IsEnemyPlayer() )
{
nSkin += 4 + ( ( m_Shared.GetDisguiseClass() - TF_FIRST_NORMAL_CLASS ) * 2 );
}
return nSkin;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : iClass -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_TFPlayer::IsPlayerClass( int iClass )
{
C_TFPlayerClass *pClass = GetPlayerClass();
if ( !pClass )
return false;
return ( pClass->GetClassIndex() == iClass );
}
//-----------------------------------------------------------------------------
// Purpose: Don't take damage decals while stealthed
//-----------------------------------------------------------------------------
void C_TFPlayer::AddDecal( const Vector& rayStart, const Vector& rayEnd,
const Vector& decalCenter, int hitbox, int decalIndex, bool doTrace, trace_t& tr, int maxLODToDecal )
{
if ( m_Shared.InCond( TF_COND_STEALTHED ) )
{
return;
}
if ( m_Shared.InCond( TF_COND_DISGUISED ) )
{
return;
}
if ( m_Shared.InCond( TF_COND_INVULNERABLE ) )
{
Vector vecDir = rayEnd - rayStart;
VectorNormalize(vecDir);
g_pEffects->Ricochet( rayEnd - (vecDir * 8), -vecDir );
return;
}
// don't decal from inside the player
if ( tr.startsolid )
{
return;
}
BaseClass::AddDecal( rayStart, rayEnd, decalCenter, hitbox, decalIndex, doTrace, tr, maxLODToDecal );
}
//-----------------------------------------------------------------------------
// Called every time the player respawns
//-----------------------------------------------------------------------------
void C_TFPlayer::ClientPlayerRespawn( void )
{
if ( IsLocalPlayer() )
{
// Dod called these, not sure why
//MoveToLastReceivedPosition( true );
//ResetLatched();
// Reset the camera.
if ( m_bWasTaunting )
{
TurnOffTauntCam();
}
ResetToneMapping(1.0);
// Release the duck toggle key
KeyUp( &in_ducktoggle, NULL );
}
UpdateVisibility();
m_hFirstGib = NULL;
m_hSpawnedGibs.Purge();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CreateSaveMeEffect( void )
{
// Don't create them for the local player
if ( IsLocalPlayer() && !ShouldDrawLocalPlayer() )
return;
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
// If I'm disguised as the enemy, play to all players
if ( m_Shared.InCond( TF_COND_DISGUISED ) && m_Shared.GetDisguiseTeam() != GetTeamNumber() )
{
// play to all players
}
else
{
// only play to teammates
if ( pLocalPlayer && pLocalPlayer->GetTeamNumber() != GetTeamNumber() )
return;
}
if ( m_pSaveMeEffect )
{
ParticleProp()->StopEmission( m_pSaveMeEffect );
m_pSaveMeEffect = NULL;
}
m_pSaveMeEffect = ParticleProp()->Create( "speech_mediccall", PATTACH_POINT_FOLLOW, "head" );
// If the local player is a medic, add this player to our list of medic callers
if ( pLocalPlayer && pLocalPlayer->IsPlayerClass( TF_CLASS_MEDIC ) && pLocalPlayer->IsAlive() == true )
{
Vector vecPos;
if ( GetAttachmentLocal( LookupAttachment( "head" ), vecPos ) )
{
vecPos += Vector(0,0,18); // Particle effect is 18 units above the attachment
CTFMedicCallerPanel::AddMedicCaller( this, 5.0, vecPos );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::IsOverridingViewmodel( void )
{
C_TFPlayer *pPlayer = this;
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE &&
pLocalPlayer->GetObserverTarget() && pLocalPlayer->GetObserverTarget()->IsPlayer() )
{
pPlayer = assert_cast<C_TFPlayer*>(pLocalPlayer->GetObserverTarget());
}
if ( pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE ) )
return true;
return BaseClass::IsOverridingViewmodel();
}
//-----------------------------------------------------------------------------
// Purpose: Draw my viewmodel in some special way
//-----------------------------------------------------------------------------
int C_TFPlayer::DrawOverriddenViewmodel( C_BaseViewModel *pViewmodel, int flags )
{
int ret = 0;
C_TFPlayer *pPlayer = this;
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer && pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE &&
pLocalPlayer->GetObserverTarget() && pLocalPlayer->GetObserverTarget()->IsPlayer() )
{
pPlayer = assert_cast<C_TFPlayer*>(pLocalPlayer->GetObserverTarget());
}
if ( pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE ) )
{
// Force the invulnerable material
modelrender->ForcedMaterialOverride( *pPlayer->GetInvulnMaterialRef() );
ret = pViewmodel->DrawOverriddenViewmodel( flags );
modelrender->ForcedMaterialOverride( NULL );
}
return ret;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::SetHealer( C_TFPlayer *pHealer, float flChargeLevel )
{
// We may be getting healed by multiple healers. Show the healer
// who's got the highest charge level.
if ( m_hHealer )
{
if ( m_flHealerChargeLevel > flChargeLevel )
return;
}
m_hHealer = pHealer;
m_flHealerChargeLevel = flChargeLevel;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float C_TFPlayer::MedicGetChargeLevel( void )
{
if ( IsPlayerClass(TF_CLASS_MEDIC) )
{
CTFWeaponBase *pWpn = ( CTFWeaponBase *)Weapon_OwnsThisID( TF_WEAPON_MEDIGUN );
if ( pWpn == NULL )
return 0;
CWeaponMedigun *pWeapon = dynamic_cast <CWeaponMedigun*>( pWpn );
if ( pWeapon )
return pWeapon->GetChargeLevel();
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CBaseEntity *C_TFPlayer::MedicGetHealTarget( void )
{
if ( IsPlayerClass(TF_CLASS_MEDIC) )
{
CWeaponMedigun *pWeapon = dynamic_cast <CWeaponMedigun*>( GetActiveWeapon() );
if ( pWeapon )
return pWeapon->GetHealTarget();
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::CanShowClassMenu( void )
{
return ( GetTeamNumber() > LAST_SHARED_TEAM );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::InitializePoseParams( void )
{
/*
m_headYawPoseParam = LookupPoseParameter( "head_yaw" );
GetPoseParameterRange( m_headYawPoseParam, m_headYawMin, m_headYawMax );
m_headPitchPoseParam = LookupPoseParameter( "head_pitch" );
GetPoseParameterRange( m_headPitchPoseParam, m_headPitchMin, m_headPitchMax );
*/
CStudioHdr *hdr = GetModelPtr();
Assert( hdr );
if ( !hdr )
return;
for ( int i = 0; i < hdr->GetNumPoseParameters() ; i++ )
{
SetPoseParameter( hdr, i, 0.0 );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
Vector C_TFPlayer::GetChaseCamViewOffset( CBaseEntity *target )
{
if ( target->IsBaseObject() )
return Vector(0,0,64);
return BaseClass::GetChaseCamViewOffset( target );
}
//-----------------------------------------------------------------------------
// Purpose: Called from PostDataUpdate to update the model index
//-----------------------------------------------------------------------------
void C_TFPlayer::ValidateModelIndex( void )
{
if ( m_Shared.InCond( TF_COND_DISGUISED ) && IsEnemyPlayer() )
{
TFPlayerClassData_t *pData = GetPlayerClassData( m_Shared.GetDisguiseClass() );
m_nModelIndex = modelinfo->GetModelIndex( pData->GetModelName() );
}
else
{
C_TFPlayerClass *pClass = GetPlayerClass();
if ( pClass )
{
m_nModelIndex = modelinfo->GetModelIndex( pClass->GetModelName() );
}
}
if ( m_iSpyMaskBodygroup > -1 && GetModelPtr() != NULL )
{
SetBodygroup( m_iSpyMaskBodygroup, ( m_Shared.InCond( TF_COND_DISGUISED ) && !IsEnemyPlayer() ) );
}
BaseClass::ValidateModelIndex();
}
//-----------------------------------------------------------------------------
// Purpose: Simulate the player for this frame
//-----------------------------------------------------------------------------
void C_TFPlayer::Simulate( void )
{
//Frame updates
if ( this == C_BasePlayer::GetLocalPlayer() )
{
//Update the flashlight
Flashlight();
}
// TF doesn't do step sounds based on velocity, instead using anim events
// So we deliberately skip over the base player simulate, which calls them.
BaseClass::BaseClass::Simulate();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options )
{
if ( event == 7001 )
{
// Force a footstep sound
m_flStepSoundTime = 0;
Vector vel;
EstimateAbsVelocity( vel );
UpdateStepSound( GetGroundSurface(), GetAbsOrigin(), vel );
}
else if ( event == AE_WPN_HIDE )
{
if ( GetActiveWeapon() )
{
GetActiveWeapon()->SetWeaponVisible( false );
}
}
else if ( event == AE_WPN_UNHIDE )
{
if ( GetActiveWeapon() )
{
GetActiveWeapon()->SetWeaponVisible( true );
}
}
else if ( event == TF_AE_CIGARETTE_THROW )
{
CEffectData data;
int iAttach = LookupAttachment( options );
GetAttachment( iAttach, data.m_vOrigin, data.m_vAngles );
data.m_vAngles = GetRenderAngles();
data.m_hEntity = ClientEntityList().EntIndexToHandle( entindex() );
DispatchEffect( "TF_ThrowCigarette", data );
return;
}
else
BaseClass::FireEvent( origin, angles, event, options );
}
// Shadows
ConVar cl_blobbyshadows( "cl_blobbyshadows", "0", FCVAR_CLIENTDLL );
ShadowType_t C_TFPlayer::ShadowCastType( void )
{
// Removed the GetPercentInvisible - should be taken care off in BindProxy now.
if ( !IsVisible() /*|| GetPercentInvisible() > 0.0f*/ )
return SHADOWS_NONE;
if ( IsEffectActive(EF_NODRAW | EF_NOSHADOW) )
return SHADOWS_NONE;
// If in ragdoll mode.
if ( m_nRenderFX == kRenderFxRagdoll )
return SHADOWS_NONE;
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
// if we're first person spectating this player
if ( pLocalPlayer &&
pLocalPlayer->GetObserverTarget() == this &&
pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE )
{
return SHADOWS_NONE;
}
if( cl_blobbyshadows.GetBool() )
return SHADOWS_SIMPLE;
return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC;
}
float g_flFattenAmt = 4;
void C_TFPlayer::GetShadowRenderBounds( Vector &mins, Vector &maxs, ShadowType_t shadowType )
{
if ( shadowType == SHADOWS_SIMPLE )
{
// Don't let the render bounds change when we're using blobby shadows, or else the shadow
// will pop and stretch.
mins = CollisionProp()->OBBMins();
maxs = CollisionProp()->OBBMaxs();
}
else
{
GetRenderBounds( mins, maxs );
// We do this because the normal bbox calculations don't take pose params into account, and
// the rotation of the guy's upper torso can place his gun a ways out of his bbox, and
// the shadow will get cut off as he rotates.
//
// Thus, we give it some padding here.
mins -= Vector( g_flFattenAmt, g_flFattenAmt, 0 );
maxs += Vector( g_flFattenAmt, g_flFattenAmt, 0 );
}
}
void C_TFPlayer::GetRenderBounds( Vector& theMins, Vector& theMaxs )
{
// TODO POSTSHIP - this hack/fix goes hand-in-hand with a fix in CalcSequenceBoundingBoxes in utils/studiomdl/simplify.cpp.
// When we enable the fix in CalcSequenceBoundingBoxes, we can get rid of this.
//
// What we're doing right here is making sure it only uses the bbox for our lower-body sequences since,
// with the current animations and the bug in CalcSequenceBoundingBoxes, are WAY bigger than they need to be.
C_BaseAnimating::GetRenderBounds( theMins, theMaxs );
}
bool C_TFPlayer::GetShadowCastDirection( Vector *pDirection, ShadowType_t shadowType ) const
{
if ( shadowType == SHADOWS_SIMPLE )
{
// Blobby shadows should sit directly underneath us.
pDirection->Init( 0, 0, -1 );
return true;
}
else
{
return BaseClass::GetShadowCastDirection( pDirection, shadowType );
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether this player is the nemesis of the local player
//-----------------------------------------------------------------------------
bool C_TFPlayer::IsNemesisOfLocalPlayer()
{
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
if ( pLocalPlayer )
{
// return whether this player is dominating the local player
return m_Shared.IsPlayerDominated( pLocalPlayer->entindex() );
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether we should show the nemesis icon for this player
//-----------------------------------------------------------------------------
bool C_TFPlayer::ShouldShowNemesisIcon()
{
// we should show the nemesis effect on this player if he is the nemesis of the local player,
// and is not dead, cloaked or disguised
if ( IsNemesisOfLocalPlayer() && g_PR && g_PR->IsConnected( entindex() ) )
{
bool bStealthed = m_Shared.InCond( TF_COND_STEALTHED );
bool bDisguised = m_Shared.InCond( TF_COND_DISGUISED );
if ( IsAlive() && !bStealthed && !bDisguised )
return true;
}
return false;
}
bool C_TFPlayer::IsWeaponLowered( void )
{
CTFWeaponBase *pWeapon = GetActiveTFWeapon();
if ( !pWeapon )
return false;
CTFGameRules *pRules = TFGameRules();
// Lower losing team's weapons in bonus round
if ( ( pRules->State_Get() == GR_STATE_TEAM_WIN ) && ( pRules->GetWinningTeam() != GetTeamNumber() ) )
return true;
// Hide all view models after the game is over
if ( pRules->State_Get() == GR_STATE_GAME_OVER )
return true;
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget )
{
switch ( event->GetType() )
{
case CChoreoEvent::SEQUENCE:
case CChoreoEvent::GESTURE:
return StartGestureSceneEvent( info, scene, event, actor, pTarget );
default:
return BaseClass::StartSceneEvent( info, scene, event, actor, pTarget );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_TFPlayer::StartGestureSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget )
{
// Get the (gesture) sequence.
info->m_nSequence = LookupSequence( event->GetParameters() );
if ( info->m_nSequence < 0 )
return false;
// Player the (gesture) sequence.
m_PlayerAnimState->AddVCDSequenceToGestureSlot( GESTURE_SLOT_VCD, info->m_nSequence );
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_TFPlayer::GetNumActivePipebombs( void )
{
if ( IsPlayerClass( TF_CLASS_DEMOMAN ) )
{
CTFPipebombLauncher *pWeapon = dynamic_cast < CTFPipebombLauncher*>( Weapon_OwnsThisID( TF_WEAPON_PIPEBOMBLAUNCHER ) );
if ( pWeapon )
{
return pWeapon->GetPipeBombCount();
}
}
return 0;
}
bool C_TFPlayer::IsAllowedToSwitchWeapons( void )
{
if ( IsWeaponLowered() == true )
return false;
return BaseClass::IsAllowedToSwitchWeapons();
}
IMaterial *C_TFPlayer::GetHeadLabelMaterial( void )
{
if ( g_pHeadLabelMaterial[0] == NULL )
SetupHeadLabelMaterials();
if ( GetTeamNumber() == TF_TEAM_RED )
{
return g_pHeadLabelMaterial[TF_PLAYER_HEAD_LABEL_RED];
}
else
{
return g_pHeadLabelMaterial[TF_PLAYER_HEAD_LABEL_BLUE];
}
return BaseClass::GetHeadLabelMaterial();
}
void SetupHeadLabelMaterials( void )
{
for ( int i = 0; i < 2; i++ )
{
if ( g_pHeadLabelMaterial[i] )
{
g_pHeadLabelMaterial[i]->DecrementReferenceCount();
g_pHeadLabelMaterial[i] = NULL;
}
g_pHeadLabelMaterial[i] = materials->FindMaterial( pszHeadLabelNames[i], TEXTURE_GROUP_VGUI );
if ( g_pHeadLabelMaterial[i] )
{
g_pHeadLabelMaterial[i]->IncrementReferenceCount();
}
}
}
void C_TFPlayer::ComputeFxBlend( void )
{
BaseClass::ComputeFxBlend();
if ( GetPlayerClass()->IsClass( TF_CLASS_SPY ) )
{
float flInvisible = GetPercentInvisible();
if ( flInvisible != 0.0f )
{
// Tell our shadow
ClientShadowHandle_t hShadow = GetShadowHandle();
if ( hShadow != CLIENTSHADOW_INVALID_HANDLE )
{
g_pClientShadowMgr->SetFalloffBias( hShadow, flInvisible * 255 );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::CalcView( Vector &eyeOrigin, QAngle &eyeAngles, float &zNear, float &zFar, float &fov )
{
HandleTaunting();
BaseClass::CalcView( eyeOrigin, eyeAngles, zNear, zFar, fov );
}
static void cc_tf_crashclient()
{
C_TFPlayer *pPlayer = NULL;
pPlayer->ComputeFxBlend();
}
static ConCommand tf_crashclient( "tf_crashclient", cc_tf_crashclient, "Crashes this client for testing.", FCVAR_DEVELOPMENTONLY );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_TFPlayer::ForceUpdateObjectHudState( void )
{
m_bUpdateObjectHudState = true;
}
#include "c_obj_sentrygun.h"
static void cc_tf_debugsentrydmg()
{
C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
pPlayer->UpdateIDTarget();
int iTarget = pPlayer->GetIDTarget();
if ( iTarget > 0 )
{
C_BaseEntity *pEnt = cl_entitylist->GetEnt( iTarget );
C_ObjectSentrygun *pSentry = dynamic_cast< C_ObjectSentrygun * >( pEnt );
if ( pSentry )
{
pSentry->DebugDamageParticles();
}
}
}
static ConCommand tf_debugsentrydamage( "tf_debugsentrydamage", cc_tf_debugsentrydmg, "", FCVAR_DEVELOPMENTONLY );