//====== 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 ¶ms ); 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( 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( 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(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, ¤tdir, &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(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( 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;iIsDormant() || 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(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(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 ( pWpn ); if ( pWeapon ) return pWeapon->GetChargeLevel(); } return 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseEntity *C_TFPlayer::MedicGetHealTarget( void ) { if ( IsPlayerClass(TF_CLASS_MEDIC) ) { CWeaponMedigun *pWeapon = dynamic_cast ( 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 );