summaryrefslogtreecommitdiff
path: root/game/shared/tf2/weapon_flame_thrower.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf2/weapon_flame_thrower.cpp')
-rw-r--r--game/shared/tf2/weapon_flame_thrower.cpp434
1 files changed, 434 insertions, 0 deletions
diff --git a/game/shared/tf2/weapon_flame_thrower.cpp b/game/shared/tf2/weapon_flame_thrower.cpp
new file mode 100644
index 0000000..9d98227
--- /dev/null
+++ b/game/shared/tf2/weapon_flame_thrower.cpp
@@ -0,0 +1,434 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "basetfplayer_shared.h"
+#include "in_buttons.h"
+#include "weapon_combatshield.h"
+#include "weapon_flame_thrower.h"
+#include "gasoline_shared.h"
+#include "ammodef.h"
+
+
+#define FLAME_THROWER_FIRE_INTERVAL 0.3 // Eject a fire blob entity this often.
+
+#define FLAMETHROWER_FLAME_DISTANCE 400.0
+
+#define FLAMETHROWER_FLAME_SPEED 500.0 //
+
+#define FLAMETHROWER_DAMAGE_PER_SEC 1000
+
+// How far the flame particles will spread from the center.
+#define FLAMETHROWER_SPREAD_ANGLE 15.0
+
+
+// ------------------------------------------------------------------------------------------------ //
+// Pretty little tables.
+// ------------------------------------------------------------------------------------------------ //
+
+#if defined( CLIENT_DLL )
+
+ #include "vstdlib/random.h"
+ #include "engine/IEngineSound.h"
+
+ #define FLAMETHROWER_PARTICLES_PER_SEC 100
+
+#else
+
+ #include "gasoline_blob.h"
+ #include "fire_damage_mgr.h"
+ #include "tf_gamerules.h"
+
+ #define FLAMETHROWER_DAMAGE_INTERVAL 0.2
+
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+PRECACHE_WEAPON_REGISTER( weapon_flame_thrower );
+
+LINK_ENTITY_TO_CLASS( weapon_flame_thrower, CWeaponFlameThrower );
+
+IMPLEMENT_NETWORKCLASS_ALIASED( WeaponFlameThrower, DT_WeaponFlameThrower )
+
+BEGIN_NETWORK_TABLE( CWeaponFlameThrower, DT_WeaponFlameThrower )
+ #if defined( CLIENT_DLL )
+ RecvPropInt( RECVINFO( m_bFiring ) )
+ #else
+ SendPropInt( SENDINFO( m_bFiring ), 1, SPROP_UNSIGNED )
+ #endif
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CWeaponFlameThrower )
+
+ DEFINE_PRED_FIELD( m_bFiring, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
+
+END_PREDICTION_DATA()
+
+
+static inline void GenerateRandomFlameThrowerVelocity( Vector &vOut, const Vector &vForward, const Vector &vRight, const Vector &vUp )
+{
+ static float radians = DEG2RAD( 90 - FLAMETHROWER_SPREAD_ANGLE );
+ static float v = cos( radians ) / sin( radians );
+
+ vOut = vForward + vRight * RandomFloat( -v, v ) + vUp * RandomFloat( -v, v );
+ VectorNormalize( vOut );
+}
+
+template< class T >
+int FindInArray( const T pTest, const T *pArray, int arrayLen )
+{
+ for ( int i=0; i < arrayLen; i++ )
+ {
+ if ( pTest == pArray[i] )
+ return i;
+ }
+ return -1;
+}
+
+
+// ------------------------------------------------------------------------------------------------ //
+// CWeaponFlameThrower implementation.
+// ------------------------------------------------------------------------------------------------ //
+
+CWeaponFlameThrower::CWeaponFlameThrower()
+{
+ InternalConstructor( false );
+}
+
+
+CWeaponFlameThrower::CWeaponFlameThrower( bool bCanister )
+{
+ InternalConstructor( bCanister );
+}
+
+void CWeaponFlameThrower::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "FlameThrower.Sound" );
+}
+
+void CWeaponFlameThrower::InternalConstructor( bool bCanister )
+{
+ m_bCanister = bCanister;
+
+ m_bFiring = false;
+ m_flNextPrimaryAttack = -1;
+
+ #if defined( CLIENT_DLL )
+ {
+ m_hFlameEmitter = CSimpleEmitter::Create( "flamethrower" );
+
+ m_hFireMaterial = INVALID_MATERIAL_HANDLE;
+ if ( IsGasCanister() )
+ {
+ m_hFireMaterial = m_hFlameEmitter->GetPMaterial( "particle/particle_noisesphere" );
+ }
+ else
+ {
+ m_hFireMaterial = m_hFlameEmitter->GetPMaterial( "particle/fire" );
+ }
+
+ m_FlameEvent.Init( FLAMETHROWER_PARTICLES_PER_SEC );
+
+ m_bSoundOn = false;
+ }
+ #endif
+}
+
+
+CWeaponFlameThrower::~CWeaponFlameThrower()
+{
+ #if defined( CLIENT_DLL )
+ StopFlameSound();
+ #endif
+}
+
+
+bool CWeaponFlameThrower::IsGasCanister() const
+{
+ return m_bCanister;
+}
+
+
+bool CWeaponFlameThrower::IsPredicted() const
+{
+ return true;
+}
+
+
+void CWeaponFlameThrower::ItemPostFrame()
+{
+ CBaseTFPlayer *pOwner = ToBaseTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+ if ( pOwner->IsAlive() &&
+ (pOwner->m_nButtons & IN_ATTACK) &&
+ GetShieldState() == SS_DOWN &&
+ GetPrimaryAmmo() > 2 )
+ {
+ PrimaryAttack();
+ m_bFiring = true;
+
+ // Prevent shield post frame if we're not ready to attack, or we're healing
+ AllowShieldPostFrame( false );
+ }
+ else
+ {
+ AllowShieldPostFrame( true );
+ m_flNextPrimaryAttack = -1;
+ m_bFiring = false;
+
+#if defined( CLIENT_DLL )
+#else
+ m_hPrevBlob = NULL;
+
+ // It's easy to lay down gasoline and forget to leave enough ammo to ignite it, so
+ // this allows the pyro to ignite any nearby gasoline blobs for free.
+ if ( !m_bCanister && GetPrimaryAmmo() <= 2 )
+ {
+ IgniteNearbyGasolineBlobs();
+ }
+#endif
+ }
+}
+
+
+void CWeaponFlameThrower::PrimaryAttack()
+{
+ #if defined( CLIENT_DLL )
+
+ #else
+
+ CBasePlayer *pOwner = ToBaseTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+ // Ok.. find eligible entities in a cone in front of us.
+ Vector vOrigin = pOwner->Weapon_ShootPosition( );
+ Vector vForward, vRight, vUp;
+ AngleVectors( pOwner->GetAbsAngles(), &vForward, &vRight, &vUp );
+
+ // Find some entities to burn.
+ CBaseEntity *pHitEnts[64];
+ int nHitEnts = 0;
+
+ #define NUM_TEST_VECTORS 30
+ for ( int iTest=0; iTest < NUM_TEST_VECTORS; iTest++ )
+ {
+ Vector vVel;
+ GenerateRandomFlameThrowerVelocity( vVel, vForward, vRight, vUp );
+
+ trace_t tr;
+ UTIL_TraceLine( vOrigin, vOrigin + vVel * FLAMETHROWER_FLAME_DISTANCE, MASK_SHOT & (~CONTENTS_HITBOX), NULL, COLLISION_GROUP_NONE, &tr );
+ if ( tr.m_pEnt )
+ {
+ if ( TFGameRules()->IsTraceBlockedByWorldOrShield( vOrigin, vOrigin + vVel * FLAMETHROWER_FLAME_DISTANCE, GetOwner(), DMG_BURN | DMG_PROBE, &tr ) == false )
+ {
+ CBaseEntity *pTestEnt = tr.m_pEnt;
+ if ( pTestEnt && IsBurnableEnt( pTestEnt, GetTeamNumber() ) )
+ {
+ if ( FindInArray( pTestEnt, pHitEnts, nHitEnts ) == -1 )
+ {
+ pHitEnts[nHitEnts++] = pTestEnt;
+ if ( nHitEnts >= ARRAYSIZE( pHitEnts ) )
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ for ( int iHitEnt=0; iHitEnt < nHitEnts; iHitEnt++ )
+ {
+ CBaseEntity *pEnt = pHitEnts[iHitEnt];
+
+ float flDist = (pEnt->GetAbsOrigin() - vOrigin).Length();
+ float flPercent = 1.0 - flDist / FLAMETHROWER_FLAME_DISTANCE;
+ if ( flPercent < 0.1 )
+ flPercent = 0.1;
+
+ float flDamage = flPercent * FLAMETHROWER_DAMAGE_PER_SEC;
+ GetFireDamageMgr()->AddDamage( pEnt, GetOwner(), flDamage, !IsGasolineBlob( pEnt ) );
+ }
+
+
+ // Drop a new petrol blob.
+ if ( gpGlobals->curtime >= m_flNextPrimaryAttack )
+ {
+ float flLifetime = MAX_LIT_GASOLINE_BLOB_LIFETIME;
+ if ( IsGasCanister() )
+ flLifetime = MAX_UNLIT_GASOLINE_BLOB_LIFETIME;
+
+ CGasolineBlob *pBlob = CGasolineBlob::Create( GetOwner(), vOrigin, vForward * FLAMETHROWER_FLAME_SPEED, false, FLAMETHROWER_FLAME_DISTANCE / FLAMETHROWER_FLAME_SPEED, flLifetime );
+ if ( pBlob )
+ {
+ if ( IsGasCanister() )
+ {
+ // Link the previous blob to this one.
+ pBlob->AddAutoBurnBlob( m_hPrevBlob );
+ if ( m_hPrevBlob.Get() )
+ m_hPrevBlob->AddAutoBurnBlob( pBlob );
+
+ m_hPrevBlob = pBlob;
+ }
+ else
+ {
+ pBlob->SetLit( true );
+ }
+ }
+
+ pOwner->RemoveAmmo( 2, m_iPrimaryAmmoType );
+
+ // Drop a blob every half second.
+ m_flNextPrimaryAttack = gpGlobals->curtime + FLAME_THROWER_FIRE_INTERVAL;
+ }
+
+ #endif
+}
+
+
+#if defined( CLIENT_DLL )
+
+ bool CWeaponFlameThrower::ShouldPredict()
+ {
+ if ( GetOwner() == C_BasePlayer::GetLocalPlayer() )
+ return true;
+
+ return BaseClass::ShouldPredict();
+ }
+
+
+ void CWeaponFlameThrower::NotifyShouldTransmit( ShouldTransmitState_t state )
+ {
+ BaseClass::NotifyShouldTransmit( state );
+
+ if ( state == SHOULDTRANSMIT_START )
+ {
+ SetNextClientThink( CLIENT_THINK_ALWAYS );
+ }
+ else if ( state == SHOULDTRANSMIT_END )
+ {
+ SetNextClientThink( CLIENT_THINK_NEVER );
+ StopFlameSound();
+ }
+ }
+
+ void CWeaponFlameThrower::ClientThink()
+ {
+ // Spew some particles out.
+ if ( !IsDormant() && IsCarrierAlive() && m_bFiring && m_hFlameEmitter.IsValid() )
+ {
+ unsigned char color[4] = { 255, 128, 0, 255 };
+ if ( IsGasCanister() )
+ {
+ color[0] = color[1] = color[2] = 200;
+ color[3] = 255;
+ }
+
+ StartSound();
+
+ Vector vForward, vUp, vRight, vOrigin;
+ QAngle vAngles;
+ GetShootPosition( vOrigin, vAngles );
+ AngleVectors( vAngles, &vForward, &vRight, &vUp );
+
+ // Spew out flame particles.
+ float dt = gpGlobals->frametime;
+ while ( m_FlameEvent.NextEvent( dt ) )
+ {
+ SimpleParticle *p = m_hFlameEmitter->AddSimpleParticle(
+ m_hFireMaterial,
+ vOrigin + RandomVector( -3, 3 ),
+ FLAMETHROWER_FLAME_DISTANCE / FLAMETHROWER_FLAME_SPEED, // lifetime,
+ 9 // size
+ );
+
+ if ( p )
+ {
+ p->m_uchColor[0] = color[0];
+ p->m_uchColor[1] = color[1];
+ p->m_uchColor[2] = color[2];
+ p->m_uchStartAlpha = color[3];
+ p->m_uchEndAlpha = 0;
+ GenerateRandomFlameThrowerVelocity( p->m_vecVelocity, vForward, vRight, vUp );
+ p->m_vecVelocity *= RandomFloat( FLAMETHROWER_FLAME_SPEED * 0.9, FLAMETHROWER_FLAME_SPEED * 1.1 );
+ }
+ }
+ }
+ else
+ {
+ StopFlameSound();
+ }
+ }
+
+
+ void CWeaponFlameThrower::StartSound()
+ {
+ if ( !m_bSoundOn )
+ {
+ CLocalPlayerFilter filter;
+ EmitSound( filter, entindex(), "FlameThrower.Sound" );
+
+ m_bSoundOn = true;
+ }
+ }
+
+
+ void CWeaponFlameThrower::StopFlameSound()
+ {
+ if ( m_bSoundOn )
+ {
+ StopSound( entindex(), "FlameThrower.Sound" );
+ m_bSoundOn = false;
+ }
+ }
+
+
+#else
+
+ bool CWeaponFlameThrower::Holster( CBaseCombatWeapon *pSwitchingTo )
+ {
+ m_bFiring = false;
+
+ return BaseClass::Holster( pSwitchingTo );
+ }
+
+
+ void CWeaponFlameThrower::IgniteNearbyGasolineBlobs()
+ {
+ CBasePlayer *pOwner = ToBaseTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+ Vector vOrigin = pOwner->Weapon_ShootPosition( );
+ CBaseEntity *ents[128];
+ float dists[128];
+ int nEnts = FindBurnableEntsInSphere(
+ ents,
+ dists,
+ ARRAYSIZE( dists ),
+ vOrigin,
+ 50,
+ pOwner );
+
+ for ( int i=0; i < nEnts; i++ )
+ {
+ CGasolineBlob *pBlob = dynamic_cast< CGasolineBlob* >( ents[i] );
+ if ( pBlob )
+ {
+ GetFireDamageMgr()->AddDamage( pBlob, pOwner, 500, false );
+ }
+ }
+ }
+
+#endif
+
+