//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "weapon_physcannon.h" #include "hl2_player.h" #include "saverestore_utlvector.h" #include "triggers.h" //----------------------------------------------------------------------------- // Weapon-dissolve trigger; all weapons in this field (sans the physcannon) are destroyed! //----------------------------------------------------------------------------- class CTriggerWeaponDissolve : public CTriggerMultiple { DECLARE_CLASS( CTriggerWeaponDissolve, CTriggerMultiple ); DECLARE_DATADESC(); public: ~CTriggerWeaponDissolve( void ); virtual void Spawn( void ); virtual void Precache( void ); virtual void Activate( void ); virtual void StartTouch( CBaseEntity *pOther ); inline bool HasWeapon( CBaseCombatWeapon *pWeapon ); Vector GetConduitPoint( CBaseEntity *pTarget ); void InputStopSound( inputdata_t &inputdata ); void AddWeapon( CBaseCombatWeapon *pWeapon ); void CreateBeam( const Vector &vecSource, CBaseEntity *pDest, float flLifetime ); void DissolveThink( void ); private: COutputEvent m_OnDissolveWeapon; COutputEvent m_OnChargingPhyscannon; CUtlVector< CHandle > m_pWeapons; CUtlVector< CHandle > m_pConduitPoints; string_t m_strEmitterName; int m_spriteTexture; }; LINK_ENTITY_TO_CLASS( trigger_weapon_dissolve, CTriggerWeaponDissolve ); BEGIN_DATADESC( CTriggerWeaponDissolve ) DEFINE_KEYFIELD( m_strEmitterName, FIELD_STRING, "emittername" ), DEFINE_UTLVECTOR( m_pWeapons, FIELD_EHANDLE ), DEFINE_UTLVECTOR( m_pConduitPoints, FIELD_EHANDLE ), DEFINE_FIELD( m_spriteTexture, FIELD_MODELINDEX ), DEFINE_OUTPUT( m_OnDissolveWeapon, "OnDissolveWeapon" ), DEFINE_OUTPUT( m_OnChargingPhyscannon, "OnChargingPhyscannon" ), DEFINE_INPUTFUNC( FIELD_VOID, "StopSound", InputStopSound ), DEFINE_THINKFUNC( DissolveThink ), END_DATADESC() //----------------------------------------------------------------------------- // Destructor //----------------------------------------------------------------------------- CTriggerWeaponDissolve::~CTriggerWeaponDissolve( void ) { m_pWeapons.Purge(); m_pConduitPoints.Purge(); } //----------------------------------------------------------------------------- // Purpose: Call precache for our sprite texture //----------------------------------------------------------------------------- void CTriggerWeaponDissolve::Spawn( void ) { BaseClass::Spawn(); Precache(); } //----------------------------------------------------------------------------- // Purpose: Precache our sprite texture //----------------------------------------------------------------------------- void CTriggerWeaponDissolve::Precache( void ) { BaseClass::Precache(); m_spriteTexture = PrecacheModel( "sprites/lgtning.vmt" ); PrecacheScriptSound( "WeaponDissolve.Dissolve" ); PrecacheScriptSound( "WeaponDissolve.Charge" ); PrecacheScriptSound( "WeaponDissolve.Beam" ); } static const char *s_pDissolveThinkContext = "DissolveThinkContext"; //----------------------------------------------------------------------------- // Purpose: Collect all our known conduit points //----------------------------------------------------------------------------- void CTriggerWeaponDissolve::Activate( void ) { BaseClass::Activate(); CBaseEntity *pEntity = NULL; while ( ( pEntity = gEntList.FindEntityByName( pEntity, m_strEmitterName ) ) != NULL ) { m_pConduitPoints.AddToTail( pEntity ); } SetContextThink( &CTriggerWeaponDissolve::DissolveThink, gpGlobals->curtime + 0.1f, s_pDissolveThinkContext ); } //----------------------------------------------------------------------------- // Purpose: Checks to see if a weapon is already known // Input : *pWeapon - weapon to check for // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CTriggerWeaponDissolve::HasWeapon( CBaseCombatWeapon *pWeapon ) { if ( m_pWeapons.Find( pWeapon ) == m_pWeapons.InvalidIndex() ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: Adds a weapon to the known weapon list // Input : *pWeapon - weapon to add //----------------------------------------------------------------------------- void CTriggerWeaponDissolve::AddWeapon( CBaseCombatWeapon *pWeapon ) { if ( HasWeapon( pWeapon ) ) return; m_pWeapons.AddToTail( pWeapon ); } //----------------------------------------------------------------------------- // Purpose: Collect any weapons inside our volume // Input : *pOther - //----------------------------------------------------------------------------- void CTriggerWeaponDissolve::StartTouch( CBaseEntity *pOther ) { BaseClass::StartTouch( pOther ); if ( PassesTriggerFilters( pOther ) == false ) return; CBaseCombatWeapon *pWeapon = dynamic_cast(pOther); if ( pWeapon == NULL ) return; AddWeapon( pWeapon ); } //----------------------------------------------------------------------------- // Purpose: Creates a beam between a conduit point and a weapon // Input : &vecSource - conduit point // *pDest - weapon // flLifetime - amount of time //----------------------------------------------------------------------------- void CTriggerWeaponDissolve::CreateBeam( const Vector &vecSource, CBaseEntity *pDest, float flLifetime ) { CBroadcastRecipientFilter filter; te->BeamEntPoint( filter, 0.0, 0, &vecSource, pDest->entindex(), &(pDest->WorldSpaceCenter()), m_spriteTexture, 0, // No halo 1, // Frame 30, flLifetime, 16.0f, // Start width 4.0f, // End width 0, // No fade 8, // Amplitude 255, 255, 255, 255, 16 ); // Speed } //----------------------------------------------------------------------------- // Purpose: Returns the closest conduit point to a weapon // Input : *pTarget - weapon to check for // Output : Vector - position of the conduit //----------------------------------------------------------------------------- Vector CTriggerWeaponDissolve::GetConduitPoint( CBaseEntity *pTarget ) { float nearDist = 9999999.0f; Vector bestPoint = vec3_origin; float testDist; // Find the nearest conduit to the target for ( int i = 0; i < m_pConduitPoints.Count(); i++ ) { testDist = ( m_pConduitPoints[i]->GetAbsOrigin() - pTarget->GetAbsOrigin() ).LengthSqr(); if ( testDist < nearDist ) { bestPoint = m_pConduitPoints[i]->GetAbsOrigin(); nearDist = testDist; } } return bestPoint; } //----------------------------------------------------------------------------- // Purpose: Dissolve all weapons within our volume //----------------------------------------------------------------------------- void CTriggerWeaponDissolve::DissolveThink( void ) { int numWeapons = m_pWeapons.Count(); // Dissolve all the items within the volume for ( int i = 0; i < numWeapons; i++ ) { CBaseCombatWeapon *pWeapon = m_pWeapons[i]; Vector vecConduit = GetConduitPoint( pWeapon ); // The physcannon upgrades when this happens if ( FClassnameIs( pWeapon, "weapon_physcannon" ) ) { // This must be the last weapon for us to care if ( numWeapons > 1 ) continue; //FIXME: Make them do this on a stagger! // All conduits send power to the weapon for ( int i = 0; i < m_pConduitPoints.Count(); i++ ) { CreateBeam( m_pConduitPoints[i]->GetAbsOrigin(), pWeapon, 4.0f ); } PhysCannonBeginUpgrade( pWeapon ); m_OnChargingPhyscannon.FireOutput( this, this ); EmitSound( "WeaponDissolve.Beam" ); // We're done m_pWeapons.Purge(); m_pConduitPoints.Purge(); SetContextThink( NULL, 0, s_pDissolveThinkContext ); return; } // Randomly dissolve them all float flLifetime = random->RandomFloat( 2.5f, 4.0f ); CreateBeam( vecConduit, pWeapon, flLifetime ); pWeapon->Dissolve( NULL, gpGlobals->curtime + ( 3.0f - flLifetime ), false ); m_OnDissolveWeapon.FireOutput( this, this ); CPASAttenuationFilter filter( pWeapon ); EmitSound( filter, pWeapon->entindex(), "WeaponDissolve.Dissolve" ); // Beam looping sound EmitSound( "WeaponDissolve.Beam" ); m_pWeapons.Remove( i ); SetContextThink( &CTriggerWeaponDissolve::DissolveThink, gpGlobals->curtime + random->RandomFloat( 0.5f, 1.5f ), s_pDissolveThinkContext ); return; } SetContextThink( &CTriggerWeaponDissolve::DissolveThink, gpGlobals->curtime + 0.1f, s_pDissolveThinkContext ); } //----------------------------------------------------------------------------- // Purpose: // Input : &inputdata - //----------------------------------------------------------------------------- void CTriggerWeaponDissolve::InputStopSound( inputdata_t &inputdata ) { StopSound( "WeaponDissolve.Beam" ); StopSound( "WeaponDissolve.Charge" ); } //----------------------------------------------------------------------------- // Weapon-strip trigger; can't pick up weapons while in the field //----------------------------------------------------------------------------- class CTriggerWeaponStrip : public CTriggerMultiple { DECLARE_CLASS( CTriggerWeaponStrip, CTriggerMultiple ); DECLARE_DATADESC(); public: void StartTouch(CBaseEntity *pOther); void EndTouch(CBaseEntity *pOther); private: bool m_bKillWeapons; }; //----------------------------------------------------------------------------- // Save/load //----------------------------------------------------------------------------- LINK_ENTITY_TO_CLASS( trigger_weapon_strip, CTriggerWeaponStrip ); BEGIN_DATADESC( CTriggerWeaponStrip ) DEFINE_KEYFIELD( m_bKillWeapons, FIELD_BOOLEAN, "KillWeapons" ), END_DATADESC() //----------------------------------------------------------------------------- // Drops all weapons, marks the character as not being able to pick up weapons //----------------------------------------------------------------------------- void CTriggerWeaponStrip::StartTouch(CBaseEntity *pOther) { BaseClass::StartTouch( pOther ); if ( PassesTriggerFilters(pOther) == false ) return; CBaseCombatCharacter *pCharacter = pOther->MyCombatCharacterPointer(); if ( m_bKillWeapons ) { for ( int i = 0 ; i < pCharacter->WeaponCount(); ++i ) { CBaseCombatWeapon *pWeapon = pCharacter->GetWeapon( i ); if ( !pWeapon ) continue; pCharacter->Weapon_Drop( pWeapon ); UTIL_Remove( pWeapon ); } return; } // Strip the player of his weapons if ( pCharacter && pCharacter->IsAllowedToPickupWeapons() ) { CBaseCombatWeapon *pBugbait = pCharacter->Weapon_OwnsThisType( "weapon_bugbait" ); if ( pBugbait ) { pCharacter->Weapon_Drop( pBugbait ); UTIL_Remove( pBugbait ); } pCharacter->Weapon_DropAll( true ); pCharacter->SetPreventWeaponPickup( true ); } } //----------------------------------------------------------------------------- // Purpose: Called when an entity stops touching us. // Input : pOther - The entity that was touching us. //----------------------------------------------------------------------------- void CTriggerWeaponStrip::EndTouch(CBaseEntity *pOther) { if ( IsTouching( pOther ) ) { CBaseCombatCharacter *pCharacter = pOther->MyCombatCharacterPointer(); if ( pCharacter ) { pCharacter->SetPreventWeaponPickup( false ); } } BaseClass::EndTouch( pOther ); } //----------------------------------------------------------------------------- // Teleport trigger //----------------------------------------------------------------------------- class CTriggerPhysicsTrap : public CTriggerMultiple { DECLARE_CLASS( CTriggerPhysicsTrap, CTriggerMultiple ); DECLARE_DATADESC(); public: void Touch( CBaseEntity *pOther ); private: void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); void InputToggle( inputdata_t &inputdata ); int m_nDissolveType; }; //----------------------------------------------------------------------------- // Save/load //----------------------------------------------------------------------------- LINK_ENTITY_TO_CLASS( trigger_physics_trap, CTriggerPhysicsTrap ); BEGIN_DATADESC( CTriggerPhysicsTrap ) DEFINE_KEYFIELD( m_nDissolveType, FIELD_INTEGER, "dissolvetype" ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), END_DATADESC() //------------------------------------------------------------------------------ // Inputs //------------------------------------------------------------------------------ void CTriggerPhysicsTrap::InputToggle( inputdata_t &inputdata ) { if ( m_bDisabled ) { InputEnable( inputdata ); } else { InputDisable( inputdata ); } } void CTriggerPhysicsTrap::InputEnable( inputdata_t &inputdata ) { if ( m_bDisabled ) { Enable(); } } void CTriggerPhysicsTrap::InputDisable( inputdata_t &inputdata ) { if ( !m_bDisabled ) { Disable(); } } //----------------------------------------------------------------------------- // Traps the entities //----------------------------------------------------------------------------- #define JOINTS_TO_CONSTRAIN 1 void CTriggerPhysicsTrap::Touch( CBaseEntity *pOther ) { if ( !PassesTriggerFilters(pOther) ) return; CBaseAnimating *pAnim = pOther->GetBaseAnimating(); if ( !pAnim ) return; #ifdef HL2_DLL // HACK: Upgrade the physcannon if ( FClassnameIs( pAnim, "weapon_physcannon" ) ) { PhysCannonBeginUpgrade( pAnim ); return; } #endif pAnim->Dissolve( NULL, gpGlobals->curtime, false, m_nDissolveType ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CWateryDeathLeech : public CBaseAnimating { DECLARE_CLASS( CWateryDeathLeech, CBaseAnimating ); public: DECLARE_DATADESC(); void Spawn( void ); void Precache( void ); void LeechThink( void ); int m_iFadeState; }; LINK_ENTITY_TO_CLASS( ent_watery_leech, CWateryDeathLeech ); BEGIN_DATADESC( CWateryDeathLeech ) DEFINE_THINKFUNC( LeechThink ), DEFINE_FIELD( m_iFadeState, FIELD_INTEGER ), END_DATADESC() void CWateryDeathLeech::Precache( void ) { //Ugh this is temporary until Jakob finishes the animations and doesn't need the command anymore. bool allowPrecache = CBaseEntity::IsPrecacheAllowed(); CBaseEntity::SetAllowPrecache( true ); BaseClass::Precache(); PrecacheModel( "models/leech.mdl" ); CBaseEntity::SetAllowPrecache( allowPrecache ); } void CWateryDeathLeech::Spawn( void ) { Precache(); BaseClass::Spawn(); SetSolid ( SOLID_NONE ); SetMoveType( MOVETYPE_NONE ); AddEffects( EF_NOSHADOW ); SetModel( "models/leech.mdl" ); SetThink( &CWateryDeathLeech::LeechThink ); SetNextThink( gpGlobals->curtime + 0.1 ); m_flPlaybackRate = random->RandomFloat( 0.5, 1.5 ); SetCycle( random->RandomFloat( 0.0f, 0.9f ) ); QAngle vAngle; vAngle[YAW] = random->RandomFloat( 0, 360 ); SetAbsAngles( vAngle ); m_iFadeState = 1; SetRenderColorA( 1 ); } void CWateryDeathLeech::LeechThink( void ) { if ( IsMarkedForDeletion() ) return; StudioFrameAdvance(); SetNextThink( gpGlobals->curtime + 0.1 ); if ( m_iFadeState != 0 ) { float dt = gpGlobals->frametime; if ( dt > 0.1f ) { dt = 0.1f; } m_nRenderMode = kRenderTransTexture; int speed = MAX(1,256*dt); // fade out over 1 second if ( m_iFadeState == -1 ) SetRenderColorA( UTIL_Approach( 0, m_clrRender->a, speed ) ); else SetRenderColorA( UTIL_Approach( 255, m_clrRender->a, speed ) ); if ( m_clrRender->a == 0 ) { UTIL_Remove(this); } else if ( m_clrRender->a == 255 ) { m_iFadeState = 0; } else { SetNextThink( gpGlobals->curtime ); } } if ( GetOwnerEntity() ) { if ( GetOwnerEntity()->GetWaterLevel() < 3 ) { AddEffects( EF_NODRAW ); } else { RemoveEffects( EF_NODRAW ); } SetAbsOrigin( GetOwnerEntity()->GetAbsOrigin() + GetOwnerEntity()->GetViewOffset() ); } } class CTriggerWateryDeath : public CBaseTrigger { DECLARE_CLASS( CTriggerWateryDeath, CBaseTrigger ); public: DECLARE_DATADESC(); void Spawn( void ); void Precache( void ); void Touch( CBaseEntity *pOther ); void SpawnLeeches( CBaseEntity *pOther ); // Ignore non-living entities virtual bool PassesTriggerFilters(CBaseEntity *pOther) { if ( !BaseClass::PassesTriggerFilters(pOther) ) return false; return (pOther->m_takedamage == DAMAGE_YES); } virtual void StartTouch(CBaseEntity *pOther); virtual void EndTouch(CBaseEntity *pOther); private: CUtlVector< EHANDLE > m_hLeeches; // Kill times for entities I'm touching CUtlVector< float > m_flEntityKillTimes; float m_flNextPullSound; float m_flPainValue; }; BEGIN_DATADESC( CTriggerWateryDeath ) DEFINE_UTLVECTOR( m_flEntityKillTimes, FIELD_TIME ), DEFINE_UTLVECTOR( m_hLeeches, FIELD_EHANDLE ), DEFINE_FIELD( m_flNextPullSound, FIELD_TIME ), DEFINE_FIELD( m_flPainValue, FIELD_FLOAT ), END_DATADESC() LINK_ENTITY_TO_CLASS( trigger_waterydeath, CTriggerWateryDeath ); // Stages of the waterydeath trigger, in time offsets from the initial touch #define WD_KILLTIME_NEXT_BITE 0.3 #define WD_PAINVALUE_STEP 2.0 #define WD_MAX_DAMAGE 15.0f //----------------------------------------------------------------------------- // Purpose: Called when spawning, after keyvalues have been handled. //----------------------------------------------------------------------------- void CTriggerWateryDeath::Spawn( void ) { BaseClass::Spawn(); Precache(); m_flNextPullSound = 0; m_flPainValue = 0; InitTrigger(); } void CTriggerWateryDeath::Precache( void ) { //Ugh this is temporary until Jakob finishes the animations and doesn't need the command anymore. BaseClass::Precache(); PrecacheModel( "models/leech.mdl" ); PrecacheScriptSound( "coast.leech_bites_loop" ); PrecacheScriptSound( "coast.leech_water_churn_loop" ); } void CTriggerWateryDeath::SpawnLeeches( CBaseEntity *pOther ) { if ( pOther == NULL ) return; if ( m_hLeeches.Count() > 0 ) return; int iMaxLeeches = 12; for ( int i = 0; i < iMaxLeeches; i++ ) { CWateryDeathLeech *pLeech = (CWateryDeathLeech*)CreateEntityByName( "ent_watery_leech" ); if ( pLeech ) { m_hLeeches.AddToTail( pLeech ); pLeech->Spawn(); pLeech->SetAbsOrigin( pOther->GetAbsOrigin() ); pLeech->SetOwnerEntity( pOther ); if ( i <= 8 ) pLeech->SetSequence( i % 4 ); else pLeech->SetSequence( ( i % 4 ) + 4 ) ; pLeech->ResetSequenceInfo(); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTriggerWateryDeath::Touch( CBaseEntity *pOther ) { if (!PassesTriggerFilters(pOther)) return; // Find our index EHANDLE hOther; hOther = pOther; int iIndex = m_hTouchingEntities.Find( hOther ); if ( iIndex == m_hTouchingEntities.InvalidIndex() ) return; float flKillTime = m_flEntityKillTimes[iIndex]; // Time to kill it? if ( gpGlobals->curtime > flKillTime ) { //EmitSound( filter, entindex(), "WateryDeath.Bite", &pOther->GetAbsOrigin() ); // Kill it if ( pOther->IsPlayer() ) { m_flPainValue = MIN( m_flPainValue + WD_PAINVALUE_STEP, WD_MAX_DAMAGE ); } else { m_flPainValue = WD_MAX_DAMAGE; } // Use DMG_GENERIC & make the target inflict the damage on himself. // This ensures that if the target is the player, the damage isn't modified by skill CTakeDamageInfo info = CTakeDamageInfo( pOther, pOther, m_flPainValue, DMG_GENERIC ); GuessDamageForce( &info, (pOther->GetAbsOrigin() - GetAbsOrigin()), pOther->GetAbsOrigin() ); pOther->TakeDamage( info ); m_flEntityKillTimes[iIndex] = gpGlobals->curtime + WD_KILLTIME_NEXT_BITE; } } //----------------------------------------------------------------------------- // Purpose: Called when an entity starts touching us. // Input : pOther - The entity that is touching us. //----------------------------------------------------------------------------- void CTriggerWateryDeath::StartTouch(CBaseEntity *pOther) { BaseClass::StartTouch( pOther ); m_flPainValue = 0.0f; // If we added him to our list, store the start time EHANDLE hOther; hOther = pOther; if ( m_hTouchingEntities.Find( hOther ) != m_hTouchingEntities.InvalidIndex() ) { // Always added to the end // Players get warned, everything else gets et quick. if ( pOther->IsPlayer() ) { m_flEntityKillTimes.AddToTail( gpGlobals->curtime + WD_KILLTIME_NEXT_BITE ); } else { m_flEntityKillTimes.AddToTail( gpGlobals->curtime + WD_KILLTIME_NEXT_BITE ); } } #ifdef HL2_DLL if ( pOther->IsPlayer() ) { SpawnLeeches( pOther ); CHL2_Player *pHL2Player = dynamic_cast( pOther ); if ( pHL2Player ) { pHL2Player->StartWaterDeathSounds(); } } #endif } //----------------------------------------------------------------------------- // Purpose: Called when an entity stops touching us. // Input : pOther - The entity that was touching us. //----------------------------------------------------------------------------- void CTriggerWateryDeath::EndTouch( CBaseEntity *pOther ) { if ( IsTouching( pOther ) ) { EHANDLE hOther; hOther = pOther; // Remove the time from our list int iIndex = m_hTouchingEntities.Find( hOther ); if ( iIndex != m_hTouchingEntities.InvalidIndex() ) { m_flEntityKillTimes.Remove( iIndex ); } } #ifdef HL2_DLL if ( pOther->IsPlayer() ) { for (int i = 0; i < m_hLeeches.Count(); i++ ) { CWateryDeathLeech *pLeech = dynamic_cast( m_hLeeches[i].Get() ); if ( pLeech ) { pLeech->m_iFadeState = -1; } } if ( m_hLeeches.Count() > 0 ) m_hLeeches.Purge(); CHL2_Player *pHL2Player = dynamic_cast( pOther ); if ( pHL2Player ) { //Adrian: Hi, you might be wondering why I'm doing this, yes? // Well, EndTouch is called not only when the player leaves // the trigger, but also on level shutdown. We can't let the // soundpatch fade the sound out since we'll hit a nasty assert // cause it'll try to fade out a sound using an entity that might // be gone since we're shutting down the server. if ( !(pHL2Player->GetFlags() & FL_DONTTOUCH ) ) pHL2Player->StopWaterDeathSounds(); } } #endif BaseClass::EndTouch( pOther ); } //----------------------------------------------------------------------------- // Purpose: Triggers whenever an RPG is fired within it //----------------------------------------------------------------------------- class CTriggerRPGFire : public CTriggerMultiple { DECLARE_CLASS( CTriggerRPGFire, CTriggerMultiple ); public: ~CTriggerRPGFire(); void Spawn( void ); void OnRestore( void ); }; LINK_ENTITY_TO_CLASS( trigger_rpgfire, CTriggerRPGFire ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTriggerRPGFire::~CTriggerRPGFire( void ) { g_hWeaponFireTriggers.FindAndRemove( this ); } //----------------------------------------------------------------------------- // Purpose: Called when spawning, after keyvalues have been handled. //----------------------------------------------------------------------------- void CTriggerRPGFire::Spawn( void ) { BaseClass::Spawn(); InitTrigger(); g_hWeaponFireTriggers.AddToTail( this ); // Stomp the touch function, because we don't want to respond to touch SetTouch( NULL ); } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CTriggerRPGFire::OnRestore() { BaseClass::OnRestore(); g_hWeaponFireTriggers.AddToTail( this ); }