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

  1. /**
  2. * Inferno.cpp
  3. * An Inferno
  4. * Author: Michael S. Booth, February 2005
  5. * Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
  6. */
  7. #include "cbase.h"
  8. #include "inferno.h"
  9. #include "engine/IEngineSound.h"
  10. #include "SoundEmitterSystem/isoundemittersystembase.h"
  11. #include <coordsize.h>
  12. #include "tier0/vprof.h"
  13. #include "igameevents.h"
  14. #include "particle_parse.h"
  15. #include "entityutil.h"
  16. #include "func_elevator.h"
  17. #include "nav.h"
  18. #include "nav_mesh.h"
  19. #include "cs_shareddefs.h"
  20. #include "smokegrenade_projectile.h"
  21. #include "improv_locomotor.h"
  22. // NOTE: This has to be the last file included!
  23. #include "tier0/memdbgon.h"
  24. IMPLEMENT_SERVERCLASS_ST( CInferno, DT_Inferno )
  25. SendPropArray3( SENDINFO_ARRAY3(m_fireXDelta), SendPropInt( SENDINFO_ARRAY(m_fireXDelta), COORD_INTEGER_BITS+1, 0 ) ),
  26. SendPropArray3( SENDINFO_ARRAY3(m_fireYDelta), SendPropInt( SENDINFO_ARRAY(m_fireYDelta), COORD_INTEGER_BITS+1, 0 ) ),
  27. SendPropArray3( SENDINFO_ARRAY3(m_fireZDelta), SendPropInt( SENDINFO_ARRAY(m_fireZDelta), COORD_INTEGER_BITS+1, 0 ) ),
  28. SendPropArray3( SENDINFO_ARRAY3(m_bFireIsBurning), SendPropBool( SENDINFO_ARRAY(m_bFireIsBurning) ) ),
  29. //SendPropArray3( SENDINFO_ARRAY3(m_BurnNormal), SendPropVector( SENDINFO_NOCHECK( m_BurnNormal ), 0, SPROP_NORMAL ) ),
  30. SendPropInt( SENDINFO(m_fireCount), 7, SPROP_UNSIGNED ),
  31. END_SEND_TABLE()
  32. BEGIN_DATADESC( CInferno )
  33. DEFINE_THINKFUNC( InfernoThink ),
  34. END_DATADESC()
  35. LINK_ENTITY_TO_CLASS( inferno, CInferno );
  36. PRECACHE_REGISTER( inferno );
  37. IMPLEMENT_SERVERCLASS_ST( CFireCrackerBlast, DT_FireCrackerBlast )
  38. END_SEND_TABLE()
  39. LINK_ENTITY_TO_CLASS( fire_cracker_blast, CFireCrackerBlast );
  40. PRECACHE_REGISTER( fire_cracker_blast );
  41. ConVar InfernoPerFlameSpawnDuration( "inferno_per_flame_spawn_duration", "3", FCVAR_CHEAT, "Duration each new flame will attempt to spawn new flames" );
  42. ConVar InfernoInitialSpawnInterval( "inferno_initial_spawn_interval", "0.02", FCVAR_CHEAT, "Time between spawning flames for first fire" );
  43. ConVar InfernoChildSpawnIntervalMultiplier( "inferno_child_spawn_interval_multiplier", "0.1", FCVAR_CHEAT, "Amount spawn interval increases for each child" );
  44. ConVar InfernoMaxChildSpawnInterval( "inferno_max_child_spawn_interval", "0.5", FCVAR_CHEAT, "Largest time interval for child flame spawning" );
  45. ConVar InfernoSpawnAngle( "inferno_spawn_angle", "45", FCVAR_CHEAT, "Angular change from parent" );
  46. ConVar InfernoMaxFlames( "inferno_max_flames", "16", FCVAR_CHEAT, "Maximum number of flames that can be created" );
  47. ConVar InfernoFlameSpacing( "inferno_flame_spacing", "42", FCVAR_CHEAT, "Minimum distance between separate flame spawns" );
  48. ConVar InfernoFlameLifetime( "inferno_flame_lifetime", "7", FCVAR_CHEAT, "Average lifetime of each flame in seconds" );
  49. ConVar InfernoFriendlyFireDuration( "inferno_friendly_fire_duration", "6", FCVAR_CHEAT, "For this long, FF is credited back to the thrower." );
  50. ConVar InfernoDebug( "inferno_debug", "0", FCVAR_CHEAT );
  51. ConVar InfernoDamage( "inferno_damage", "40", FCVAR_CHEAT, "Damage per second" );
  52. ConVar InfernoMaxRange( "inferno_max_range", "150", FCVAR_CHEAT, "Maximum distance flames can spread from their initial ignition point" );
  53. ConVar InfernoVelocityFactor( "inferno_velocity_factor", "0.003", FCVAR_CHEAT );
  54. ConVar InfernoVelocityDecayFactor( "inferno_velocity_decay_factor", "0.2", FCVAR_CHEAT );
  55. ConVar InfernoVelocityNormalFactor( "inferno_velocity_normal_factor", "0", FCVAR_CHEAT );
  56. ConVar InfernoSurfaceOffset( "inferno_surface_offset", "20", FCVAR_CHEAT );
  57. ConVar InfernoChildSpawnMaxDepth( "inferno_child_spawn_max_depth", "4", FCVAR_CHEAT );
  58. ConVar inferno_scorch_decals( "inferno_scorch_decals", "1", FCVAR_CHEAT );
  59. ConVar inferno_max_trace_per_tick("inferno_max_trace_per_tick", "16");
  60. ConVar inferno_forward_reduction_factor( "inferno_forward_reduction_factor", "0.9", FCVAR_CHEAT );
  61. // Inferno trace masks can allow to do different traces for spreading fire
  62. #define INFERNO_MASK_TO_GROUND ((MASK_SOLID_BRUSHONLY) & (~CONTENTS_GRATE))
  63. #define INFERNO_MASK_LOS_CHECK ( INFERNO_MASK_TO_GROUND | CONTENTS_MONSTER )
  64. #define INFERNO_MASK_DAMAGE INFERNO_MASK_LOS_CHECK
  65. // Smoke grenade radius constant is actually tuned for the bots
  66. // and not for gameplay. Visualizing smoke will show that it goes
  67. // up from the emitter by 128 units (fuzzy top), nothing goes down,
  68. // and it makes a wide XY-donut with a radius of *128* units (fuzzy edges).
  69. ASSERT_INVARIANT( CONSTANT_UNITS_SMOKEGRENADERADIUS == 166 );
  70. // When interacting with fire we don't want any vertical interactions unless
  71. // contact points are definitely in smoke vertically.
  72. static const float SmokeGrenadeRadius_InfernoAffectingZ = 120.0f;
  73. // When interacting with fire on the same plane we don't want alpha depth-fighting
  74. // in the most common case, so leave a grace margin between the smoke particles
  75. // and the fire particles.
  76. static const float SmokeGrenadeRadius_InfernoAffectingXY_topedge = 100.0f;
  77. static const float SmokeGrenadeRadius_InfernoAffectingXY_equator = 150.0f;
  78. static const float SmokeGrenadeRadius_InfernoAffectingXY_bottomedge = 128.0f;
  79. // Fire burning things and smoke constants
  80. static const float InfernoFire_HalfWidth = 30.0f;
  81. static const float InfernoFire_FullHeight = 80.0f;
  82. static bool BCheckFirePointInSmokeCloud( const Vector &vecFirePoint, const Vector &vecSmokeOrigin )
  83. {
  84. const float flFireUpToSmokeCheckHeight = ( 2 * InfernoFire_HalfWidth + 4.0f );
  85. vec_t flFireAboveSmokeZ = ( vecFirePoint.z - vecSmokeOrigin.z );
  86. if ( flFireAboveSmokeZ < -flFireUpToSmokeCheckHeight )
  87. return false; // fire not tall enough to burn up to smoke
  88. if ( flFireAboveSmokeZ > SmokeGrenadeRadius_InfernoAffectingZ )
  89. return false; // smoke cloud not tall enough to reach to the fire
  90. // Now we know that fire is in XY-slice containing the smoke cloud
  91. // Figure out if we are in the equator XY-plane or in the shrinking edge XY-plane
  92. float flRadiusSquaredTest = SmokeGrenadeRadius_InfernoAffectingXY_equator*SmokeGrenadeRadius_InfernoAffectingXY_equator;
  93. if ( flFireAboveSmokeZ > SmokeGrenadeRadius_InfernoAffectingZ * 0.6f )
  94. {
  95. float flPctFromEquatorToEdge = RemapValClamped( flFireAboveSmokeZ, SmokeGrenadeRadius_InfernoAffectingZ * 0.6f, SmokeGrenadeRadius_InfernoAffectingZ, 0.0f, 1.0f );
  96. flPctFromEquatorToEdge *= flPctFromEquatorToEdge; // 0.0 still equator; 1.0 edge (squaring makes things feel quadratically closer to equator)
  97. flRadiusSquaredTest = RemapValClamped( flPctFromEquatorToEdge, 0.0f, 1.0f, flRadiusSquaredTest, SmokeGrenadeRadius_InfernoAffectingXY_topedge*SmokeGrenadeRadius_InfernoAffectingXY_topedge );
  98. }
  99. else if ( flFireAboveSmokeZ < SmokeGrenadeRadius_InfernoAffectingZ * 0.15f )
  100. {
  101. float flPctFromEquatorToEdge = RemapValClamped( flFireAboveSmokeZ, SmokeGrenadeRadius_InfernoAffectingZ * 0.1f, -flFireUpToSmokeCheckHeight, 0.0f, 1.0f );
  102. flPctFromEquatorToEdge *= flPctFromEquatorToEdge; // 0.0 still equator; 1.0 edge (squaring makes things feel quadratically closer to equator)
  103. flRadiusSquaredTest = RemapValClamped( flPctFromEquatorToEdge, 0.0f, 1.0f, flRadiusSquaredTest, SmokeGrenadeRadius_InfernoAffectingXY_bottomedge*SmokeGrenadeRadius_InfernoAffectingXY_bottomedge );
  104. }
  105. // Check if it is within XY-plane radius now
  106. vec_t lenXYsqr = ( vecFirePoint - vecSmokeOrigin ).Length2DSqr();
  107. return lenXYsqr <= flRadiusSquaredTest;
  108. }
  109. //------------------------------------------------------------------------------------------
  110. CInferno::CInferno() :
  111. m_pWeaponInfo( NULL )
  112. {
  113. // Set max flames to default in case the user doesn't ask for
  114. // more or less max flames for this inferno.
  115. SetMaxFlames( InfernoMaxFlames.GetInt() );
  116. ListenForGameEvent( "hegrenade_detonate" );
  117. ListenForGameEvent( "smokegrenade_detonate" );
  118. m_bWasCreatedInSmoke = false;
  119. }
  120. //------------------------------------------------------------------------------------------
  121. CInferno::~CInferno()
  122. {
  123. for ( int i = 0; i < m_fireCount; i++ )
  124. {
  125. delete m_fire[i];
  126. m_fire[i] = NULL;
  127. }
  128. switch( GetInfernoType() )
  129. {
  130. case INFERNO_TYPE_FIRE:
  131. case INFERNO_TYPE_INCGREN_FIRE:
  132. EmitSound( "Inferno.FadeOut" );
  133. StopSound( "Inferno.Loop" );
  134. break;
  135. case INFERNO_TYPE_FIREWORKS:
  136. EmitSound( "FireworksCrate.Stop" );
  137. StopSound( "FireworksCrate.Start" );
  138. break;
  139. }
  140. }
  141. //------------------------------------------------------------------------------------------
  142. void CInferno::Precache( void )
  143. {
  144. // extend
  145. BaseClass::Precache();
  146. PrecacheScriptSound( "Inferno.Start" );
  147. PrecacheScriptSound( "Inferno.Start_IncGrenade" );
  148. PrecacheScriptSound( "Inferno.StartSweeten" );
  149. PrecacheScriptSound( "Inferno.Loop" );
  150. PrecacheScriptSound( "Inferno.Fire.Ignite" );
  151. PrecacheScriptSound( "Inferno.FadeOut" );
  152. PrecacheParticleSystem( "extinguish_fire" );
  153. PrecacheParticleSystem( "extinsguish_fire_blastout_01" );
  154. PrecacheScriptSound( "Molotov.Throw" );
  155. PrecacheScriptSound( "FireworksCrate.Start" );
  156. PrecacheScriptSound( "FireworksCrate.Stop" );
  157. if( GetParticleEffectName() != NULL )
  158. {
  159. PrecacheParticleSystem( GetParticleEffectName() );
  160. }
  161. if( GetImpactParticleEffectName() != NULL )
  162. {
  163. PrecacheParticleSystem( GetImpactParticleEffectName() );
  164. }
  165. }
  166. //------------------------------------------------------------------------------------------
  167. void CInferno::Spawn( void )
  168. {
  169. m_fireCount = 0;
  170. const float damageRampUpTime = 2.0f;
  171. m_damageRampTimer.Start( damageRampUpTime );
  172. SetThink( &CInferno::InfernoThink );
  173. SetNextThink( gpGlobals->curtime );
  174. m_NextSpreadTimer.Start( GetFlameSpreadDelay() );
  175. AddFlag( FL_ONFIRE );
  176. SetInfernoType( INFERNO_TYPE_FIRE );
  177. }
  178. //------------------------------------------------------------------------------------------
  179. float CInferno::GetDamagePerSecond()
  180. {
  181. return InfernoDamage.GetFloat();
  182. }
  183. //------------------------------------------------------------------------------------------
  184. float CInferno::GetFlameLifetime() const
  185. {
  186. return InfernoFlameLifetime.GetFloat();
  187. }
  188. void CInferno::FireGameEvent( IGameEvent *event )
  189. {
  190. const char *eventname = event->GetName();
  191. // if ( Q_strcmp( "hegrenade_detonate", eventname ) == 0 )
  192. // {
  193. // Vector vecGrenade = Vector( ( float )event->GetInt( "x" ), ( float )event->GetInt( "y" ), ( float )event->GetInt( "z" ) );
  194. // float flGrenadeRadius = HEGrenadeRadius*1.5;
  195. //
  196. // if ((vecGrenade - m_startPos).IsLengthLessThan( flGrenadeRadius*4 ))
  197. // {
  198. // ExtinguishFlamesInSphere( vecGrenade, flGrenadeRadius );
  199. // }
  200. // }
  201. if ( Q_strcmp( "smokegrenade_detonate", eventname ) == 0 )
  202. {
  203. Vector vecGrenade = Vector( ( float )event->GetInt( "x" ), ( float )event->GetInt( "y" ), ( float )event->GetInt( "z" ) );
  204. if ((vecGrenade - m_startPos).IsLengthLessThan( SmokeGrenadeRadius*4 ))
  205. {
  206. int extinguishCount = ExtinguishFlamesAroundSmokeGrenade( vecGrenade );
  207. if ( extinguishCount == m_fireCount )
  208. {
  209. CSmokeGrenadeProjectile* pEnt = (CSmokeGrenadeProjectile*)CBaseEntity::Instance( INDEXENT( event->GetInt( "entityid" ) ) );
  210. if ( pEnt )
  211. {
  212. // If we were extinguished by a detonating smoke grenade, record it so we can report to OGS
  213. pEnt->m_unOGSExtraFlags |= CBaseCSGrenadeProjectile::GRENADE_EXTINGUISHED_INFERNO;
  214. }
  215. }
  216. }
  217. }
  218. }
  219. //------------------------------------------------------------------------------------------
  220. /**
  221. * Start the Inferno burning
  222. */
  223. void CInferno::StartBurning( const Vector &pos, const Vector &normal, const Vector &velocity, int initialDepth )
  224. {
  225. m_startPos.x = pos.x + InfernoSurfaceOffset.GetFloat() * normal.x;
  226. m_startPos.y = pos.y + InfernoSurfaceOffset.GetFloat() * normal.y;
  227. m_startPos.z = pos.z;
  228. // reflect velocity off of surface
  229. float splash = DotProduct( velocity, normal );
  230. Vector remainder = velocity - normal * splash;
  231. m_splashVelocity = remainder - InfernoVelocityNormalFactor.GetFloat() * normal * splash;
  232. QAngle splashangle;
  233. VectorAngles( velocity, splashangle );
  234. if( GetImpactParticleEffectName() != NULL )
  235. {
  236. DispatchParticleEffect( GetImpactParticleEffectName(), pos, splashangle );
  237. }
  238. if( InfernoDebug.GetBool() )
  239. {
  240. NDebugOverlay::Sphere( pos, 0.5f * InfernoFire_HalfWidth, 0, 255, 0, true, 10.0f);
  241. NDebugOverlay::Sphere( m_startPos, 0.5f * InfernoFire_HalfWidth, 255, 255, 0, true, 10.0f);
  242. }
  243. // create the initial bonfire that begins to spread
  244. if ( k_ECreateFireResult_OK == CreateFire( m_startPos, normal, NULL, initialDepth ) )
  245. {
  246. switch( GetInfernoType() )
  247. {
  248. case INFERNO_TYPE_FIRE:
  249. EmitSound( "Inferno.Start" );
  250. EmitSound( "Inferno.StartSweeten" );
  251. EmitSound( "Inferno.Loop" );
  252. break;
  253. case INFERNO_TYPE_INCGREN_FIRE:
  254. EmitSound( "Inferno.Start_IncGrenade" );
  255. EmitSound( "Inferno.StartSweeten_IncGrenade" );
  256. EmitSound( "Inferno.Loop" );
  257. break;
  258. case INFERNO_TYPE_FIREWORKS:
  259. EmitSound( "FireworksCrate.Start" );
  260. break;
  261. }
  262. m_startPos = m_fire[0]->m_pos;
  263. SetAbsOrigin( m_startPos );
  264. IGameEvent * event = gameeventmanager->CreateEvent( "inferno_startburn" );
  265. if ( event )
  266. {
  267. event->SetInt( "entityid", this->entindex() );
  268. event->SetFloat( "x", m_startPos.x );
  269. event->SetFloat( "y", m_startPos.y );
  270. event->SetFloat( "z", m_startPos.z );
  271. gameeventmanager->FireEvent( event );
  272. }
  273. m_activeTimer.Start();
  274. }
  275. else
  276. {
  277. EmitSound( "Molotov.Extinguish" );
  278. DispatchParticleEffect( "extinguish_fire", m_startPos, splashangle );
  279. UTIL_Remove( this );
  280. }
  281. }
  282. class CInfernoLOSTraceFilter : public CTraceFilter
  283. {
  284. // Find which objects can block molotov spreads
  285. virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  286. {
  287. CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
  288. // Players do not block spread
  289. if ( pEntity->IsPlayer() )
  290. return false;
  291. // Chickens, hostages, and other 'navigating' objects don't block spread
  292. if( dynamic_cast< CImprovLocomotor* >( pEntity ) != nullptr )
  293. return false;
  294. // Other objects (doors, terrain, etc.) block spread
  295. return true;
  296. }
  297. };
  298. //------------------------------------------------------------------------------------------
  299. /**
  300. * Spread the flames
  301. */
  302. void CInferno::Spread( const Vector &spreadVelocity )
  303. {
  304. if( m_NextSpreadTimer.HasStarted() && !m_NextSpreadTimer.IsElapsed() )
  305. {
  306. return;
  307. }
  308. m_NextSpreadTimer.Start( GetFlameSpreadDelay() );
  309. for ( int i = 0; i < m_fireCount; i++ )
  310. {
  311. // attempt to spawn child-flames
  312. FireInfo *fire = m_fire[ i ];
  313. if ( !fire->m_burning ||
  314. fire->m_lifetime.IsElapsed() )
  315. continue; // This flame has been extinguished or elapsed, shouldn't be spreading from here
  316. if ( !fire->m_spawnLifetime.IsElapsed() && fire->m_spawnTimer.IsElapsed() )
  317. {
  318. fire->m_spawnTimer.Reset();
  319. fire->m_spawnCount++;
  320. }
  321. }
  322. int traceCount = inferno_max_trace_per_tick.GetInt();
  323. int nextFireOffset = m_fireSpawnOffset + 1;
  324. for ( int i = 0; i < m_fireCount && traceCount > 0; i++ )
  325. {
  326. if ( m_fireCount >= MIN( ( int ) MAX_INFERNO_FIRES, m_nMaxFlames ) )
  327. break;
  328. int fireIndex = (i + m_fireSpawnOffset) % m_fireCount;
  329. FireInfo *fire = m_fire[fireIndex];
  330. nextFireOffset = fireIndex;
  331. if ( !fire->m_spawnCount )
  332. continue;
  333. if ( !fire->m_burning ||
  334. fire->m_lifetime.IsElapsed() )
  335. continue; // This flame has been extinguished or elapsed, shouldn't be spreading from here
  336. int depth = fire->m_treeDepth + 1;
  337. if ( depth >= InfernoChildSpawnMaxDepth.GetInt() )
  338. continue;
  339. fire->m_spawnCount--;
  340. trace_t tr;
  341. const int maxRetry = 4;
  342. for( int t=0; t<maxRetry; ++t )
  343. {
  344. Vector out;
  345. if (fire->m_parent == NULL)
  346. {
  347. // initial fire spreads outward in a circle
  348. float angle = random->RandomFloat( -3.14159f, 3.14159f );
  349. out = Vector( cos(angle), sin(angle), 0.0f );
  350. }
  351. else
  352. {
  353. // child flames tend to spread away from their parent
  354. Vector to = fire->m_pos - fire->m_parent->m_pos;
  355. to.NormalizeInPlace();
  356. QAngle angles;
  357. VectorAngles( to, angles );
  358. angles.y += random->RandomFloat( -InfernoSpawnAngle.GetFloat(), InfernoSpawnAngle.GetFloat() );
  359. AngleVectors( angles, &out );
  360. }
  361. // If we're going into a wall, don't keep trying to spread into a wall the entire lifetime - back off to
  362. // a circular spread at the end.
  363. float velocityDecay = pow( InfernoVelocityDecayFactor.GetFloat(), float(fire->m_treeDepth) );
  364. Vector timeAdjustedSpreadVelocity = spreadVelocity * fire->m_lifetime.GetRemainingRatio() * velocityDecay;
  365. out += InfernoVelocityFactor.GetFloat() * timeAdjustedSpreadVelocity;
  366. // put fire on plane of ground
  367. Vector side = CrossProduct( fire->m_normal, out );
  368. out = CrossProduct( side, fire->m_normal );
  369. float range = random->RandomFloat( 50.0f, 75.0f );
  370. Vector pos = fire->m_pos + range * out;
  371. // limit maximum range of spread
  372. Vector fireDir = pos - m_startPos;
  373. if ( fireDir.IsLengthGreaterThan( InfernoMaxRange.GetFloat() ) )
  374. {
  375. VectorNormalize(fireDir);
  376. fireDir *= InfernoMaxRange.GetFloat();
  377. pos = m_startPos + fireDir;
  378. }
  379. // dont let flames fall too far
  380. const float maxDrop = 200.0f;
  381. Vector endPos = pos;
  382. endPos.z = fire->m_pos.z - maxDrop;
  383. // put fire on the ground
  384. UTIL_TraceLine( pos + Vector( 0, 0, 50.0f ), endPos, INFERNO_MASK_TO_GROUND, NULL, COLLISION_GROUP_NONE, &tr );
  385. traceCount--;
  386. if (!tr.DidHit())
  387. {
  388. if ( InfernoDebug.GetBool() )
  389. {
  390. NDebugOverlay::Line( pos + Vector( 0, 0, 50.0f ), endPos, 255, 255, 0, true, 1.0f );
  391. NDebugOverlay::Cross3D( pos, 5, 255, 0, 0, true, 1.0f );
  392. }
  393. m_splashVelocity *= inferno_forward_reduction_factor.GetFloat();
  394. continue;
  395. }
  396. pos.z = tr.endpos.z;
  397. Vector normal = tr.plane.normal;
  398. // make sure we dont go through walls
  399. const Vector fireHeight( 0, 0, InfernoFire_HalfWidth );
  400. CInfernoLOSTraceFilter losTraceFilter;
  401. UTIL_TraceLine( fire->m_pos + fireHeight, pos + fireHeight, INFERNO_MASK_LOS_CHECK, &losTraceFilter, &tr );
  402. traceCount--;
  403. if (tr.fraction < 1.0f)
  404. {
  405. if ( InfernoDebug.GetBool() )
  406. {
  407. NDebugOverlay::Line( fire->m_pos + fireHeight, pos + fireHeight, 255, 0, 0, true, 1.0f );
  408. }
  409. m_splashVelocity *= inferno_forward_reduction_factor.GetFloat();
  410. continue;
  411. }
  412. ECreateFireResult_t eCreateFireResult = CreateFire( pos, normal, fire, depth );
  413. if ( ( eCreateFireResult == k_ECreateFireResult_OK )
  414. || ( eCreateFireResult == k_ECreateFireResult_LimitExceeded ) )
  415. break;
  416. else if ( eCreateFireResult != k_ECreateFireResult_AlreadyOnFire )
  417. m_splashVelocity *= inferno_forward_reduction_factor.GetFloat();
  418. if ( InfernoDebug.GetBool() )
  419. {
  420. if ( eCreateFireResult == k_ECreateFireResult_InSmoke )
  421. NDebugOverlay::Line( fire->m_pos + fireHeight, pos + fireHeight, 255, 255, 0, true, 10.0f );
  422. else if ( eCreateFireResult == k_ECreateFireResult_AlreadyOnFire )
  423. NDebugOverlay::Line( fire->m_pos + fireHeight, pos + fireHeight, 255, 100, 100, true, 2.0f );
  424. else
  425. NDebugOverlay::Line( fire->m_pos + fireHeight, pos + fireHeight, 255, 100, 0, true, 10.0f );
  426. }
  427. }
  428. }
  429. m_fireSpawnOffset = nextFireOffset + 1;
  430. }
  431. //------------------------------------------------------------------------------------------
  432. /**
  433. * Checks whether destination fire point is within a detonated smoke grenade cloud
  434. * This is useful to deny molly detonation in smoke, and to deny fire flames spreading into smoke
  435. */
  436. bool CInferno::IsFirePosInSmokeCloud( const Vector &pos ) const
  437. {
  438. const int INFERNO_SEARCH_ENTS = 32;
  439. CBaseEntity *pEntities[ INFERNO_SEARCH_ENTS ];
  440. int iNumEntities = UTIL_EntitiesInSphere( pEntities, INFERNO_SEARCH_ENTS, pos, SmokeGrenadeRadius, FL_GRENADE );
  441. for ( int i = 0; i < iNumEntities; i++ )
  442. {
  443. CSmokeGrenadeProjectile *pGrenade = dynamic_cast< CSmokeGrenadeProjectile * >( pEntities[ i ] );
  444. if ( pGrenade && pGrenade->m_bDidSmokeEffect
  445. && BCheckFirePointInSmokeCloud( pos, pGrenade->GetAbsOrigin() ) )
  446. {
  447. return true;
  448. }
  449. }
  450. return false;
  451. }
  452. //------------------------------------------------------------------------------------------
  453. /**
  454. * Create an actual fire entity at the given position
  455. */
  456. CInferno::ECreateFireResult_t CInferno::CreateFire( const Vector &pos, const Vector &normal, FireInfo *parent, int depth )
  457. {
  458. if ( m_fireCount >= MIN( ( int ) MAX_INFERNO_FIRES, m_nMaxFlames ) )
  459. {
  460. return k_ECreateFireResult_LimitExceeded;
  461. }
  462. if ( IsTouching( pos, pos, NULL ) )
  463. {
  464. // we already created a fire here
  465. return k_ECreateFireResult_AlreadyOnFire;
  466. }
  467. // if we throw down a molly in the middle of a smoke grenade, DENY!
  468. if( IsFirePosInSmokeCloud( pos ) )
  469. {
  470. m_bWasCreatedInSmoke = true;
  471. return k_ECreateFireResult_InSmoke;
  472. }
  473. if (InfernoDebug.GetBool())
  474. {
  475. if (parent)
  476. {
  477. NDebugOverlay::Line( parent->m_pos, pos, 0, 255, 255, true, 10.0f );
  478. }
  479. }
  480. Vector firePos( pos );
  481. bool overWater = false;
  482. trace_t tr;
  483. int contents = enginetrace->GetPointContents( pos, MASK_WATER );
  484. if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME ) )
  485. {
  486. Vector fireHeight( 0, 0, 30.0f );
  487. int mask = MASK_SOLID_BRUSHONLY | CONTENTS_SLIME | CONTENTS_WATER;
  488. UTIL_TraceLine( pos + fireHeight, pos, mask, NULL, COLLISION_GROUP_NONE, &tr );
  489. if ( tr.allsolid )
  490. {
  491. return k_ECreateFireResult_AllSolid;
  492. }
  493. else
  494. {
  495. firePos = tr.endpos;
  496. overWater = true;
  497. }
  498. }
  499. FireInfo *fire = new FireInfo;
  500. fire->m_pos = firePos;
  501. fire->m_center = firePos + Vector( 0, 0, 0.5f * InfernoFire_FullHeight );
  502. fire->m_normal = normal;
  503. fire->m_parent = parent;
  504. fire->m_treeDepth = depth;
  505. fire->m_spawnCount = 0;
  506. fire->m_flWaterHeight = firePos.z - pos.z;
  507. fire->m_burning = true;
  508. // all control points on the client die down at the same time, so the server needs to match this
  509. if ( m_activeTimer.HasStarted() )
  510. {
  511. fire->m_lifetime.Start( GetFlameLifetime() - m_activeTimer.GetElapsedTime() );
  512. }
  513. else
  514. {
  515. fire->m_lifetime.Start( GetFlameLifetime() );
  516. }
  517. if (parent)
  518. {
  519. fire->m_spawnLifetime.Start( parent->m_spawnLifetime.GetCountdownDuration() );
  520. float duration = InfernoChildSpawnIntervalMultiplier.GetFloat() * parent->m_spawnTimer.GetCountdownDuration();
  521. if (duration > InfernoMaxChildSpawnInterval.GetFloat())
  522. duration = InfernoMaxChildSpawnInterval.GetFloat();
  523. fire->m_spawnTimer.Start( duration );
  524. }
  525. else
  526. {
  527. fire->m_spawnLifetime.Start( InfernoPerFlameSpawnDuration.GetFloat() );
  528. fire->m_spawnTimer.Start( InfernoInitialSpawnInterval.GetFloat() );
  529. }
  530. // keep a simple array of all active fires
  531. m_fire[ m_fireCount ] = fire;
  532. // propogate across the network
  533. // Compute this fire's position relative to the Inferno entity.
  534. Vector vecDelta = fire->m_pos - GetAbsOrigin();
  535. m_fireXDelta.Set( m_fireCount, (int)vecDelta.x );
  536. m_fireYDelta.Set( m_fireCount, (int)vecDelta.y );
  537. m_fireZDelta.Set( m_fireCount, (int)vecDelta.z );
  538. m_bFireIsBurning.Set( m_fireCount, true );
  539. m_BurnNormal.Set( m_fireCount, normal );
  540. ++m_fireCount;
  541. RecomputeExtent();
  542. // emit a small flame burst sound
  543. if( GetInfernoType() == INFERNO_TYPE_FIRE || GetInfernoType() == INFERNO_TYPE_INCGREN_FIRE )
  544. {
  545. CSoundParameters params;
  546. if ( GetParametersForSound( "Inferno.Fire.Ignite", params, NULL ) )
  547. {
  548. EmitSound_t ep( params );
  549. ep.m_pOrigin = &fire->m_pos;
  550. CBroadcastRecipientFilter filter;
  551. EmitSound( filter, SOUND_FROM_WORLD, ep );
  552. }
  553. if ( inferno_scorch_decals.GetBool() && !overWater )
  554. {
  555. trace_t trace;
  556. const float dist = 100.0f;
  557. Vector dir( 0, 0, -1 );
  558. UTIL_TraceLine( fire->m_pos, fire->m_pos + dir * dist, MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &trace );
  559. UTIL_DecalTrace( &trace, "MolotovScorch" );
  560. }
  561. }
  562. return k_ECreateFireResult_OK;
  563. }
  564. void CInferno::ExtinguishIndividualFlameBySmokeGrenade( int iFire, Vector vecStart )
  565. {
  566. m_fire[ iFire ]->m_lifetime.Invalidate();
  567. Vector vecAngleAway = m_fire[ iFire ]->m_pos - vecStart;
  568. vecAngleAway.NormalizeInPlace();
  569. QAngle angParticle;
  570. VectorAngles( vecAngleAway, angParticle );
  571. DispatchParticleEffect( "extinguish_fire", m_fire[ iFire ]->m_pos, angParticle );
  572. }
  573. int CInferno::ExtinguishFlamesAroundSmokeGrenade( Vector vecStart )
  574. {
  575. bool bExtinguished = false;
  576. bool bCheckDistanceForFlames = true;
  577. int nNumExtinguished = 0;
  578. // if the radius overlaps the center, extinguish the whole flame
  579. if ( BCheckFirePointInSmokeCloud( m_startPos, vecStart ) )
  580. bCheckDistanceForFlames = false;
  581. for( int i=0; i<m_fireCount; ++i )
  582. {
  583. // if this fire just died, propagate over the network
  584. if ( m_fire[i]->m_burning && (
  585. !bCheckDistanceForFlames || BCheckFirePointInSmokeCloud( m_fire[i]->m_pos, vecStart )
  586. ) )
  587. {
  588. ExtinguishIndividualFlameBySmokeGrenade( i, vecStart );
  589. bExtinguished = true;
  590. nNumExtinguished++;
  591. }
  592. }
  593. // if we extinguished third or more of our fire, just put out the rest
  594. if ( !bCheckDistanceForFlames && nNumExtinguished >= (m_fireCount/3) )
  595. {
  596. for( int i=0; i<m_fireCount; ++i )
  597. {
  598. // if this fire just died, propagate over the network
  599. if ( !m_fire[i]->m_lifetime.IsElapsed() )
  600. {
  601. ExtinguishIndividualFlameBySmokeGrenade( i, vecStart );
  602. nNumExtinguished++;
  603. }
  604. }
  605. }
  606. if ( bExtinguished )
  607. {
  608. EmitSound( "Molotov.Extinguish" );
  609. IGameEvent * event = gameeventmanager->CreateEvent( "inferno_extinguish" );
  610. if ( event )
  611. {
  612. event->SetInt( "entityid", this->entindex() );
  613. event->SetFloat( "x", m_startPos.x );
  614. event->SetFloat( "y", m_startPos.y );
  615. event->SetFloat( "z", m_startPos.z );
  616. gameeventmanager->FireEvent( event );
  617. }
  618. }
  619. return nNumExtinguished;
  620. }
  621. bool CInferno::CheckExpired()
  622. {
  623. VPROF_BUDGET( "CInferno::CheckExpired (check lifetimes)", "Fire" );
  624. bool bIsAttachedToMovingObject = GetParent() != NULL;
  625. Vector vecInfernoOrigin = GetAbsOrigin();
  626. // check lifetime of flames
  627. bool isDone = true;
  628. for ( int i = 0; i < m_fireCount; ++i )
  629. {
  630. // Already dead.
  631. if ( !m_fire[i]->m_burning )
  632. continue;
  633. // if this fire just died, propagate over the network
  634. if ( m_fire[i]->m_lifetime.IsElapsed() )
  635. {
  636. m_fire[i]->m_pos = Vector( 0, 0, 0 );
  637. m_fire[i]->m_burning = false;
  638. m_bFireIsBurning.Set( i, false );
  639. continue;
  640. }
  641. // still at least one fire alive
  642. isDone = false;
  643. m_fire[i]->m_pos = vecInfernoOrigin;
  644. m_fire[i]->m_pos.x += m_fireXDelta[i];
  645. m_fire[i]->m_pos.y += m_fireYDelta[i];
  646. m_fire[i]->m_pos.z += m_fireZDelta[i];
  647. if ( bIsAttachedToMovingObject )
  648. {
  649. RecomputeExtent();
  650. }
  651. if ( InfernoDebug.GetBool() )
  652. {
  653. NDebugOverlay::Sphere( m_fire[i]->m_pos, 2.0f * InfernoFire_HalfWidth, 255, 100, 0, true, 0.1f );
  654. }
  655. }
  656. if ( isDone )
  657. {
  658. IGameEvent * event = gameeventmanager->CreateEvent( "inferno_expire" );
  659. if ( event )
  660. {
  661. event->SetInt( "entityid", this->entindex() );
  662. event->SetFloat( "x", m_startPos.x );
  663. event->SetFloat( "y", m_startPos.y );
  664. event->SetFloat( "z", m_startPos.z );
  665. gameeventmanager->FireEvent( event );
  666. }
  667. // if all fires have burned out, we're done
  668. UTIL_Remove( this );
  669. // Expired!
  670. return true;
  671. }
  672. // Not expired
  673. return false;
  674. }
  675. void CInferno::MarkCoveredAreaAsDamaging()
  676. {
  677. // mark overlapping nav areas as "damaging"
  678. NavAreaCollector overlap;
  679. // bloat extents enough to ensure any non-damaging area is actually safe
  680. // bloat in Z as well to catch nav areas that may be slightly above/below ground
  681. Extent extent = m_extent;
  682. float DangerBloat = 32.0f;
  683. Vector dangerBloat( DangerBloat, DangerBloat, DangerBloat );
  684. extent.lo -= dangerBloat;
  685. extent.hi += dangerBloat;
  686. //NDebugOverlay::Box( vec3_origin, extent.lo, extent.hi, 0, 255, 0, 10, 0.1f );
  687. TheNavMesh->ForAllAreasOverlappingExtent( overlap, extent );
  688. FOR_EACH_VEC( overlap.m_area, it )
  689. {
  690. CNavArea *area = overlap.m_area[it];
  691. if ( IsTouching( area ) )
  692. {
  693. area->MarkAsDamaging( 1.0f );
  694. }
  695. }
  696. }
  697. //------------------------------------------------------------------------------------------
  698. /**
  699. * Spread the flames
  700. */
  701. void CInferno::InfernoThink( void )
  702. {
  703. VPROF_BUDGET( "CInferno::InfernoThink", "Fire" );
  704. bool bExpiryCheckPerformed = false;
  705. // Run bookkeeping every 0.1s
  706. if ( m_BookkeepingTimer.Interval(0.1f) )
  707. {
  708. bExpiryCheckPerformed = true;
  709. if ( CheckExpired() )
  710. return;
  711. // the fire grows...
  712. if ( m_fireCount > 0 && m_fireCount < MIN( m_nMaxFlames, ( int )MAX_INFERNO_FIRES ) )
  713. {
  714. VPROF_BUDGET( "CInferno::InfernoThink (spread)", "Fire" );
  715. Spread( m_splashVelocity );
  716. }
  717. // Mark area as damaging for bot avoidance
  718. MarkCoveredAreaAsDamaging();
  719. }
  720. #if 0
  721. // Debug draw flame region
  722. NDebugOverlay::Box( vec3_origin, m_extent.lo, m_extent.hi, 255, 255, 255, 10, 0.1f );
  723. #endif
  724. // Deal damage every 0.2s
  725. const float kDamageTimerSeconds = 0.2f;
  726. while ( m_damageTimer.RunEvery( kDamageTimerSeconds ) )
  727. {
  728. // Note that we run a lot of code in this RunEvery(), but we expect the loop to run 0 or 1 times
  729. // in almost every case, unless this think function somehow got super delayed by the server
  730. if(!bExpiryCheckPerformed)
  731. {
  732. bExpiryCheckPerformed = true;
  733. if ( CheckExpired() )
  734. return;
  735. }
  736. VPROF_BUDGET( "CInferno::InfernoThink (damage)", "Fire" );
  737. const int maxVictims = 256;
  738. CBaseEntity *damageList[ maxVictims ];
  739. CBaseEntity *owner = GetOwnerEntity();
  740. int damageCount = 0;
  741. const float flameRadius = 2.0f * InfernoFire_HalfWidth;
  742. CBaseEntity *list[ maxVictims ];
  743. int count = UTIL_EntitiesInBox( list, maxVictims, m_extent.lo, m_extent.hi, 0 );
  744. for( int i=0; i<count; ++i )
  745. {
  746. if (list[i] == NULL || !list[i]->IsAlive() || list[i] == this)
  747. continue;
  748. if (IsTouching( list[i], flameRadius, list[i]->IsPlayer() ))
  749. {
  750. damageList[damageCount] = list[i];
  751. damageCount++;
  752. }
  753. }
  754. int damageType = GetDamageType();
  755. #if !defined( CSTRIKE15 )
  756. // After the first few seconds of burning, the thrower isn't responsible for teammates who run into the fire.
  757. if ( m_activeTimer.GetElapsedTime() > InfernoFriendlyFireDuration.GetFloat() || owner == NULL )
  758. {
  759. damageType |= DMG_BLAMELESS_FRIENDLY_FIRE; // Add in a flag to prevent FF demerits
  760. }
  761. #endif
  762. // Note that we expect molotov this value to be an integer (currently it is 40 * 0.2 == 8 damage per tick)
  763. // If molotov DPS changes, we may need to also adjust how often damage is applied.
  764. //
  765. // We could also change this to tick at the exact rate required to deal 1 damage (tick rate = 1 / GetDamagePerSecond())
  766. // at which point we might want to consider optimizing this loop to run a single time and multiply its damage
  767. // by the damage dealt, so that (for example) if molotovs deal 80 dps on a 64 tick server, we only find the targets
  768. // once during the ticks when the molotov deals 2 damage.
  769. float baseDamage = GetDamagePerSecond() * kDamageTimerSeconds; // dmg / sec * sec / tick = dmg / tick
  770. if ( !m_damageRampTimer.IsElapsed() )
  771. {
  772. baseDamage *= m_damageRampTimer.GetElapsedRatio();
  773. }
  774. for ( int i = 0; i < damageCount; i++ )
  775. {
  776. // damage the victim
  777. CBaseEntity *pEnt = damageList[i];
  778. float damage = baseDamage;
  779. if( CanHarm( pEnt ) )
  780. {
  781. CTakeDamageInfo info( this, owner, damage, damageType );
  782. pEnt->TakeDamage( info );
  783. }
  784. }
  785. }
  786. // Figure out when our next event is and make sure we get a think tick close to it.
  787. float nextThink = m_BookkeepingTimer.GetTargetTime();
  788. nextThink = MIN( nextThink, m_damageTimer.GetTargetTime() );
  789. SetNextThink( nextThink );
  790. }
  791. //-----------------------------------------------------------------------------------------------
  792. void CInferno::RecomputeExtent( void )
  793. {
  794. m_extent.lo = Vector( 999999.9f, 999999.9f, 999999.9f );
  795. m_extent.hi = Vector( -999999.9f, -999999.9f, -999999.9f );
  796. for( int i=0; i<m_fireCount; ++i )
  797. {
  798. FireInfo *fire = m_fire[i];
  799. if ( fire->m_pos.x - InfernoFire_HalfWidth < m_extent.lo.x )
  800. m_extent.lo.x = fire->m_pos.x - InfernoFire_HalfWidth;
  801. if ( fire->m_pos.x + InfernoFire_HalfWidth > m_extent.hi.x )
  802. m_extent.hi.x = fire->m_pos.x + InfernoFire_HalfWidth;
  803. if ( fire->m_pos.y - InfernoFire_HalfWidth < m_extent.lo.y )
  804. m_extent.lo.y = fire->m_pos.y - InfernoFire_HalfWidth;
  805. if ( fire->m_pos.y + InfernoFire_HalfWidth > m_extent.hi.y )
  806. m_extent.hi.y = fire->m_pos.y + InfernoFire_HalfWidth;
  807. if ( fire->m_pos.z < m_extent.lo.z )
  808. m_extent.lo.z = fire->m_pos.z;
  809. if ( fire->m_pos.z + InfernoFire_FullHeight > m_extent.hi.z )
  810. m_extent.hi.z = fire->m_pos.z + InfernoFire_FullHeight;
  811. }
  812. }
  813. bool CInferno::BShouldExtinguishSmokeGrenadeBounce( CBaseEntity *entity, Vector &posDropSmoke ) const
  814. {
  815. const float radius = 2.0f * InfernoFire_HalfWidth;
  816. for ( int i = 0; i < m_fireCount; i++ )
  817. {
  818. FireInfo *fire = m_fire[ i ];
  819. if ( !fire->m_burning ||
  820. fire->m_lifetime.IsElapsed() )
  821. continue; // This flame has been extinguished or elapsed, shouldn't cause damage
  822. if ( ( posDropSmoke - fire->m_center ).IsLengthLessThan( radius ) )
  823. {
  824. // doublecheck los if required
  825. trace_t tr;
  826. const Vector fireHeight( 0, 0, InfernoFire_HalfWidth );
  827. UTIL_TraceLine( fire->m_center + fireHeight, posDropSmoke, INFERNO_MASK_DAMAGE, entity, COLLISION_GROUP_NONE, &tr );
  828. if ( tr.fraction < 1.0f )
  829. UTIL_TraceLine( fire->m_center, posDropSmoke, INFERNO_MASK_DAMAGE, entity, COLLISION_GROUP_NONE, &tr );
  830. if ( tr.fraction == 1.0f )
  831. {
  832. if ( InfernoDebug.GetBool() )
  833. {
  834. NDebugOverlay::Line( fire->m_center, posDropSmoke, 255, 0, 255, true, 50.2f );
  835. }
  836. return true;
  837. }
  838. else
  839. {
  840. if ( InfernoDebug.GetBool() )
  841. {
  842. NDebugOverlay::Line( fire->m_center, posDropSmoke, 255, 0, 0, true, 50.2f );
  843. }
  844. }
  845. }
  846. }
  847. return false;
  848. }
  849. //-----------------------------------------------------------------------------------------------
  850. /**
  851. * Return true if position is in contact with a fire within the Inferno
  852. */
  853. bool CInferno::IsTouching( CBaseEntity *entity, float radius, bool checkLOS ) const
  854. {
  855. if ( entity != NULL )
  856. {
  857. for ( int i = 0; i < m_fireCount; i++ )
  858. {
  859. FireInfo *fire = m_fire[i];
  860. if ( !fire->m_burning ||
  861. fire->m_lifetime.IsElapsed() )
  862. continue; // This flame has been extinguished or elapsed, shouldn't cause damage
  863. // Calculate the nearest point to our potential victim, from our center point
  864. const Vector fireHeight( 0, 0, InfernoFire_HalfWidth );
  865. Vector pos;
  866. Vector fireCheck = fire->m_center;
  867. if( checkLOS )
  868. fireCheck += fireHeight;
  869. entity->CollisionProp()->CalcNearestPoint( fireCheck, &pos );
  870. if ( ( pos - fireCheck ).IsLengthLessThan( radius ) )
  871. {
  872. // touching at least one flame
  873. if( checkLOS )
  874. {
  875. // doublecheck los if required
  876. trace_t tr;
  877. UTIL_TraceLine( fireCheck, pos, INFERNO_MASK_DAMAGE, entity, COLLISION_GROUP_NONE, &tr );
  878. if ( tr.fraction < 1.0f )
  879. {
  880. fireCheck = fire->m_center;
  881. entity->CollisionProp()->CalcNearestPoint( fireCheck, &pos );
  882. if ( ( pos - fireCheck ).IsLengthLessThan( radius ) )
  883. UTIL_TraceLine( fireCheck, pos, INFERNO_MASK_DAMAGE, entity, COLLISION_GROUP_NONE, &tr );
  884. }
  885. if( tr.fraction == 1.0f )
  886. {
  887. if( InfernoDebug.GetBool() )
  888. {
  889. NDebugOverlay::Line( fire->m_center, pos, 255, 0, 255, true, 50.2f );
  890. }
  891. return true;
  892. }
  893. else
  894. {
  895. if( InfernoDebug.GetBool() )
  896. {
  897. NDebugOverlay::Line( fire->m_center, pos, 255, 0, 0, true, 50.2f );
  898. }
  899. }
  900. }
  901. else
  902. {
  903. // los not needed, it's touching
  904. if( InfernoDebug.GetBool() )
  905. {
  906. NDebugOverlay::Line( fire->m_center, pos, 255, 0, 255, true, 0.2f );
  907. }
  908. return true;
  909. }
  910. }
  911. }
  912. }
  913. return false;
  914. }
  915. //-----------------------------------------------------------------------------------------------
  916. /**
  917. * Return true if given ray intersects any fires
  918. * TODO: Check LOS if needed.
  919. */
  920. bool CInferno::IsTouching( const Vector &from, const Vector &to, Vector *where ) const
  921. {
  922. for ( int i = 0; i < m_fireCount; i++ )
  923. {
  924. FireInfo *fire = m_fire[i];
  925. if ( !fire->m_burning ||
  926. fire->m_lifetime.IsElapsed() )
  927. continue; // This flame has been extinguished or elapsed, shouldn't be considered touching
  928. Vector pointOnRay;
  929. ClosestPointOnRay( m_fire[i]->m_center, from, to, &pointOnRay );
  930. const float radius = 2.0f * InfernoFire_HalfWidth;
  931. if ( ( pointOnRay - m_fire[i]->m_center ).IsLengthLessThan( radius ) )
  932. {
  933. if ( where )
  934. {
  935. *where = pointOnRay;
  936. }
  937. return true;
  938. }
  939. }
  940. return false;
  941. }
  942. //-----------------------------------------------------------------------------------------------
  943. /**
  944. * Return true if given area overlaps any fires
  945. */
  946. bool CInferno::IsTouching( const CNavArea *area ) const
  947. {
  948. if ( area != NULL )
  949. {
  950. float radius = 2.0f * InfernoFire_HalfWidth;
  951. for ( int i = 0; i < m_fireCount; i++ )
  952. {
  953. FireInfo *fire = m_fire[ i ];
  954. if ( !fire->m_burning ||
  955. fire->m_lifetime.IsElapsed() )
  956. continue; // This flame has been extinguished or elapsed, shouldn't be considered touching
  957. Vector close;
  958. area->GetClosestPointOnArea( m_fire[i]->m_center, &close );
  959. 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
  960. if ( ( close - m_fire[i]->m_center ).IsLengthLessThan( radius ) )
  961. {
  962. return true;
  963. }
  964. }
  965. }
  966. return false;
  967. }
  968. //------------------------------------------------------------------------------------------
  969. //
  970. //------------------------------------------------------------------------------------------
  971. void CFireCrackerBlast::Spawn( void )
  972. {
  973. BaseClass::Spawn();
  974. SetInfernoType( INFERNO_TYPE_FIREWORKS );
  975. }