Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

435 lines
9.9 KiB

5 years ago
//========= 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