//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "tf_player.h" #include "tf_basecombatweapon.h" #include "basegrenade_shared.h" #include "weapon_limpetmine.h" #include "engine/IEngineSound.h" #include "grenade_limpetmine.h" #include "tf_shareddefs.h" #include "IEffects.h" #include "player.h" #include "basetfvehicle.h" #define LIMPET_LIVE_TIME 0.5 // Time it takes before a limpet can be detonated after placement #define LIMPET_LIFETIME 120 // After this time, limpets fizzle naturally #define LIMPET_FIZZLE_DURATION 2.0f #define LIMPET_MINS Vector(-5, -5, 0) #define LIMPET_MAXS Vector( 5, 5, 10) // Damage CVars ConVar weapon_limpetmine_grenade_damage( "weapon_limpetmine_grenade_damage","0", FCVAR_NONE, "Limpet Mine's grenade maximum damage" ); ConVar weapon_limpetmine_grenade_radius( "weapon_limpetmine_grenade_radius","0", FCVAR_NONE, "Limpet Mine's grenade splash radius" ); // Global Savedata for friction modifier BEGIN_DATADESC( CLimpetMine ) // Function Pointers DEFINE_THINKFUNC( LiveThink ), DEFINE_ENTITYFUNC( StickyTouch ), DEFINE_THINKFUNC( LimpetThink ), END_DATADESC() IMPLEMENT_SERVERCLASS_ST(CLimpetMine, DT_LimpetMine) SendPropInt(SENDINFO(m_bLive), 1, SPROP_UNSIGNED ), END_SEND_TABLE() LINK_ENTITY_TO_CLASS( grenade_limpetmine, CLimpetMine ); //----------------------------------------------------------------------------- // Static initializers: //----------------------------------------------------------------------------- CLimpetMine* CLimpetMine::allLimpets = NULL; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CLimpetMine::CLimpetMine( void ) { UseClientSideAnimation(); // --------------------------------- // Add to linked list of limpets // --------------------------------- nextLimpet = CLimpetMine::allLimpets; CLimpetMine::allLimpets = this; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CLimpetMine::~CLimpetMine( void ) { // -------------------------------------- // Remove from linked list of limpets // -------------------------------------- CLimpetMine *pLimpet = CLimpetMine::allLimpets; if (pLimpet == this) { CLimpetMine::allLimpets = pLimpet->nextLimpet; } else { while (pLimpet) { if (pLimpet->nextLimpet == this) { pLimpet->nextLimpet = pLimpet->nextLimpet->nextLimpet; break; } pLimpet = pLimpet->nextLimpet; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CLimpetMine::Precache( void ) { PrecacheModel( "models/projectiles/grenade_limpet.mdl" ); PrecacheScriptSound( "LimpetMine.Beep" ); PrecacheScriptSound( "LimpetMine.Fizzle" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CLimpetMine::Spawn( void ) { SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); SetSolid( SOLID_BBOX ); SetGravity( 1.0 ); SetFriction( 1.25 ); SetModel( "models/projectiles/grenade_limpet.mdl"); UTIL_SetSize( this, LIMPET_MINS, LIMPET_MAXS ); m_bLive = false; m_bFizzleInit = false; m_bEMPed = false; SetThink( LiveThink ); SetNextThink( gpGlobals->curtime + LIMPET_LIVE_TIME ); SetTouch( StickyTouch ); // Causes these to collide with everything but NPCs and players SetCollisionGroup( TFCOLLISION_GROUP_GRENADE ); AddFlag( FL_OBJECT ); // Prevent sentry guns detecting these. AddFlag( FL_NOTARGET ); // Set my damages to the cvar values SetDamage( weapon_limpetmine_grenade_damage.GetFloat() ); SetDamageRadius( weapon_limpetmine_grenade_radius.GetFloat() ); } //----------------------------------------------------------------------------- // Purpose: Return true if this limpet mine can be detonated yet //----------------------------------------------------------------------------- bool CLimpetMine::IsLive( void ) { return m_bLive; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CLimpetMine::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { Assert( pCaller ); // USE_SET Means we're being asked to fizzle out and dielf if ( useType == USE_SET ) { if ( !m_bFizzleInit ) { // Set the defuse - fizzle think m_flFizzleDuration = gpGlobals->curtime + 0.3; SetThink( LimpetThink ); SetNextThink( gpGlobals->curtime + 0.1f ); m_bFizzleInit = true; } } else if ( IsLive() ) { // Get the TF2 player that owns this limpet: CBaseTFPlayer *pPlayer = NULL; if ( m_hLauncher ) { pPlayer = ToBaseTFPlayer( m_hLauncher->GetOwner() ); } // Get the TF2 player that is calling this object, if any: CBaseTFPlayer *pCallerPlayer = NULL; if ( pCaller ) { pCallerPlayer = ToBaseTFPlayer( pCaller ); } // If the owning player is directly using the limpet, then pick it up: if( pPlayer && pCallerPlayer && pPlayer->IsSameClass( pCallerPlayer ) ) { if ( m_hLauncher ) { pPlayer->GiveAmmo( 1, m_hLauncher->m_iPrimaryAmmoType ); m_hLauncher->DecrementLimpets(); } UTIL_Remove( this ); } else if ( pActivator && !pActivator->InSameTeam( this ) ) { // only the owning player can detonate his own limpets, so return if this isn't the owner. return; } // We're being detonated // If are EMPed then we cannot be detonated. else if ( !IsEMPed() ) { // Beep and detonate soon afterwards EmitSound( "LimpetMine.Beep" ); SetThink( Detonate ); SetNextThink( gpGlobals->curtime + 0.5f ); // Pretend I'm not live anymore so I don't get exploded again m_bLive = false; if ( m_hLauncher ) { m_hLauncher->DecrementLimpets(); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CLimpetMine::TakeEMPDamage( float duration ) { if ( !m_bFizzleInit ) { // Set the defuse - fizzle think float flDuration = MIN( duration, LIMPET_FIZZLE_DURATION ); m_flFizzleDuration = gpGlobals->curtime + ( flDuration - 1.0f ); SetThink( LimpetThink ); SetNextThink( gpGlobals->curtime + 0.1f ); m_bFizzleInit = true; m_bEMPed = true; } return true; } //----------------------------------------------------------------------------- // Purpose: Create a limpet mine //----------------------------------------------------------------------------- CLimpetMine* CLimpetMine::Create( const Vector &vecOrigin, const Vector &vecForward, CBasePlayer *pOwner ) { CLimpetMine *pGrenade = (CLimpetMine*)CreateEntityByName("grenade_limpetmine"); pGrenade->Teleport( &vecOrigin, NULL, NULL ); pGrenade->Spawn(); pGrenade->SetOwnerEntity( pOwner ); pGrenade->SetThrower( pOwner ); pGrenade->SetAbsVelocity( vecForward ); pGrenade->ChangeTeam( pOwner->GetTeamNumber() ); return pGrenade; } //----------------------------------------------------------------------------- // Purpose: Keep a pointer to the launcher (parent) //----------------------------------------------------------------------------- void CLimpetMine::SetLauncher( CWeaponLimpetmine *pLauncher ) { m_hLauncher = pLauncher; } //----------------------------------------------------------------------------- // Purpose: Go Live //----------------------------------------------------------------------------- void CLimpetMine::LiveThink( void ) { m_bLive = true; // Remove myself after a while m_flFizzleDuration = gpGlobals->curtime + LIMPET_LIFETIME + 0.3; SetNextThink( gpGlobals->curtime + 0.1f ); SetThink( LimpetThink ); } //----------------------------------------------------------------------------- // Purpose: Make the grenade stick to whatever it touches //----------------------------------------------------------------------------- void CLimpetMine::StickyTouch( CBaseEntity *pOther ) { Assert( pOther ); if ( !pOther->IsSolid() ) return; if ( !pOther->IsBSPModel() && !pOther->GetBaseAnimating() ) return; BounceSound(); m_bStuckToTarget = false; // Bounce off of shields if ( pOther->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD ) { // Move away from the shield... // Fling it out a little extra along the plane normal Vector vecNewVelocity; Vector vecCenter; AngleVectors( pOther->GetAbsAngles(), &vecCenter ); VectorMultiply( vecCenter, 400.0f, vecNewVelocity ); SetAbsVelocity( vecNewVelocity ); return; } // Only stick to non-moving entities if ( !pOther->GetBaseAnimating() ) return; // Don't stick to team members if ( InSameTeam( pOther ) ) return; // ROBIN: Removed stick to enemies for now { SetAbsVelocity( vec3_origin ); SetMoveType( MOVETYPE_NONE ); return; } m_bStuckToTarget = true; // Orient to stick to the wall I just hit trace_t tr; Vector vecPrev = GetLocalOrigin() - (GetAbsVelocity() * 0.1); UTIL_TraceLine( vecPrev, vecPrev + (GetAbsVelocity() * 2), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction != 1 ) { // Orient the *up* axis to be along the plane normal Vector perp( 1, 0, 0 ); Vector forward, right; CrossProduct( perp, tr.plane.normal, forward ); if (forward.LengthSqr() < 0.1f) { perp.Init( 0, 1, 0 ); CrossProduct( perp, tr.plane.normal, forward ); } VectorNormalize( forward ); CrossProduct( tr.plane.normal, forward, right ); VMatrix orientation( forward, right, tr.plane.normal ); QAngle angles; MatrixToAngles( orientation, angles ); SetAbsAngles( angles ); } SetAbsVelocity( vec3_origin ); SetMoveType( MOVETYPE_NONE ); // At this point, it shouldn't affect player movement SetCollisionGroup( COLLISION_GROUP_DEBRIS ); BounceSound(); SetParent( pOther ); } //----------------------------------------------------------------------------- // Purpose: Once the limpet's active, it starts running this //----------------------------------------------------------------------------- void CLimpetMine::LimpetThink( void ) { SetNextThink( gpGlobals->curtime + 0.1f ); // If I'm not ready to fizzle yet, make sure my parent's still there. if ( m_bStuckToTarget ) { // Lost our parent? if ( !GetMoveParent() ) { m_bStuckToTarget = false; // Fall to the ground SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM ); SetTouch( StickyTouch ); } } float flDeltaTime = m_flFizzleDuration - gpGlobals->curtime; // Not ready to fizzle yet? if ( flDeltaTime > 0.3 ) return; // Start fizzling if ( flDeltaTime > 0.0f ) { // Emit a fizzle sound EmitSound( "LimpetMine.Fizzle" ); g_pEffects->Sparks( GetAbsOrigin() ); // Smoke. UTIL_Smoke( GetAbsOrigin(), random->RandomInt( 1, 3), 10 ); } else { // Done fizzling - no more sound. StopSound( "LimpetMine.Fizzle" ); UTIL_Remove( this ); // Remove this limpet mine from the launcher deployment count. if ( m_hLauncher ) { m_hLauncher->DecrementLimpets(); } return; } }