Counter Strike : Global Offensive Source Code
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.
 
 
 
 
 
 

1170 lines
36 KiB

/**
* Inferno.cpp
* An Inferno
* Author: Michael S. Booth, February 2005
* Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
*/
#include "cbase.h"
#include "inferno.h"
#include "engine/IEngineSound.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include <coordsize.h>
#include "tier0/vprof.h"
#include "igameevents.h"
#include "particle_parse.h"
#include "entityutil.h"
#include "func_elevator.h"
#include "nav.h"
#include "nav_mesh.h"
#include "cs_shareddefs.h"
#include "smokegrenade_projectile.h"
#include "improv_locomotor.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
IMPLEMENT_SERVERCLASS_ST( CInferno, DT_Inferno )
SendPropArray3( SENDINFO_ARRAY3(m_fireXDelta), SendPropInt( SENDINFO_ARRAY(m_fireXDelta), COORD_INTEGER_BITS+1, 0 ) ),
SendPropArray3( SENDINFO_ARRAY3(m_fireYDelta), SendPropInt( SENDINFO_ARRAY(m_fireYDelta), COORD_INTEGER_BITS+1, 0 ) ),
SendPropArray3( SENDINFO_ARRAY3(m_fireZDelta), SendPropInt( SENDINFO_ARRAY(m_fireZDelta), COORD_INTEGER_BITS+1, 0 ) ),
SendPropArray3( SENDINFO_ARRAY3(m_bFireIsBurning), SendPropBool( SENDINFO_ARRAY(m_bFireIsBurning) ) ),
//SendPropArray3( SENDINFO_ARRAY3(m_BurnNormal), SendPropVector( SENDINFO_NOCHECK( m_BurnNormal ), 0, SPROP_NORMAL ) ),
SendPropInt( SENDINFO(m_fireCount), 7, SPROP_UNSIGNED ),
END_SEND_TABLE()
BEGIN_DATADESC( CInferno )
DEFINE_THINKFUNC( InfernoThink ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( inferno, CInferno );
PRECACHE_REGISTER( inferno );
IMPLEMENT_SERVERCLASS_ST( CFireCrackerBlast, DT_FireCrackerBlast )
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( fire_cracker_blast, CFireCrackerBlast );
PRECACHE_REGISTER( fire_cracker_blast );
ConVar InfernoPerFlameSpawnDuration( "inferno_per_flame_spawn_duration", "3", FCVAR_CHEAT, "Duration each new flame will attempt to spawn new flames" );
ConVar InfernoInitialSpawnInterval( "inferno_initial_spawn_interval", "0.02", FCVAR_CHEAT, "Time between spawning flames for first fire" );
ConVar InfernoChildSpawnIntervalMultiplier( "inferno_child_spawn_interval_multiplier", "0.1", FCVAR_CHEAT, "Amount spawn interval increases for each child" );
ConVar InfernoMaxChildSpawnInterval( "inferno_max_child_spawn_interval", "0.5", FCVAR_CHEAT, "Largest time interval for child flame spawning" );
ConVar InfernoSpawnAngle( "inferno_spawn_angle", "45", FCVAR_CHEAT, "Angular change from parent" );
ConVar InfernoMaxFlames( "inferno_max_flames", "16", FCVAR_CHEAT, "Maximum number of flames that can be created" );
ConVar InfernoFlameSpacing( "inferno_flame_spacing", "42", FCVAR_CHEAT, "Minimum distance between separate flame spawns" );
ConVar InfernoFlameLifetime( "inferno_flame_lifetime", "7", FCVAR_CHEAT, "Average lifetime of each flame in seconds" );
ConVar InfernoFriendlyFireDuration( "inferno_friendly_fire_duration", "6", FCVAR_CHEAT, "For this long, FF is credited back to the thrower." );
ConVar InfernoDebug( "inferno_debug", "0", FCVAR_CHEAT );
ConVar InfernoDamage( "inferno_damage", "40", FCVAR_CHEAT, "Damage per second" );
ConVar InfernoMaxRange( "inferno_max_range", "150", FCVAR_CHEAT, "Maximum distance flames can spread from their initial ignition point" );
ConVar InfernoVelocityFactor( "inferno_velocity_factor", "0.003", FCVAR_CHEAT );
ConVar InfernoVelocityDecayFactor( "inferno_velocity_decay_factor", "0.2", FCVAR_CHEAT );
ConVar InfernoVelocityNormalFactor( "inferno_velocity_normal_factor", "0", FCVAR_CHEAT );
ConVar InfernoSurfaceOffset( "inferno_surface_offset", "20", FCVAR_CHEAT );
ConVar InfernoChildSpawnMaxDepth( "inferno_child_spawn_max_depth", "4", FCVAR_CHEAT );
ConVar inferno_scorch_decals( "inferno_scorch_decals", "1", FCVAR_CHEAT );
ConVar inferno_max_trace_per_tick("inferno_max_trace_per_tick", "16");
ConVar inferno_forward_reduction_factor( "inferno_forward_reduction_factor", "0.9", FCVAR_CHEAT );
// Inferno trace masks can allow to do different traces for spreading fire
#define INFERNO_MASK_TO_GROUND ((MASK_SOLID_BRUSHONLY) & (~CONTENTS_GRATE))
#define INFERNO_MASK_LOS_CHECK ( INFERNO_MASK_TO_GROUND | CONTENTS_MONSTER )
#define INFERNO_MASK_DAMAGE INFERNO_MASK_LOS_CHECK
// Smoke grenade radius constant is actually tuned for the bots
// and not for gameplay. Visualizing smoke will show that it goes
// up from the emitter by 128 units (fuzzy top), nothing goes down,
// and it makes a wide XY-donut with a radius of *128* units (fuzzy edges).
ASSERT_INVARIANT( CONSTANT_UNITS_SMOKEGRENADERADIUS == 166 );
// When interacting with fire we don't want any vertical interactions unless
// contact points are definitely in smoke vertically.
static const float SmokeGrenadeRadius_InfernoAffectingZ = 120.0f;
// When interacting with fire on the same plane we don't want alpha depth-fighting
// in the most common case, so leave a grace margin between the smoke particles
// and the fire particles.
static const float SmokeGrenadeRadius_InfernoAffectingXY_topedge = 100.0f;
static const float SmokeGrenadeRadius_InfernoAffectingXY_equator = 150.0f;
static const float SmokeGrenadeRadius_InfernoAffectingXY_bottomedge = 128.0f;
// Fire burning things and smoke constants
static const float InfernoFire_HalfWidth = 30.0f;
static const float InfernoFire_FullHeight = 80.0f;
static bool BCheckFirePointInSmokeCloud( const Vector &vecFirePoint, const Vector &vecSmokeOrigin )
{
const float flFireUpToSmokeCheckHeight = ( 2 * InfernoFire_HalfWidth + 4.0f );
vec_t flFireAboveSmokeZ = ( vecFirePoint.z - vecSmokeOrigin.z );
if ( flFireAboveSmokeZ < -flFireUpToSmokeCheckHeight )
return false; // fire not tall enough to burn up to smoke
if ( flFireAboveSmokeZ > SmokeGrenadeRadius_InfernoAffectingZ )
return false; // smoke cloud not tall enough to reach to the fire
// Now we know that fire is in XY-slice containing the smoke cloud
// Figure out if we are in the equator XY-plane or in the shrinking edge XY-plane
float flRadiusSquaredTest = SmokeGrenadeRadius_InfernoAffectingXY_equator*SmokeGrenadeRadius_InfernoAffectingXY_equator;
if ( flFireAboveSmokeZ > SmokeGrenadeRadius_InfernoAffectingZ * 0.6f )
{
float flPctFromEquatorToEdge = RemapValClamped( flFireAboveSmokeZ, SmokeGrenadeRadius_InfernoAffectingZ * 0.6f, SmokeGrenadeRadius_InfernoAffectingZ, 0.0f, 1.0f );
flPctFromEquatorToEdge *= flPctFromEquatorToEdge; // 0.0 still equator; 1.0 edge (squaring makes things feel quadratically closer to equator)
flRadiusSquaredTest = RemapValClamped( flPctFromEquatorToEdge, 0.0f, 1.0f, flRadiusSquaredTest, SmokeGrenadeRadius_InfernoAffectingXY_topedge*SmokeGrenadeRadius_InfernoAffectingXY_topedge );
}
else if ( flFireAboveSmokeZ < SmokeGrenadeRadius_InfernoAffectingZ * 0.15f )
{
float flPctFromEquatorToEdge = RemapValClamped( flFireAboveSmokeZ, SmokeGrenadeRadius_InfernoAffectingZ * 0.1f, -flFireUpToSmokeCheckHeight, 0.0f, 1.0f );
flPctFromEquatorToEdge *= flPctFromEquatorToEdge; // 0.0 still equator; 1.0 edge (squaring makes things feel quadratically closer to equator)
flRadiusSquaredTest = RemapValClamped( flPctFromEquatorToEdge, 0.0f, 1.0f, flRadiusSquaredTest, SmokeGrenadeRadius_InfernoAffectingXY_bottomedge*SmokeGrenadeRadius_InfernoAffectingXY_bottomedge );
}
// Check if it is within XY-plane radius now
vec_t lenXYsqr = ( vecFirePoint - vecSmokeOrigin ).Length2DSqr();
return lenXYsqr <= flRadiusSquaredTest;
}
//------------------------------------------------------------------------------------------
CInferno::CInferno() :
m_pWeaponInfo( NULL )
{
// Set max flames to default in case the user doesn't ask for
// more or less max flames for this inferno.
SetMaxFlames( InfernoMaxFlames.GetInt() );
ListenForGameEvent( "hegrenade_detonate" );
ListenForGameEvent( "smokegrenade_detonate" );
m_bWasCreatedInSmoke = false;
}
//------------------------------------------------------------------------------------------
CInferno::~CInferno()
{
for ( int i = 0; i < m_fireCount; i++ )
{
delete m_fire[i];
m_fire[i] = NULL;
}
switch( GetInfernoType() )
{
case INFERNO_TYPE_FIRE:
case INFERNO_TYPE_INCGREN_FIRE:
EmitSound( "Inferno.FadeOut" );
StopSound( "Inferno.Loop" );
break;
case INFERNO_TYPE_FIREWORKS:
EmitSound( "FireworksCrate.Stop" );
StopSound( "FireworksCrate.Start" );
break;
}
}
//------------------------------------------------------------------------------------------
void CInferno::Precache( void )
{
// extend
BaseClass::Precache();
PrecacheScriptSound( "Inferno.Start" );
PrecacheScriptSound( "Inferno.Start_IncGrenade" );
PrecacheScriptSound( "Inferno.StartSweeten" );
PrecacheScriptSound( "Inferno.Loop" );
PrecacheScriptSound( "Inferno.Fire.Ignite" );
PrecacheScriptSound( "Inferno.FadeOut" );
PrecacheParticleSystem( "extinguish_fire" );
PrecacheParticleSystem( "extinsguish_fire_blastout_01" );
PrecacheScriptSound( "Molotov.Throw" );
PrecacheScriptSound( "FireworksCrate.Start" );
PrecacheScriptSound( "FireworksCrate.Stop" );
if( GetParticleEffectName() != NULL )
{
PrecacheParticleSystem( GetParticleEffectName() );
}
if( GetImpactParticleEffectName() != NULL )
{
PrecacheParticleSystem( GetImpactParticleEffectName() );
}
}
//------------------------------------------------------------------------------------------
void CInferno::Spawn( void )
{
m_fireCount = 0;
const float damageRampUpTime = 2.0f;
m_damageRampTimer.Start( damageRampUpTime );
SetThink( &CInferno::InfernoThink );
SetNextThink( gpGlobals->curtime );
m_NextSpreadTimer.Start( GetFlameSpreadDelay() );
AddFlag( FL_ONFIRE );
SetInfernoType( INFERNO_TYPE_FIRE );
}
//------------------------------------------------------------------------------------------
float CInferno::GetDamagePerSecond()
{
return InfernoDamage.GetFloat();
}
//------------------------------------------------------------------------------------------
float CInferno::GetFlameLifetime() const
{
return InfernoFlameLifetime.GetFloat();
}
void CInferno::FireGameEvent( IGameEvent *event )
{
const char *eventname = event->GetName();
// if ( Q_strcmp( "hegrenade_detonate", eventname ) == 0 )
// {
// Vector vecGrenade = Vector( ( float )event->GetInt( "x" ), ( float )event->GetInt( "y" ), ( float )event->GetInt( "z" ) );
// float flGrenadeRadius = HEGrenadeRadius*1.5;
//
// if ((vecGrenade - m_startPos).IsLengthLessThan( flGrenadeRadius*4 ))
// {
// ExtinguishFlamesInSphere( vecGrenade, flGrenadeRadius );
// }
// }
if ( Q_strcmp( "smokegrenade_detonate", eventname ) == 0 )
{
Vector vecGrenade = Vector( ( float )event->GetInt( "x" ), ( float )event->GetInt( "y" ), ( float )event->GetInt( "z" ) );
if ((vecGrenade - m_startPos).IsLengthLessThan( SmokeGrenadeRadius*4 ))
{
int extinguishCount = ExtinguishFlamesAroundSmokeGrenade( vecGrenade );
if ( extinguishCount == m_fireCount )
{
CSmokeGrenadeProjectile* pEnt = (CSmokeGrenadeProjectile*)CBaseEntity::Instance( INDEXENT( event->GetInt( "entityid" ) ) );
if ( pEnt )
{
// If we were extinguished by a detonating smoke grenade, record it so we can report to OGS
pEnt->m_unOGSExtraFlags |= CBaseCSGrenadeProjectile::GRENADE_EXTINGUISHED_INFERNO;
}
}
}
}
}
//------------------------------------------------------------------------------------------
/**
* Start the Inferno burning
*/
void CInferno::StartBurning( const Vector &pos, const Vector &normal, const Vector &velocity, int initialDepth )
{
m_startPos.x = pos.x + InfernoSurfaceOffset.GetFloat() * normal.x;
m_startPos.y = pos.y + InfernoSurfaceOffset.GetFloat() * normal.y;
m_startPos.z = pos.z;
// reflect velocity off of surface
float splash = DotProduct( velocity, normal );
Vector remainder = velocity - normal * splash;
m_splashVelocity = remainder - InfernoVelocityNormalFactor.GetFloat() * normal * splash;
QAngle splashangle;
VectorAngles( velocity, splashangle );
if( GetImpactParticleEffectName() != NULL )
{
DispatchParticleEffect( GetImpactParticleEffectName(), pos, splashangle );
}
if( InfernoDebug.GetBool() )
{
NDebugOverlay::Sphere( pos, 0.5f * InfernoFire_HalfWidth, 0, 255, 0, true, 10.0f);
NDebugOverlay::Sphere( m_startPos, 0.5f * InfernoFire_HalfWidth, 255, 255, 0, true, 10.0f);
}
// create the initial bonfire that begins to spread
if ( k_ECreateFireResult_OK == CreateFire( m_startPos, normal, NULL, initialDepth ) )
{
switch( GetInfernoType() )
{
case INFERNO_TYPE_FIRE:
EmitSound( "Inferno.Start" );
EmitSound( "Inferno.StartSweeten" );
EmitSound( "Inferno.Loop" );
break;
case INFERNO_TYPE_INCGREN_FIRE:
EmitSound( "Inferno.Start_IncGrenade" );
EmitSound( "Inferno.StartSweeten_IncGrenade" );
EmitSound( "Inferno.Loop" );
break;
case INFERNO_TYPE_FIREWORKS:
EmitSound( "FireworksCrate.Start" );
break;
}
m_startPos = m_fire[0]->m_pos;
SetAbsOrigin( m_startPos );
IGameEvent * event = gameeventmanager->CreateEvent( "inferno_startburn" );
if ( event )
{
event->SetInt( "entityid", this->entindex() );
event->SetFloat( "x", m_startPos.x );
event->SetFloat( "y", m_startPos.y );
event->SetFloat( "z", m_startPos.z );
gameeventmanager->FireEvent( event );
}
m_activeTimer.Start();
}
else
{
EmitSound( "Molotov.Extinguish" );
DispatchParticleEffect( "extinguish_fire", m_startPos, splashangle );
UTIL_Remove( this );
}
}
class CInfernoLOSTraceFilter : public CTraceFilter
{
// Find which objects can block molotov spreads
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
{
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
// Players do not block spread
if ( pEntity->IsPlayer() )
return false;
// Chickens, hostages, and other 'navigating' objects don't block spread
if( dynamic_cast< CImprovLocomotor* >( pEntity ) != nullptr )
return false;
// Other objects (doors, terrain, etc.) block spread
return true;
}
};
//------------------------------------------------------------------------------------------
/**
* Spread the flames
*/
void CInferno::Spread( const Vector &spreadVelocity )
{
if( m_NextSpreadTimer.HasStarted() && !m_NextSpreadTimer.IsElapsed() )
{
return;
}
m_NextSpreadTimer.Start( GetFlameSpreadDelay() );
for ( int i = 0; i < m_fireCount; i++ )
{
// attempt to spawn child-flames
FireInfo *fire = m_fire[ i ];
if ( !fire->m_burning ||
fire->m_lifetime.IsElapsed() )
continue; // This flame has been extinguished or elapsed, shouldn't be spreading from here
if ( !fire->m_spawnLifetime.IsElapsed() && fire->m_spawnTimer.IsElapsed() )
{
fire->m_spawnTimer.Reset();
fire->m_spawnCount++;
}
}
int traceCount = inferno_max_trace_per_tick.GetInt();
int nextFireOffset = m_fireSpawnOffset + 1;
for ( int i = 0; i < m_fireCount && traceCount > 0; i++ )
{
if ( m_fireCount >= MIN( ( int ) MAX_INFERNO_FIRES, m_nMaxFlames ) )
break;
int fireIndex = (i + m_fireSpawnOffset) % m_fireCount;
FireInfo *fire = m_fire[fireIndex];
nextFireOffset = fireIndex;
if ( !fire->m_spawnCount )
continue;
if ( !fire->m_burning ||
fire->m_lifetime.IsElapsed() )
continue; // This flame has been extinguished or elapsed, shouldn't be spreading from here
int depth = fire->m_treeDepth + 1;
if ( depth >= InfernoChildSpawnMaxDepth.GetInt() )
continue;
fire->m_spawnCount--;
trace_t tr;
const int maxRetry = 4;
for( int t=0; t<maxRetry; ++t )
{
Vector out;
if (fire->m_parent == NULL)
{
// initial fire spreads outward in a circle
float angle = random->RandomFloat( -3.14159f, 3.14159f );
out = Vector( cos(angle), sin(angle), 0.0f );
}
else
{
// child flames tend to spread away from their parent
Vector to = fire->m_pos - fire->m_parent->m_pos;
to.NormalizeInPlace();
QAngle angles;
VectorAngles( to, angles );
angles.y += random->RandomFloat( -InfernoSpawnAngle.GetFloat(), InfernoSpawnAngle.GetFloat() );
AngleVectors( angles, &out );
}
// If we're going into a wall, don't keep trying to spread into a wall the entire lifetime - back off to
// a circular spread at the end.
float velocityDecay = pow( InfernoVelocityDecayFactor.GetFloat(), float(fire->m_treeDepth) );
Vector timeAdjustedSpreadVelocity = spreadVelocity * fire->m_lifetime.GetRemainingRatio() * velocityDecay;
out += InfernoVelocityFactor.GetFloat() * timeAdjustedSpreadVelocity;
// put fire on plane of ground
Vector side = CrossProduct( fire->m_normal, out );
out = CrossProduct( side, fire->m_normal );
float range = random->RandomFloat( 50.0f, 75.0f );
Vector pos = fire->m_pos + range * out;
// limit maximum range of spread
Vector fireDir = pos - m_startPos;
if ( fireDir.IsLengthGreaterThan( InfernoMaxRange.GetFloat() ) )
{
VectorNormalize(fireDir);
fireDir *= InfernoMaxRange.GetFloat();
pos = m_startPos + fireDir;
}
// dont let flames fall too far
const float maxDrop = 200.0f;
Vector endPos = pos;
endPos.z = fire->m_pos.z - maxDrop;
// put fire on the ground
UTIL_TraceLine( pos + Vector( 0, 0, 50.0f ), endPos, INFERNO_MASK_TO_GROUND, NULL, COLLISION_GROUP_NONE, &tr );
traceCount--;
if (!tr.DidHit())
{
if ( InfernoDebug.GetBool() )
{
NDebugOverlay::Line( pos + Vector( 0, 0, 50.0f ), endPos, 255, 255, 0, true, 1.0f );
NDebugOverlay::Cross3D( pos, 5, 255, 0, 0, true, 1.0f );
}
m_splashVelocity *= inferno_forward_reduction_factor.GetFloat();
continue;
}
pos.z = tr.endpos.z;
Vector normal = tr.plane.normal;
// make sure we dont go through walls
const Vector fireHeight( 0, 0, InfernoFire_HalfWidth );
CInfernoLOSTraceFilter losTraceFilter;
UTIL_TraceLine( fire->m_pos + fireHeight, pos + fireHeight, INFERNO_MASK_LOS_CHECK, &losTraceFilter, &tr );
traceCount--;
if (tr.fraction < 1.0f)
{
if ( InfernoDebug.GetBool() )
{
NDebugOverlay::Line( fire->m_pos + fireHeight, pos + fireHeight, 255, 0, 0, true, 1.0f );
}
m_splashVelocity *= inferno_forward_reduction_factor.GetFloat();
continue;
}
ECreateFireResult_t eCreateFireResult = CreateFire( pos, normal, fire, depth );
if ( ( eCreateFireResult == k_ECreateFireResult_OK )
|| ( eCreateFireResult == k_ECreateFireResult_LimitExceeded ) )
break;
else if ( eCreateFireResult != k_ECreateFireResult_AlreadyOnFire )
m_splashVelocity *= inferno_forward_reduction_factor.GetFloat();
if ( InfernoDebug.GetBool() )
{
if ( eCreateFireResult == k_ECreateFireResult_InSmoke )
NDebugOverlay::Line( fire->m_pos + fireHeight, pos + fireHeight, 255, 255, 0, true, 10.0f );
else if ( eCreateFireResult == k_ECreateFireResult_AlreadyOnFire )
NDebugOverlay::Line( fire->m_pos + fireHeight, pos + fireHeight, 255, 100, 100, true, 2.0f );
else
NDebugOverlay::Line( fire->m_pos + fireHeight, pos + fireHeight, 255, 100, 0, true, 10.0f );
}
}
}
m_fireSpawnOffset = nextFireOffset + 1;
}
//------------------------------------------------------------------------------------------
/**
* Checks whether destination fire point is within a detonated smoke grenade cloud
* This is useful to deny molly detonation in smoke, and to deny fire flames spreading into smoke
*/
bool CInferno::IsFirePosInSmokeCloud( const Vector &pos ) const
{
const int INFERNO_SEARCH_ENTS = 32;
CBaseEntity *pEntities[ INFERNO_SEARCH_ENTS ];
int iNumEntities = UTIL_EntitiesInSphere( pEntities, INFERNO_SEARCH_ENTS, pos, SmokeGrenadeRadius, FL_GRENADE );
for ( int i = 0; i < iNumEntities; i++ )
{
CSmokeGrenadeProjectile *pGrenade = dynamic_cast< CSmokeGrenadeProjectile * >( pEntities[ i ] );
if ( pGrenade && pGrenade->m_bDidSmokeEffect
&& BCheckFirePointInSmokeCloud( pos, pGrenade->GetAbsOrigin() ) )
{
return true;
}
}
return false;
}
//------------------------------------------------------------------------------------------
/**
* Create an actual fire entity at the given position
*/
CInferno::ECreateFireResult_t CInferno::CreateFire( const Vector &pos, const Vector &normal, FireInfo *parent, int depth )
{
if ( m_fireCount >= MIN( ( int ) MAX_INFERNO_FIRES, m_nMaxFlames ) )
{
return k_ECreateFireResult_LimitExceeded;
}
if ( IsTouching( pos, pos, NULL ) )
{
// we already created a fire here
return k_ECreateFireResult_AlreadyOnFire;
}
// if we throw down a molly in the middle of a smoke grenade, DENY!
if( IsFirePosInSmokeCloud( pos ) )
{
m_bWasCreatedInSmoke = true;
return k_ECreateFireResult_InSmoke;
}
if (InfernoDebug.GetBool())
{
if (parent)
{
NDebugOverlay::Line( parent->m_pos, pos, 0, 255, 255, true, 10.0f );
}
}
Vector firePos( pos );
bool overWater = false;
trace_t tr;
int contents = enginetrace->GetPointContents( pos, MASK_WATER );
if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME ) )
{
Vector fireHeight( 0, 0, 30.0f );
int mask = MASK_SOLID_BRUSHONLY | CONTENTS_SLIME | CONTENTS_WATER;
UTIL_TraceLine( pos + fireHeight, pos, mask, NULL, COLLISION_GROUP_NONE, &tr );
if ( tr.allsolid )
{
return k_ECreateFireResult_AllSolid;
}
else
{
firePos = tr.endpos;
overWater = true;
}
}
FireInfo *fire = new FireInfo;
fire->m_pos = firePos;
fire->m_center = firePos + Vector( 0, 0, 0.5f * InfernoFire_FullHeight );
fire->m_normal = normal;
fire->m_parent = parent;
fire->m_treeDepth = depth;
fire->m_spawnCount = 0;
fire->m_flWaterHeight = firePos.z - pos.z;
fire->m_burning = true;
// all control points on the client die down at the same time, so the server needs to match this
if ( m_activeTimer.HasStarted() )
{
fire->m_lifetime.Start( GetFlameLifetime() - m_activeTimer.GetElapsedTime() );
}
else
{
fire->m_lifetime.Start( GetFlameLifetime() );
}
if (parent)
{
fire->m_spawnLifetime.Start( parent->m_spawnLifetime.GetCountdownDuration() );
float duration = InfernoChildSpawnIntervalMultiplier.GetFloat() * parent->m_spawnTimer.GetCountdownDuration();
if (duration > InfernoMaxChildSpawnInterval.GetFloat())
duration = InfernoMaxChildSpawnInterval.GetFloat();
fire->m_spawnTimer.Start( duration );
}
else
{
fire->m_spawnLifetime.Start( InfernoPerFlameSpawnDuration.GetFloat() );
fire->m_spawnTimer.Start( InfernoInitialSpawnInterval.GetFloat() );
}
// keep a simple array of all active fires
m_fire[ m_fireCount ] = fire;
// propogate across the network
// Compute this fire's position relative to the Inferno entity.
Vector vecDelta = fire->m_pos - GetAbsOrigin();
m_fireXDelta.Set( m_fireCount, (int)vecDelta.x );
m_fireYDelta.Set( m_fireCount, (int)vecDelta.y );
m_fireZDelta.Set( m_fireCount, (int)vecDelta.z );
m_bFireIsBurning.Set( m_fireCount, true );
m_BurnNormal.Set( m_fireCount, normal );
++m_fireCount;
RecomputeExtent();
// emit a small flame burst sound
if( GetInfernoType() == INFERNO_TYPE_FIRE || GetInfernoType() == INFERNO_TYPE_INCGREN_FIRE )
{
CSoundParameters params;
if ( GetParametersForSound( "Inferno.Fire.Ignite", params, NULL ) )
{
EmitSound_t ep( params );
ep.m_pOrigin = &fire->m_pos;
CBroadcastRecipientFilter filter;
EmitSound( filter, SOUND_FROM_WORLD, ep );
}
if ( inferno_scorch_decals.GetBool() && !overWater )
{
trace_t trace;
const float dist = 100.0f;
Vector dir( 0, 0, -1 );
UTIL_TraceLine( fire->m_pos, fire->m_pos + dir * dist, MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &trace );
UTIL_DecalTrace( &trace, "MolotovScorch" );
}
}
return k_ECreateFireResult_OK;
}
void CInferno::ExtinguishIndividualFlameBySmokeGrenade( int iFire, Vector vecStart )
{
m_fire[ iFire ]->m_lifetime.Invalidate();
Vector vecAngleAway = m_fire[ iFire ]->m_pos - vecStart;
vecAngleAway.NormalizeInPlace();
QAngle angParticle;
VectorAngles( vecAngleAway, angParticle );
DispatchParticleEffect( "extinguish_fire", m_fire[ iFire ]->m_pos, angParticle );
}
int CInferno::ExtinguishFlamesAroundSmokeGrenade( Vector vecStart )
{
bool bExtinguished = false;
bool bCheckDistanceForFlames = true;
int nNumExtinguished = 0;
// if the radius overlaps the center, extinguish the whole flame
if ( BCheckFirePointInSmokeCloud( m_startPos, vecStart ) )
bCheckDistanceForFlames = false;
for( int i=0; i<m_fireCount; ++i )
{
// if this fire just died, propagate over the network
if ( m_fire[i]->m_burning && (
!bCheckDistanceForFlames || BCheckFirePointInSmokeCloud( m_fire[i]->m_pos, vecStart )
) )
{
ExtinguishIndividualFlameBySmokeGrenade( i, vecStart );
bExtinguished = true;
nNumExtinguished++;
}
}
// if we extinguished third or more of our fire, just put out the rest
if ( !bCheckDistanceForFlames && nNumExtinguished >= (m_fireCount/3) )
{
for( int i=0; i<m_fireCount; ++i )
{
// if this fire just died, propagate over the network
if ( !m_fire[i]->m_lifetime.IsElapsed() )
{
ExtinguishIndividualFlameBySmokeGrenade( i, vecStart );
nNumExtinguished++;
}
}
}
if ( bExtinguished )
{
EmitSound( "Molotov.Extinguish" );
IGameEvent * event = gameeventmanager->CreateEvent( "inferno_extinguish" );
if ( event )
{
event->SetInt( "entityid", this->entindex() );
event->SetFloat( "x", m_startPos.x );
event->SetFloat( "y", m_startPos.y );
event->SetFloat( "z", m_startPos.z );
gameeventmanager->FireEvent( event );
}
}
return nNumExtinguished;
}
bool CInferno::CheckExpired()
{
VPROF_BUDGET( "CInferno::CheckExpired (check lifetimes)", "Fire" );
bool bIsAttachedToMovingObject = GetParent() != NULL;
Vector vecInfernoOrigin = GetAbsOrigin();
// check lifetime of flames
bool isDone = true;
for ( int i = 0; i < m_fireCount; ++i )
{
// Already dead.
if ( !m_fire[i]->m_burning )
continue;
// if this fire just died, propagate over the network
if ( m_fire[i]->m_lifetime.IsElapsed() )
{
m_fire[i]->m_pos = Vector( 0, 0, 0 );
m_fire[i]->m_burning = false;
m_bFireIsBurning.Set( i, false );
continue;
}
// still at least one fire alive
isDone = false;
m_fire[i]->m_pos = vecInfernoOrigin;
m_fire[i]->m_pos.x += m_fireXDelta[i];
m_fire[i]->m_pos.y += m_fireYDelta[i];
m_fire[i]->m_pos.z += m_fireZDelta[i];
if ( bIsAttachedToMovingObject )
{
RecomputeExtent();
}
if ( InfernoDebug.GetBool() )
{
NDebugOverlay::Sphere( m_fire[i]->m_pos, 2.0f * InfernoFire_HalfWidth, 255, 100, 0, true, 0.1f );
}
}
if ( isDone )
{
IGameEvent * event = gameeventmanager->CreateEvent( "inferno_expire" );
if ( event )
{
event->SetInt( "entityid", this->entindex() );
event->SetFloat( "x", m_startPos.x );
event->SetFloat( "y", m_startPos.y );
event->SetFloat( "z", m_startPos.z );
gameeventmanager->FireEvent( event );
}
// if all fires have burned out, we're done
UTIL_Remove( this );
// Expired!
return true;
}
// Not expired
return false;
}
void CInferno::MarkCoveredAreaAsDamaging()
{
// mark overlapping nav areas as "damaging"
NavAreaCollector overlap;
// bloat extents enough to ensure any non-damaging area is actually safe
// bloat in Z as well to catch nav areas that may be slightly above/below ground
Extent extent = m_extent;
float DangerBloat = 32.0f;
Vector dangerBloat( DangerBloat, DangerBloat, DangerBloat );
extent.lo -= dangerBloat;
extent.hi += dangerBloat;
//NDebugOverlay::Box( vec3_origin, extent.lo, extent.hi, 0, 255, 0, 10, 0.1f );
TheNavMesh->ForAllAreasOverlappingExtent( overlap, extent );
FOR_EACH_VEC( overlap.m_area, it )
{
CNavArea *area = overlap.m_area[it];
if ( IsTouching( area ) )
{
area->MarkAsDamaging( 1.0f );
}
}
}
//------------------------------------------------------------------------------------------
/**
* Spread the flames
*/
void CInferno::InfernoThink( void )
{
VPROF_BUDGET( "CInferno::InfernoThink", "Fire" );
bool bExpiryCheckPerformed = false;
// Run bookkeeping every 0.1s
if ( m_BookkeepingTimer.Interval(0.1f) )
{
bExpiryCheckPerformed = true;
if ( CheckExpired() )
return;
// the fire grows...
if ( m_fireCount > 0 && m_fireCount < MIN( m_nMaxFlames, ( int )MAX_INFERNO_FIRES ) )
{
VPROF_BUDGET( "CInferno::InfernoThink (spread)", "Fire" );
Spread( m_splashVelocity );
}
// Mark area as damaging for bot avoidance
MarkCoveredAreaAsDamaging();
}
#if 0
// Debug draw flame region
NDebugOverlay::Box( vec3_origin, m_extent.lo, m_extent.hi, 255, 255, 255, 10, 0.1f );
#endif
// Deal damage every 0.2s
const float kDamageTimerSeconds = 0.2f;
while ( m_damageTimer.RunEvery( kDamageTimerSeconds ) )
{
// Note that we run a lot of code in this RunEvery(), but we expect the loop to run 0 or 1 times
// in almost every case, unless this think function somehow got super delayed by the server
if(!bExpiryCheckPerformed)
{
bExpiryCheckPerformed = true;
if ( CheckExpired() )
return;
}
VPROF_BUDGET( "CInferno::InfernoThink (damage)", "Fire" );
const int maxVictims = 256;
CBaseEntity *damageList[ maxVictims ];
CBaseEntity *owner = GetOwnerEntity();
int damageCount = 0;
const float flameRadius = 2.0f * InfernoFire_HalfWidth;
CBaseEntity *list[ maxVictims ];
int count = UTIL_EntitiesInBox( list, maxVictims, m_extent.lo, m_extent.hi, 0 );
for( int i=0; i<count; ++i )
{
if (list[i] == NULL || !list[i]->IsAlive() || list[i] == this)
continue;
if (IsTouching( list[i], flameRadius, list[i]->IsPlayer() ))
{
damageList[damageCount] = list[i];
damageCount++;
}
}
int damageType = GetDamageType();
#if !defined( CSTRIKE15 )
// After the first few seconds of burning, the thrower isn't responsible for teammates who run into the fire.
if ( m_activeTimer.GetElapsedTime() > InfernoFriendlyFireDuration.GetFloat() || owner == NULL )
{
damageType |= DMG_BLAMELESS_FRIENDLY_FIRE; // Add in a flag to prevent FF demerits
}
#endif
// Note that we expect molotov this value to be an integer (currently it is 40 * 0.2 == 8 damage per tick)
// If molotov DPS changes, we may need to also adjust how often damage is applied.
//
// We could also change this to tick at the exact rate required to deal 1 damage (tick rate = 1 / GetDamagePerSecond())
// at which point we might want to consider optimizing this loop to run a single time and multiply its damage
// by the damage dealt, so that (for example) if molotovs deal 80 dps on a 64 tick server, we only find the targets
// once during the ticks when the molotov deals 2 damage.
float baseDamage = GetDamagePerSecond() * kDamageTimerSeconds; // dmg / sec * sec / tick = dmg / tick
if ( !m_damageRampTimer.IsElapsed() )
{
baseDamage *= m_damageRampTimer.GetElapsedRatio();
}
for ( int i = 0; i < damageCount; i++ )
{
// damage the victim
CBaseEntity *pEnt = damageList[i];
float damage = baseDamage;
if( CanHarm( pEnt ) )
{
CTakeDamageInfo info( this, owner, damage, damageType );
pEnt->TakeDamage( info );
}
}
}
// Figure out when our next event is and make sure we get a think tick close to it.
float nextThink = m_BookkeepingTimer.GetTargetTime();
nextThink = MIN( nextThink, m_damageTimer.GetTargetTime() );
SetNextThink( nextThink );
}
//-----------------------------------------------------------------------------------------------
void CInferno::RecomputeExtent( void )
{
m_extent.lo = Vector( 999999.9f, 999999.9f, 999999.9f );
m_extent.hi = Vector( -999999.9f, -999999.9f, -999999.9f );
for( int i=0; i<m_fireCount; ++i )
{
FireInfo *fire = m_fire[i];
if ( fire->m_pos.x - InfernoFire_HalfWidth < m_extent.lo.x )
m_extent.lo.x = fire->m_pos.x - InfernoFire_HalfWidth;
if ( fire->m_pos.x + InfernoFire_HalfWidth > m_extent.hi.x )
m_extent.hi.x = fire->m_pos.x + InfernoFire_HalfWidth;
if ( fire->m_pos.y - InfernoFire_HalfWidth < m_extent.lo.y )
m_extent.lo.y = fire->m_pos.y - InfernoFire_HalfWidth;
if ( fire->m_pos.y + InfernoFire_HalfWidth > m_extent.hi.y )
m_extent.hi.y = fire->m_pos.y + InfernoFire_HalfWidth;
if ( fire->m_pos.z < m_extent.lo.z )
m_extent.lo.z = fire->m_pos.z;
if ( fire->m_pos.z + InfernoFire_FullHeight > m_extent.hi.z )
m_extent.hi.z = fire->m_pos.z + InfernoFire_FullHeight;
}
}
bool CInferno::BShouldExtinguishSmokeGrenadeBounce( CBaseEntity *entity, Vector &posDropSmoke ) const
{
const float radius = 2.0f * InfernoFire_HalfWidth;
for ( int i = 0; i < m_fireCount; i++ )
{
FireInfo *fire = m_fire[ i ];
if ( !fire->m_burning ||
fire->m_lifetime.IsElapsed() )
continue; // This flame has been extinguished or elapsed, shouldn't cause damage
if ( ( posDropSmoke - fire->m_center ).IsLengthLessThan( radius ) )
{
// doublecheck los if required
trace_t tr;
const Vector fireHeight( 0, 0, InfernoFire_HalfWidth );
UTIL_TraceLine( fire->m_center + fireHeight, posDropSmoke, INFERNO_MASK_DAMAGE, entity, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction < 1.0f )
UTIL_TraceLine( fire->m_center, posDropSmoke, INFERNO_MASK_DAMAGE, entity, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction == 1.0f )
{
if ( InfernoDebug.GetBool() )
{
NDebugOverlay::Line( fire->m_center, posDropSmoke, 255, 0, 255, true, 50.2f );
}
return true;
}
else
{
if ( InfernoDebug.GetBool() )
{
NDebugOverlay::Line( fire->m_center, posDropSmoke, 255, 0, 0, true, 50.2f );
}
}
}
}
return false;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if position is in contact with a fire within the Inferno
*/
bool CInferno::IsTouching( CBaseEntity *entity, float radius, bool checkLOS ) const
{
if ( entity != NULL )
{
for ( int i = 0; i < m_fireCount; i++ )
{
FireInfo *fire = m_fire[i];
if ( !fire->m_burning ||
fire->m_lifetime.IsElapsed() )
continue; // This flame has been extinguished or elapsed, shouldn't cause damage
// Calculate the nearest point to our potential victim, from our center point
const Vector fireHeight( 0, 0, InfernoFire_HalfWidth );
Vector pos;
Vector fireCheck = fire->m_center;
if( checkLOS )
fireCheck += fireHeight;
entity->CollisionProp()->CalcNearestPoint( fireCheck, &pos );
if ( ( pos - fireCheck ).IsLengthLessThan( radius ) )
{
// touching at least one flame
if( checkLOS )
{
// doublecheck los if required
trace_t tr;
UTIL_TraceLine( fireCheck, pos, INFERNO_MASK_DAMAGE, entity, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction < 1.0f )
{
fireCheck = fire->m_center;
entity->CollisionProp()->CalcNearestPoint( fireCheck, &pos );
if ( ( pos - fireCheck ).IsLengthLessThan( radius ) )
UTIL_TraceLine( fireCheck, pos, INFERNO_MASK_DAMAGE, entity, COLLISION_GROUP_NONE, &tr );
}
if( tr.fraction == 1.0f )
{
if( InfernoDebug.GetBool() )
{
NDebugOverlay::Line( fire->m_center, pos, 255, 0, 255, true, 50.2f );
}
return true;
}
else
{
if( InfernoDebug.GetBool() )
{
NDebugOverlay::Line( fire->m_center, pos, 255, 0, 0, true, 50.2f );
}
}
}
else
{
// los not needed, it's touching
if( InfernoDebug.GetBool() )
{
NDebugOverlay::Line( fire->m_center, pos, 255, 0, 255, true, 0.2f );
}
return true;
}
}
}
}
return false;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if given ray intersects any fires
* TODO: Check LOS if needed.
*/
bool CInferno::IsTouching( const Vector &from, const Vector &to, Vector *where ) const
{
for ( int i = 0; i < m_fireCount; i++ )
{
FireInfo *fire = m_fire[i];
if ( !fire->m_burning ||
fire->m_lifetime.IsElapsed() )
continue; // This flame has been extinguished or elapsed, shouldn't be considered touching
Vector pointOnRay;
ClosestPointOnRay( m_fire[i]->m_center, from, to, &pointOnRay );
const float radius = 2.0f * InfernoFire_HalfWidth;
if ( ( pointOnRay - m_fire[i]->m_center ).IsLengthLessThan( radius ) )
{
if ( where )
{
*where = pointOnRay;
}
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if given area overlaps any fires
*/
bool CInferno::IsTouching( const CNavArea *area ) const
{
if ( area != NULL )
{
float radius = 2.0f * InfernoFire_HalfWidth;
for ( int i = 0; i < m_fireCount; i++ )
{
FireInfo *fire = m_fire[ i ];
if ( !fire->m_burning ||
fire->m_lifetime.IsElapsed() )
continue; // This flame has been extinguished or elapsed, shouldn't be considered touching
Vector close;
area->GetClosestPointOnArea( m_fire[i]->m_center, &close );
close.z += m_fire[i]->m_flWaterHeight; // If the inferno was raised above the water, check the nav as if it was in the original pos
if ( ( close - m_fire[i]->m_center ).IsLengthLessThan( radius ) )
{
return true;
}
}
}
return false;
}
//------------------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------------------
void CFireCrackerBlast::Spawn( void )
{
BaseClass::Spawn();
SetInfernoType( INFERNO_TYPE_FIREWORKS );
}