//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "BaseAnimating.h" #include "tf_shieldgrenade.h" #include "tf_shieldshared.h" #include "tf_shield_flat.h" #include "tf_player.h" #include "engine/IEngineSound.h" #include "Sprite.h" #define SHIELD_GRENADE_FUSE_TIME 2.25f //----------------------------------------------------------------------------- // // The shield grenade class // //----------------------------------------------------------------------------- class CShieldGrenade : public CBaseAnimating { DECLARE_CLASS( CShieldGrenade, CBaseAnimating ); public: DECLARE_DATADESC(); CShieldGrenade(); virtual void Spawn( void ); virtual void Precache( void ); virtual void UpdateOnRemove( void ); void SetLifetime( float timer ); int GetDamageType() const { return DMG_ENERGYBEAM; } void StickyTouch( CBaseEntity *pOther ); void BeepThink( void ); void ShieldActiveThink( void ); void DeathThink( void ); virtual bool CanTakeEMPDamage() { return true; } virtual bool TakeEMPDamage( float duration ); private: // Check when we're done with EMP void CheckEMPDamageFinish( ); void CreateShield( ); void ComputeActiveThinkTime( ); void BounceSound( ); private: // Make sure all grenades explode Vector m_LastCollision; // Time when EMP runs out float m_flEMPDamageEndTime; float m_ShieldLifetime; float m_flDetonateTime; // Are we EMPed? bool m_IsEMPed; bool m_IsDeployed; // The deployed shield CHandle m_hDeployedShield; CSprite *m_pLiveSprite; }; //----------------------------------------------------------------------------- // Data table //----------------------------------------------------------------------------- // Global Savedata for friction modifier BEGIN_DATADESC( CShieldGrenade ) // Function Pointers DEFINE_FUNCTION( StickyTouch ), DEFINE_FUNCTION( BeepThink ), DEFINE_FUNCTION( ShieldActiveThink ), DEFINE_FUNCTION( DeathThink ), END_DATADESC() LINK_ENTITY_TO_CLASS( grenade_shield, CShieldGrenade ); PRECACHE_REGISTER( grenade_shield ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CShieldGrenade::CShieldGrenade() { UseClientSideAnimation(); m_hDeployedShield.Set(0); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CShieldGrenade::Precache( void ) { PrecacheModel( "models/weapons/w_grenade.mdl" ); PrecacheScriptSound( "ShieldGrenade.Bounce" ); PrecacheScriptSound( "ShieldGrenade.StickBeep" ); PrecacheModel( "sprites/redglow1.vmt" ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CShieldGrenade::Spawn( void ) { BaseClass::Spawn(); m_LastCollision.Init( 0, 0, 0 ); SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); SetSolid( SOLID_BBOX ); SetGravity( 1.0 ); SetFriction( 0.9 ); SetModel( "models/weapons/w_grenade.mdl"); UTIL_SetSize(this, Vector( -4, -4, -4), Vector(4, 4, 4)); m_IsEMPed = false; m_IsDeployed = false; m_flEMPDamageEndTime = 0.0f; SetTouch( StickyTouch ); SetCollisionGroup( TFCOLLISION_GROUP_GRENADE ); // Create a green light m_pLiveSprite = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin() + Vector(0,0,1), false ); m_pLiveSprite->SetTransparency( kRenderGlow, 0, 0, 255, 128, kRenderFxNoDissipation ); m_pLiveSprite->SetScale( 1 ); m_pLiveSprite->SetAttachment( this, 0 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CShieldGrenade::UpdateOnRemove( void ) { if ( m_pLiveSprite ) { UTIL_Remove( m_pLiveSprite ); m_pLiveSprite = NULL; } BaseClass::UpdateOnRemove(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CShieldGrenade::SetLifetime( float timer ) { m_ShieldLifetime = timer; } //----------------------------------------------------------------------------- // EMP Related methods //----------------------------------------------------------------------------- bool CShieldGrenade::TakeEMPDamage( float duration ) { m_flEMPDamageEndTime = gpGlobals->curtime + duration; m_IsEMPed = true; if (m_hDeployedShield) { m_hDeployedShield->SetEMPed(true); // Recompute the next think time ComputeActiveThinkTime(); } return true; } //----------------------------------------------------------------------------- // Purpose: See if EMP impairment time has elapsed //----------------------------------------------------------------------------- void CShieldGrenade::CheckEMPDamageFinish( void ) { if ( !m_flEMPDamageEndTime || gpGlobals->curtime < m_flEMPDamageEndTime ) return; m_flEMPDamageEndTime = 0.0f; m_IsEMPed = false; if (m_hDeployedShield) { m_hDeployedShield->SetEMPed(false); // Recompute the next think time ComputeActiveThinkTime(); } } //----------------------------------------------------------------------------- // Plays a random bounce sound //----------------------------------------------------------------------------- void CShieldGrenade ::BounceSound( void ) { EmitSound( "ShieldGrenade.Bounce" ); } //----------------------------------------------------------------------------- // Purpose: Make the grenade stick to whatever it touches //----------------------------------------------------------------------------- void CShieldGrenade::StickyTouch( CBaseEntity *pOther ) { if (m_IsDeployed) return; // The touch can get called multiple times - create an ignore case if we // have already stuck. if ( m_LastCollision == GetLocalOrigin() ) return; // Only stick to floors... Vector up( 0, 0, 1 ); if ( DotProduct( GetTouchTrace().plane.normal, up ) < 0.5f ) return; // Only stick to BSP models if ( pOther->IsBSPModel() == false ) return; BounceSound(); SetAbsVelocity( vec3_origin ); SetMoveType( MOVETYPE_NONE ); // Beep EmitSound( "ShieldGrenade.StickBeep" ); // Start ticking... SetThink( BeepThink ); m_IsDeployed = true; SetNextThink( gpGlobals->curtime + 0.01f ); m_flDetonateTime = gpGlobals->curtime + SHIELD_GRENADE_FUSE_TIME; m_LastCollision = GetLocalOrigin(); } //----------------------------------------------------------------------------- // Purpose: Play beeping sounds until the charge explode //----------------------------------------------------------------------------- void CShieldGrenade::CreateShield( void ) { /* // Set the orientation of the shield based on // the closest teammate's relative position... float mindist = FLT_MAX; Vector dir; for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CBaseTFPlayer *pPlayer = ToBaseTFPlayer( UTIL_PlayerByIndex(i) ); if ( pPlayer ) { if (pPlayer->GetTeamNumber() == GetTeamNumber()) { Vector tempdir; VectorSubtract( pPlayer->Center(), Center(), tempdir ); float dist = VectorNormalize( tempdir ); if (dist < mindist) { mindist = dist; VectorCopy( tempdir, dir ); } } } } if( mindist == FLT_MAX ) { AngleVectors( GetAngles(), &dir ); } // Never pitch the shield dir.z = 0.0f; VectorNormalize(dir); QAngle relAngles; VMatrix parentMatrix; VMatrix worldShieldMatrix; VMatrix relativeMatrix; VMatrix parentInvMatrix; // Construct a transform from shield to grenade (parent) MatrixFromAngles( GetAngles(), parentMatrix ); #ifdef _DEBUG bool ok = #endif MatrixInverseGeneral( parentMatrix, parentInvMatrix ); Assert( ok ); Vector up( 0, 0, 1 ); Vector left; CrossProduct( up, dir, left ); MatrixSetIdentity( worldShieldMatrix ); worldShieldMatrix.SetUp( up ); worldShieldMatrix.SetLeft( left ); worldShieldMatrix.SetForward( dir ); MatrixMultiply( parentInvMatrix, worldShieldMatrix, relativeMatrix ); MatrixToAngles( relativeMatrix, relAngles ); */ Vector offset( 0, 0, SHIELD_GRENADE_HEIGHT * 0.5f ); m_hDeployedShield = CreateFlatShield( this, SHIELD_GRENADE_WIDTH, SHIELD_GRENADE_HEIGHT, offset, vec3_angle ); // Notify it about EMP state if (m_IsEMPed) m_hDeployedShield->SetEMPed(true); // Play a sound // WeaponSound( SPECIAL1 ); } //----------------------------------------------------------------------------- // Purpose: Play beeping sounds until the charge explode //----------------------------------------------------------------------------- void CShieldGrenade::BeepThink( void ) { if (!IsInWorld()) { UTIL_Remove( this ); return; } if (m_flDetonateTime <= gpGlobals->curtime) { // Here we must project the shield CreateShield(); SetThink( ShieldActiveThink ); m_flDetonateTime = gpGlobals->curtime + m_ShieldLifetime; // Get the EMP state correct CheckEMPDamageFinish(); ComputeActiveThinkTime(); } else { SetNextThink( gpGlobals->curtime + 1.0f ); } } //----------------------------------------------------------------------------- // Compute next think time while active //----------------------------------------------------------------------------- void CShieldGrenade::ComputeActiveThinkTime( void ) { // Next think should be when we detonate, unless we un-EMP before then SetNextThink( gpGlobals->curtime + m_flDetonateTime ); if (m_IsEMPed) { Assert( m_flEMPDamageEndTime != 0.0f ); if ( m_flEMPDamageEndTime < GetNextThink() ) SetNextThink( gpGlobals->curtime + m_flEMPDamageEndTime ); } } //----------------------------------------------------------------------------- // Purpose: Here's where the grenade "detonates" //----------------------------------------------------------------------------- void CShieldGrenade::ShieldActiveThink( void ) { if (m_flDetonateTime > gpGlobals->curtime) { // If it's not time to die, check EMP state CheckEMPDamageFinish(); } else { if (m_hDeployedShield) { m_hDeployedShield->Activate( false ); } SetNextThink( gpGlobals->curtime + SHIELD_FLAT_SHUTDOWN_TIME + 0.2f ); SetThink( DeathThink ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CShieldGrenade::DeathThink( void ) { // kill the grenade if (m_hDeployedShield) { UTIL_Remove( m_hDeployedShield ); m_hDeployedShield.Set(0); } UTIL_Remove( this ); } //----------------------------------------------------------------------------- // Creates a shield grenade //----------------------------------------------------------------------------- CBaseEntity *CreateShieldGrenade( const Vector &position, const QAngle &angles, const Vector &velocity, const QAngle &angVelocity, CBaseEntity *pOwner, float timer ) { CShieldGrenade *pGrenade = (CShieldGrenade *)CBaseEntity::Create( "grenade_shield", position, angles, pOwner ); pGrenade->SetLifetime( timer ); pGrenade->SetAbsVelocity( velocity ); if (pOwner) { pGrenade->ChangeTeam( pOwner->GetTeamNumber() ); } return pGrenade; }