From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/game/server/fire.cpp | 1463 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1463 insertions(+) create mode 100644 mp/src/game/server/fire.cpp (limited to 'mp/src/game/server/fire.cpp') diff --git a/mp/src/game/server/fire.cpp b/mp/src/game/server/fire.cpp new file mode 100644 index 00000000..75a11399 --- /dev/null +++ b/mp/src/game/server/fire.cpp @@ -0,0 +1,1463 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//--------------------------------------------------------- +//--------------------------------------------------------- +#include "cbase.h" +#include "decals.h" +#include "fire.h" +#include "entitylist.h" +#include "basecombatcharacter.h" +#include "ndebugoverlay.h" +#include "engine/IEngineSound.h" +#include "ispatialpartition.h" +#include "collisionutils.h" +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +/******************************************************************** + NOTE: if you are looking at this file becase you would like flares + to be considered as fires (and thereby trigger gas traps), be aware + that the env_flare class is actually found in weapon_flaregun.cpp + and is really a repurposed piece of ammunition. (env_flare isn't the + rod-like safety flare prop, but rather the bit of flame on the end.) + + You will have some difficulty making it work here, because CFlare + does not inherit from CFire and will thus not be enumerated by + CFireSphere::EnumElement(). In order to have flares be detected and + used by this system, you will need to promote certain member functions + of CFire into an interface class from which both CFire and CFlare + inherit. You will also need to modify CFireSphere::EnumElement so that + it properly disambiguates between fires and flares. + + For some partial work towards this end, see changelist 192474. + + ********************************************************************/ + + + + +#define FIRE_HEIGHT 256.0f +#define FIRE_SCALE_FROM_SIZE(firesize) (firesize * (1/FIRE_HEIGHT)) + +#define FIRE_MAX_GROUND_OFFSET 24.0f //(2 feet) + +#define DEFAULT_ATTACK_TIME 4.0f +#define DEFAULT_DECAY_TIME 8.0f + +// UNDONE: This shouldn't be constant but depend on specific fire +#define FIRE_WIDTH 128 +#define FIRE_MINS Vector(-20,-20,0 ) // Sould be FIRE_WIDTH in size +#define FIRE_MAXS Vector( 20, 20,20) // Sould be FIRE_WIDTH in size +#define FIRE_SPREAD_DAMAGE_MULTIPLIER 2.0 + +#define FIRE_MAX_HEAT_LEVEL 64.0f +#define FIRE_NORMAL_ATTACK_TIME 20.0f +#define FIRE_THINK_INTERVAL 0.1 + +ConVar fire_maxabsorb( "fire_maxabsorb", "50" ); +ConVar fire_absorbrate( "fire_absorbrate", "3" ); +ConVar fire_extscale("fire_extscale", "12"); +ConVar fire_extabsorb("fire_extabsorb", "5"); +ConVar fire_heatscale( "fire_heatscale", "1.0" ); +ConVar fire_incomingheatscale( "fire_incomingheatscale", "0.1" ); +ConVar fire_dmgscale( "fire_dmgscale", "0.1" ); +ConVar fire_dmgbase( "fire_dmgbase", "1" ); +ConVar fire_growthrate( "fire_growthrate", "1.0" ); +ConVar fire_dmginterval( "fire_dmginterval", "1.0" ); + +#define VPROF_FIRE(s) VPROF( s ) + +class CFire : public CBaseEntity +{ +public: + DECLARE_CLASS( CFire, CBaseEntity ); + + int DrawDebugTextOverlays(void); + + CFire( void ); + + virtual void UpdateOnRemove( void ); + + void Precache( void ); + void Init( const Vector &position, float scale, float attackTime, float fuel, int flags, int fireType ); + bool GoOut(); + + void BurnThink(); + void GoOutThink(); + void GoOutInSeconds( float seconds ); + + void SetOwner( CBaseEntity *hOwner ) { m_hOwner = hOwner; } + + void Scale( float end, float time ); + void AddHeat( float heat, bool selfHeat = false ); + int OnTakeDamage( const CTakeDamageInfo &info ); + + bool IsBurning( void ) const; + + bool GetFireDimensions( Vector *pFireMins, Vector *pFireMaxs ); + + void Extinguish( float heat ); + void DestroyEffect(); + + virtual void Update( float simTime ); + + void Spawn( void ); + void Activate( void ); + void StartFire( void ); + void Start(); + void SetToOutSize() + { + UTIL_SetSize( this, Vector(-8,-8,0), Vector(8,8,8) ); + } + + float GetHeatLevel() { return m_flHeatLevel; } + + virtual int UpdateTransmitState(); + + void DrawDebugGeometryOverlays(void) + { + if (m_debugOverlays & OVERLAY_BBOX_BIT) + { + if ( m_lastDamage > gpGlobals->curtime && m_flHeatAbsorb > 0 ) + { + NDebugOverlay::EntityBounds(this, 88, 255, 128, 0 ,0); + char tempstr[512]; + Q_snprintf( tempstr, sizeof(tempstr), "Heat: %.1f", m_flHeatAbsorb ); + EntityText(1,tempstr, 0); + } + else if ( !IsBurning() ) + { + NDebugOverlay::EntityBounds(this, 88, 88, 128, 0 ,0); + } + + if ( IsBurning() ) + { + Vector mins, maxs; + if ( GetFireDimensions( &mins, &maxs ) ) + { + NDebugOverlay::Box(GetAbsOrigin(), mins, maxs, 128, 0, 0, 10, 0); + } + } + + + } + BaseClass::DrawDebugGeometryOverlays(); + } + + void Disable(); + + //Inputs + void InputStartFire( inputdata_t &inputdata ); + void InputExtinguish( inputdata_t &inputdata ); + void InputExtinguishTemporary( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + +protected: + + void Spread( void ); + void SpawnEffect( fireType_e type, float scale ); + + CHandle m_hEffect; + EHANDLE m_hOwner; + + int m_nFireType; + + float m_flFuel; + float m_flDamageTime; + float m_lastDamage; + float m_flFireSize; // size of the fire in world units + + float m_flHeatLevel; // Used as a "health" for the fire. > 0 means the fire is burning + float m_flHeatAbsorb; // This much heat must be "absorbed" before it gets transferred to the flame size + float m_flDamageScale; + + float m_flMaxHeat; + float m_flLastHeatLevel; + + //NOTENOTE: Lifetime is an expression of the sum total of these amounts plus the global time when started + float m_flAttackTime; //Amount of time to scale up + + bool m_bEnabled; + bool m_bStartDisabled; + bool m_bDidActivate; + + + COutputEvent m_OnIgnited; + COutputEvent m_OnExtinguished; + + DECLARE_DATADESC(); +}; + +class CFireSphere : public IPartitionEnumerator +{ +public: + CFireSphere( CFire **pList, int listMax, bool onlyActiveFires, const Vector &origin, float radius ); + // This gets called by the enumeration methods with each element + // that passes the test. + virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ); + + int GetCount() { return m_count; } + bool AddToList( CFire *pEntity ); + +private: + Vector m_origin; + float m_radiusSqr; + CFire **m_pList; + int m_listMax; + int m_count; + bool m_onlyActiveFires; +}; + +CFireSphere::CFireSphere( CFire **pList, int listMax, bool onlyActiveFires, const Vector &origin, float radius ) +{ + m_pList = pList; + m_listMax = listMax; + m_count = 0; + m_onlyActiveFires = onlyActiveFires; + m_origin = origin; + m_radiusSqr = radius * radius; +} + +bool CFireSphere::AddToList( CFire *pFire ) +{ + if ( m_count >= m_listMax ) + return false; + m_pList[m_count] = pFire; + m_count++; + return true; +} + +IterationRetval_t CFireSphere::EnumElement( IHandleEntity *pHandleEntity ) +{ + CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); + if ( pEntity ) + { + // UNDONE: Measure which of these is faster +// CFire *pFire = dynamic_cast(pEntity); + if ( !FClassnameIs( pEntity, "env_fire" ) ) + return ITERATION_CONTINUE; + + CFire *pFire = static_cast(pEntity); + if ( pFire ) + { + if ( !m_onlyActiveFires || pFire->IsBurning() ) + { + if ( (m_origin - pFire->GetAbsOrigin()).LengthSqr() < m_radiusSqr ) + { + if ( !AddToList( pFire ) ) + return ITERATION_STOP; + } + } + } + } + + return ITERATION_CONTINUE; +} + + +int FireSystem_GetFiresInSphere( CFire **pList, int listMax, bool onlyActiveFires, const Vector &origin, float radius ) +{ + CFireSphere sphereEnum( pList, listMax, onlyActiveFires, origin, radius ); + partition->EnumerateElementsInSphere( PARTITION_ENGINE_NON_STATIC_EDICTS, origin, radius, false, &sphereEnum ); + + return sphereEnum.GetCount(); +} + + +bool FireSystem_IsValidFirePosition( const Vector &position, float testRadius ) +{ + CFire *pList[1]; + int count = FireSystem_GetFiresInSphere( pList, ARRAYSIZE(pList), true, position, testRadius ); + if ( count > 0 ) + return false; + return true; +} + + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +bool FireSystem_IsFireInWall( Vector &position, fireType_e type ) +{ + // Don't check natural fire against walls + if (type == FIRE_NATURAL) + return false; + + trace_t tr; + UTIL_TraceHull( position, position+Vector(0,0,0.1), FIRE_MINS,FIRE_MAXS,MASK_SOLID, NULL, COLLISION_GROUP_NONE, &tr ); + if (tr.fraction != 1.0 || tr.startsolid) + { + //NDebugOverlay::Box(position,FIRE_MINS,FIRE_MAXS,255,0,0,50,10); + return true; + } + //NDebugOverlay::Box(position,FIRE_MINS,FIRE_MAXS,0,255,0,50,10); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Determines whether or not a new fire may be placed at a given location +// Input : &position - where we are trying to put the new fire +// separationRadius - the maximum distance fires must be apart from one another +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool FireSystem_CanAddFire( Vector *position, float separationRadius, fireType_e type, int flags ) +{ + //See if we found a fire inside the sphere + if ( !FireSystem_IsValidFirePosition( *position, separationRadius ) ) + return false; + + // Unless our fire is floating, make sure were not too high + if (!(flags & SF_FIRE_DONT_DROP)) + { + trace_t tr; + Vector startpos = *position; + Vector endpos = *position; + + startpos[2] += 1; + endpos[2] -= FIRE_MAX_GROUND_OFFSET; + + UTIL_TraceLine( startpos, endpos, MASK_SOLID, NULL, COLLISION_GROUP_NONE, &tr ); + + //See if we're floating too high + if ( ( tr.allsolid ) || ( tr.startsolid) || ( tr.fraction == 1.0f ) ) + { + return false; + } + + //TODO: If we've hit an entity here, start it on fire + CBaseEntity *pEntity = tr.m_pEnt; + + if ( ENTINDEX( pEntity->edict() ) != 0 ) + { + return false; + } + } + + + + // Check if fire is in a wall, if so try shifting around a bit + if (FireSystem_IsFireInWall( *position, type )) + { + Vector vTestPos = *position; + vTestPos.x += 10; + if (FireSystem_IsValidFirePosition( vTestPos, separationRadius ) && !FireSystem_IsFireInWall( vTestPos, type )) + { + *position = vTestPos; + return true; + } + vTestPos.y += 10; + if (FireSystem_IsValidFirePosition( vTestPos, separationRadius ) && !FireSystem_IsFireInWall( vTestPos, type )) + { + *position = vTestPos; + return true; + } + vTestPos.y -= 20; + if (FireSystem_IsValidFirePosition( vTestPos, separationRadius ) && !FireSystem_IsFireInWall( vTestPos, type )) + { + *position = vTestPos; + return true; + } + vTestPos.x -= 20; + if (FireSystem_IsValidFirePosition( vTestPos, separationRadius ) && !FireSystem_IsFireInWall( vTestPos, type )) + { + *position = vTestPos; + return true; + } + return false; + } + + //Able to add here + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Starts a fire at a specified location +// Input : &position - position to start the fire at +// flags - any special modifiers +//----------------------------------------------------------------------------- +bool FireSystem_StartFire( const Vector &position, float fireHeight, float attack, float fuel, int flags, CBaseEntity *owner, fireType_e type ) +{ + VPROF_FIRE( "FireSystem_StartFire1" ); + + Vector testPos = position; + //Must be okay to add fire here + if ( FireSystem_CanAddFire( &testPos, 16.0f, type, flags ) == false ) + { + CFire *pFires[16]; + int fireCount = FireSystem_GetFiresInSphere( pFires, ARRAYSIZE(pFires), true, position, 16.0f ); + for ( int i = 0; i < fireCount; i++ ) + { + // add to this fire + pFires[i]->AddHeat( fireHeight, false ); + } + + return false; + } + + //Create a new fire entity + CFire *fire = (CFire *) CreateEntityByName( "env_fire" ); + + if ( fire == NULL ) + return false; + + //Spawn the fire + // Fires not placed by a designer should be cleaned up automatically (not catch fire again) + fire->AddSpawnFlags( SF_FIRE_DIE_PERMANENT ); + fire->Spawn(); + fire->Init( testPos, fireHeight, attack, fuel, flags, type ); + fire->Start(); + fire->SetOwner( owner ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Starts a fire on a specified model. +// Input : pEntity - The model entity to catch on fire. +// fireHeight - +// attack - +// fuel - +// flags - +// owner - +// type - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool FireSystem_StartFire( CBaseAnimating *pEntity, float fireHeight, float attack, float fuel, int flags, CBaseEntity *owner, fireType_e type ) +{ + VPROF_FIRE( "FireSystem_StartFire2" ); + + Vector position = pEntity->GetAbsOrigin(); + Vector testPos = position; + + // Make sure its a valid position for fire (not in a wall, etc) + if ( FireSystem_CanAddFire( &testPos, 16.0f, type, flags ) == false ) + { + // Contribute heat to all fires within 16 units of this fire. + CFire *pFires[16]; + int fireCount = FireSystem_GetFiresInSphere( pFires, ARRAYSIZE(pFires), true, position, 16.0f ); + for ( int i = 0; i < fireCount; i++ ) + { + pFires[i]->AddHeat( fireHeight, false ); + } + + return false; + } + + // Create a new fire entity + CFire *fire = (CFire *) CreateEntityByName( "env_fire" ); + if ( fire == NULL ) + { + return false; + } + + // Spawn the fire. + // Fires not placed by a designer should be cleaned up automatically (not catch fire again). + fire->AddSpawnFlags( SF_FIRE_DIE_PERMANENT ); + fire->Spawn(); + fire->Init( testPos, fireHeight, attack, fuel, flags, type ); + fire->Start(); + fire->SetOwner( owner ); + + return true; +} + + +void FireSystem_ExtinguishInRadius( const Vector &origin, float radius, float rate ) +{ + // UNDONE: pass this instead of percent + float heat = (1-rate) * fire_extscale.GetFloat(); + + CFire *pFires[32]; + int fireCount = FireSystem_GetFiresInSphere( pFires, ARRAYSIZE(pFires), false, origin, radius ); + for ( int i = 0; i < fireCount; i++ ) + { + pFires[i]->Extinguish( heat ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// radius - +// heat - +//----------------------------------------------------------------------------- +void FireSystem_AddHeatInRadius( const Vector &origin, float radius, float heat ) +{ + VPROF_FIRE( "FireSystem_AddHeatInRadius" ); + + CFire *pFires[32]; + + int fireCount = FireSystem_GetFiresInSphere( pFires, ARRAYSIZE(pFires), false, origin, radius ); + for ( int i = 0; i < fireCount; i++ ) + { + pFires[i]->AddHeat( heat ); + } +} + +//----------------------------------------------------------------------------- + +bool FireSystem_GetFireDamageDimensions( CBaseEntity *pEntity, Vector *pFireMins, Vector *pFireMaxs ) +{ + CFire *pFire = dynamic_cast(pEntity); + + if ( pFire && pFire->GetFireDimensions( pFireMins, pFireMaxs ) ) + { + *pFireMins /= FIRE_SPREAD_DAMAGE_MULTIPLIER; + *pFireMaxs /= FIRE_SPREAD_DAMAGE_MULTIPLIER; + return true; + } + pFireMins->Init(); + pFireMaxs->Init(); + return false; +} + + +//================================================== +// CFire +//================================================== +BEGIN_DATADESC( CFire ) + + DEFINE_FIELD( m_hEffect, FIELD_EHANDLE ), + DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_nFireType, FIELD_INTEGER, "firetype" ), + + DEFINE_FIELD( m_flFuel, FIELD_FLOAT ), + DEFINE_FIELD( m_flDamageTime, FIELD_TIME ), + DEFINE_FIELD( m_lastDamage, FIELD_TIME ), + DEFINE_KEYFIELD( m_flFireSize, FIELD_FLOAT, "firesize" ), + + DEFINE_KEYFIELD( m_flHeatLevel, FIELD_FLOAT, "ignitionpoint" ), + DEFINE_FIELD( m_flHeatAbsorb, FIELD_FLOAT ), + DEFINE_KEYFIELD( m_flDamageScale,FIELD_FLOAT, "damagescale" ), + + DEFINE_FIELD( m_flMaxHeat, FIELD_FLOAT ), + //DEFINE_FIELD( m_flLastHeatLevel, FIELD_FLOAT ), + + DEFINE_KEYFIELD( m_flAttackTime, FIELD_FLOAT, "fireattack" ), + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + DEFINE_KEYFIELD( m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled" ), + DEFINE_FIELD( m_bDidActivate, FIELD_BOOLEAN ), + + DEFINE_FUNCTION( BurnThink ), + DEFINE_FUNCTION( GoOutThink ), + + + + DEFINE_INPUTFUNC( FIELD_VOID, "StartFire", InputStartFire ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "Extinguish", InputExtinguish ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "ExtinguishTemporary", InputExtinguishTemporary ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + DEFINE_OUTPUT( m_OnIgnited, "OnIgnited" ), + DEFINE_OUTPUT( m_OnExtinguished, "OnExtinguished" ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( env_fire, CFire ); + +//================================================== +// CFire +//================================================== + +CFire::CFire( void ) +{ + m_flFuel = 0.0f; + m_flAttackTime = 0.0f; + m_flDamageTime = 0.0f; + m_lastDamage = 0; + m_nFireType = FIRE_NATURAL; + + //Spreading + m_flHeatAbsorb = 8.0f; + m_flHeatLevel = 0; + + // Must be in the constructor! + AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); +} + +//----------------------------------------------------------------------------- +// UpdateOnRemove +//----------------------------------------------------------------------------- +void CFire::UpdateOnRemove( void ) +{ + //Stop any looping sounds that might be playing + StopSound( "Fire.Plasma" ); + + DestroyEffect(); + + // Chain at end to mimic destructor unwind order + BaseClass::UpdateOnRemove(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFire::Precache( void ) +{ + if ( m_nFireType == FIRE_NATURAL ) + { + UTIL_PrecacheOther("_firesmoke"); + + if ( m_spawnflags & SF_FIRE_SMOKELESS ) + { + PrecacheParticleSystem( "env_fire_tiny" ); + PrecacheParticleSystem( "env_fire_small" ); + PrecacheParticleSystem( "env_fire_medium" ); + PrecacheParticleSystem( "env_fire_large" ); + } + else + { + PrecacheParticleSystem( "env_fire_tiny_smoke" ); + PrecacheParticleSystem( "env_fire_small_smoke" ); + PrecacheParticleSystem( "env_fire_medium_smoke" ); + PrecacheParticleSystem( "env_fire_large_smoke" ); + } + } + + if ( m_nFireType == FIRE_PLASMA ) + { + UTIL_PrecacheOther("_plasma"); + } + + PrecacheScriptSound( "Fire.Plasma" ); +} + +//------------------------------------------------------------------------------ +// Purpose : Input handler for starting the fire. +//------------------------------------------------------------------------------ +void CFire::InputStartFire( inputdata_t &inputdata ) +{ + if ( !m_bEnabled ) + return; + + StartFire(); +} + +void CFire::InputEnable( inputdata_t &inputdata ) +{ + m_bEnabled = true; +} + +void CFire::InputDisable( inputdata_t &inputdata ) +{ + Disable(); +} + +void CFire::Disable() +{ + m_bEnabled = false; + if ( IsBurning() ) + { + GoOut(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CFire::InputExtinguish( inputdata_t &inputdata ) +{ + m_spawnflags &= ~SF_FIRE_INFINITE; + GoOutInSeconds( inputdata.value.Float() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CFire::InputExtinguishTemporary( inputdata_t &inputdata ) +{ + GoOutInSeconds( inputdata.value.Float() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Starts burning. +//----------------------------------------------------------------------------- +void CFire::StartFire( void ) +{ + if ( m_hEffect != NULL ) + return; + + // Trace down and start a fire there. Nothing fancy yet. + Vector vFirePos; + trace_t tr; + if ( m_spawnflags & SF_FIRE_DONT_DROP ) + { + vFirePos = GetAbsOrigin(); + } + else + { + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 1024 ), MASK_FIRE_SOLID, this, COLLISION_GROUP_NONE, &tr ); + vFirePos = tr.endpos; + } + + int spawnflags = m_spawnflags; + m_spawnflags |= SF_FIRE_START_ON; + Init( vFirePos, m_flFireSize, m_flAttackTime, GetHealth(), m_spawnflags, (fireType_e) m_nFireType ); + Start(); + m_spawnflags = spawnflags; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFire::Spawn( void ) +{ + BaseClass::Spawn(); + + Precache(); + + m_takedamage = DAMAGE_NO; + + SetSolid( SOLID_NONE ); + AddEffects( EF_NODRAW ); + SetToOutSize(); + + // set up the ignition point + m_flHeatAbsorb = m_flHeatLevel * 0.05; + m_flHeatLevel = 0; + Init( GetAbsOrigin(), m_flFireSize, m_flAttackTime, m_flFuel, m_spawnflags, m_nFireType ); + + if( m_bStartDisabled ) + { + Disable(); + } + else + { + m_bEnabled = true; + } +} + +int CFire::UpdateTransmitState() +{ + // Don't want to be FL_EDICT_DONTSEND because our fire entity may make us transmit. + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFire::Activate( void ) +{ + BaseClass::Activate(); + + //See if we should start active + if ( !m_bDidActivate && ( m_spawnflags & SF_FIRE_START_ON ) ) + { + m_flHeatLevel = m_flMaxHeat; + + StartFire(); + } + + m_bDidActivate = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFire::SpawnEffect( fireType_e type, float scale ) +{ + CBaseFire *pEffect = NULL; + switch ( type ) + { + default: + case FIRE_NATURAL: + { + CFireSmoke *fireSmoke = (CFireSmoke *) CreateEntityByName( "_firesmoke" ); + fireSmoke->EnableSmoke( ( m_spawnflags & SF_FIRE_SMOKELESS )==false ); + fireSmoke->EnableGlow( ( m_spawnflags & SF_FIRE_NO_GLOW )==false ); + fireSmoke->EnableVisibleFromAbove( ( m_spawnflags & SF_FIRE_VISIBLE_FROM_ABOVE )!=false ); + + pEffect = fireSmoke; + m_nFireType = FIRE_NATURAL; + m_takedamage = DAMAGE_YES; + } + break; + + case FIRE_PLASMA: + { + CPlasma *plasma = (CPlasma *) CreateEntityByName( "_plasma" ); + plasma->EnableSmoke( true ); + + pEffect = plasma; + m_nFireType = FIRE_PLASMA; + m_takedamage = DAMAGE_YES; + + // Start burn sound + EmitSound( "Fire.Plasma" ); + } + break; + } + + UTIL_SetOrigin( pEffect, GetAbsOrigin() ); + pEffect->Spawn(); + pEffect->SetParent( this ); + pEffect->Scale( m_flFireSize, m_flFireSize, 0 ); + //Start it going + pEffect->Enable( ( m_spawnflags & SF_FIRE_START_ON ) ); + m_hEffect = pEffect; +} + +//----------------------------------------------------------------------------- +// Purpose: Spawn and initialize the fire +// Input : &position - where the fire resides +// lifetime - +//----------------------------------------------------------------------------- +void CFire::Init( const Vector &position, float scale, float attackTime, float fuel, int flags, int fireType ) +{ + m_flAttackTime = attackTime; + + m_spawnflags = flags; + m_nFireType = fireType; + + if ( flags & SF_FIRE_INFINITE ) + { + fuel = 0; + } + m_flFuel = fuel; + if ( m_flFuel ) + { + m_spawnflags |= SF_FIRE_DIE_PERMANENT; + } + + Vector localOrigin = position; + if ( GetMoveParent() ) + { + EntityMatrix parentMatrix; + parentMatrix.InitFromEntity( GetMoveParent() ); + localOrigin = parentMatrix.WorldToLocal( position ); + } + UTIL_SetOrigin( this, localOrigin ); + + SetSolid( SOLID_NONE ); + m_flFireSize = scale; + m_flMaxHeat = FIRE_MAX_HEAT_LEVEL * FIRE_SCALE_FROM_SIZE(scale); + //See if we should start on + if ( m_spawnflags & SF_FIRE_START_FULL ) + { + m_flHeatLevel = m_flMaxHeat; + } + m_flLastHeatLevel = 0; + +} + +void CFire::Start() +{ + float boxWidth = (m_flFireSize * (FIRE_WIDTH/FIRE_HEIGHT))*0.5f; + UTIL_SetSize(this, Vector(-boxWidth,-boxWidth,0),Vector(boxWidth,boxWidth,m_flFireSize)); + + //Spawn the client-side effect + SpawnEffect( (fireType_e)m_nFireType, FIRE_SCALE_FROM_SIZE(m_flFireSize) ); + m_OnIgnited.FireOutput( this, this ); + SetThink( &CFire::BurnThink ); + m_flDamageTime = 0; + // think right now + BurnThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: Determines whether or not the fire is still active +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CFire::IsBurning( void ) const +{ + if ( m_flHeatLevel > 0 ) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the damage box of the fire +//----------------------------------------------------------------------------- +bool CFire::GetFireDimensions( Vector *pFireMins, Vector *pFireMaxs ) +{ + if ( m_flHeatLevel <= 0 ) + { + pFireMins->Init(); + pFireMaxs->Init(); + return false; + } + + float scale = m_flHeatLevel / m_flMaxHeat; + float damageRadius = scale * m_flFireSize * FIRE_WIDTH / FIRE_HEIGHT * 0.5; + + damageRadius *= FIRE_SPREAD_DAMAGE_MULTIPLIER; //FIXME: Trying slightly larger radius for burning + + if ( damageRadius < 16 ) + { + damageRadius = 16; + } + + pFireMins->Init(-damageRadius,-damageRadius,0); + pFireMaxs->Init(damageRadius,damageRadius,m_flFireSize*scale); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Update the fire and its children +//----------------------------------------------------------------------------- +void CFire::Update( float simTime ) +{ + VPROF_FIRE( "CFire::Update" ); + + if ( m_flFuel != 0 ) + { + m_flFuel -= simTime; + if ( m_flFuel <= 0 ) + { + GoOutInSeconds( 1 ); + return; + } + } + + float strength = m_flHeatLevel / FIRE_MAX_HEAT_LEVEL; + if ( m_flHeatLevel != m_flLastHeatLevel ) + { + m_flLastHeatLevel = m_flHeatLevel; + // Make the effect the appropriate size given the heat level + m_hEffect->Scale( strength, 0.5f ); + } + // add heat to myself (grow) + float addedHeat = (m_flAttackTime > 0) ? m_flMaxHeat / m_flAttackTime : m_flMaxHeat; + addedHeat *= simTime * fire_growthrate.GetFloat(); + AddHeat( addedHeat, true ); + + // add heat to nearby fires + float outputHeat = strength * m_flHeatLevel; + + Vector fireMins; + Vector fireMaxs; + Vector fireEntityDamageMins; + Vector fireEntityDamageMaxs; + + GetFireDimensions( &fireMins, &fireMaxs ); + + if ( FIRE_SPREAD_DAMAGE_MULTIPLIER != 1.0 ) // if set to 1.0, optimizer will remove this code + { + fireEntityDamageMins = fireMins / FIRE_SPREAD_DAMAGE_MULTIPLIER; + fireEntityDamageMaxs = fireMaxs / FIRE_SPREAD_DAMAGE_MULTIPLIER; + } + + //NDebugOverlay::Box( GetAbsOrigin(), fireMins, fireMaxs, 255, 255, 255, 0, fire_dmginterval.GetFloat() ); + fireMins += GetAbsOrigin(); + fireMaxs += GetAbsOrigin(); + + if ( FIRE_SPREAD_DAMAGE_MULTIPLIER != 1.0 ) + { + fireEntityDamageMins += GetAbsOrigin(); + fireEntityDamageMaxs += GetAbsOrigin(); + } + + CBaseEntity *pNearby[256]; + CFire *pFires[16]; + int nearbyCount = UTIL_EntitiesInBox( pNearby, ARRAYSIZE(pNearby), fireMins, fireMaxs, 0 ); + int fireCount = 0; + int i; + + // is it time to do damage? + bool damage = false; + int outputDamage = 0; + if ( m_flDamageTime <= gpGlobals->curtime ) + { + m_flDamageTime = gpGlobals->curtime + fire_dmginterval.GetFloat(); + outputDamage = (fire_dmgbase.GetFloat() + outputHeat * fire_dmgscale.GetFloat() * m_flDamageScale) * fire_dmginterval.GetFloat(); + if ( outputDamage ) + { + damage = true; + } + } + int damageFlags = (m_nFireType == FIRE_NATURAL) ? DMG_BURN : DMG_PLASMA; + for ( i = 0; i < nearbyCount; i++ ) + { + CBaseEntity *pOther = pNearby[i]; + + if ( pOther == this ) + { + continue; + } + else if ( FClassnameIs( pOther, "env_fire" ) ) + { + if ( fireCount < ARRAYSIZE(pFires) ) + { + pFires[fireCount] = (CFire *)pOther; + fireCount++; + } + continue; + } + else if ( pOther->m_takedamage == DAMAGE_NO ) + { + pNearby[i] = NULL; + } + else if ( damage ) + { + bool bDoDamage; + + if ( FIRE_SPREAD_DAMAGE_MULTIPLIER != 1.0 && !pOther->IsPlayer() ) // if set to 1.0, optimizer will remove this code + { + Vector otherMins, otherMaxs; + pOther->CollisionProp()->WorldSpaceAABB( &otherMins, &otherMaxs ); + bDoDamage = IsBoxIntersectingBox( otherMins, otherMaxs, + fireEntityDamageMins, fireEntityDamageMaxs ); + + } + else + bDoDamage = true; + + if ( bDoDamage ) + { + // Make sure can actually see entity (don't damage through walls) + trace_t tr; + UTIL_TraceLine( this->WorldSpaceCenter(), pOther->WorldSpaceCenter(), MASK_FIRE_SOLID, pOther, COLLISION_GROUP_NONE, &tr ); + + if (tr.fraction == 1.0 && !tr.startsolid) + { + pOther->TakeDamage( CTakeDamageInfo( this, this, outputDamage, damageFlags ) ); + } + } + } + } + + outputHeat *= fire_heatscale.GetFloat() * simTime; + + if ( fireCount > 0 ) + { + outputHeat /= fireCount; + for ( i = 0; i < fireCount; i++ ) + { + pFires[i]->AddHeat( outputHeat, false ); + } + } +} + +// Destroy any effect I have +void CFire::DestroyEffect() +{ + CBaseFire *pEffect = m_hEffect; + if ( pEffect != NULL ) + { + //disable the graphics and remove the entity + pEffect->Enable( false ); + UTIL_Remove( pEffect ); + } +} +//----------------------------------------------------------------------------- +// Purpose: Think +//----------------------------------------------------------------------------- +void CFire::BurnThink( void ) +{ + SetNextThink( gpGlobals->curtime + FIRE_THINK_INTERVAL ); + + Update( FIRE_THINK_INTERVAL ); +} + +void CFire::GoOutThink() +{ + GoOut(); +} + +void CFire::GoOutInSeconds( float seconds ) +{ + Scale( 0.0f, seconds ); + + SetThink( &CFire::GoOutThink ); + SetNextThink( gpGlobals->curtime + seconds ); +} + +//------------------------------------------------------------------------------ +// Purpose : Blasts of significant size blow out fires that take damage +// Input : +// Output : +//------------------------------------------------------------------------------ +int CFire::OnTakeDamage( const CTakeDamageInfo &info ) +{ + return 0; +} + +void CFire::AddHeat( float heat, bool selfHeat ) +{ + if ( m_bEnabled ) + { + if ( !selfHeat ) + { + if ( IsBurning() ) + { + // scale back the incoming heat from surrounding fires + // if I've already ignited + heat *= fire_incomingheatscale.GetFloat(); + } + } + m_lastDamage = gpGlobals->curtime + 0.5; + bool start = m_flHeatLevel <= 0 ? true : false; + if ( m_flHeatAbsorb > 0 ) + { + float absorbDamage = heat * fire_absorbrate.GetFloat(); + if ( absorbDamage > m_flHeatAbsorb ) + { + heat -= m_flHeatAbsorb / fire_absorbrate.GetFloat(); + m_flHeatAbsorb = 0; + } + else + { + m_flHeatAbsorb -= absorbDamage; + heat = 0; + } + } + + m_flHeatLevel += heat; + if ( start && m_flHeatLevel > 0 && m_hEffect == NULL ) + { + StartFire(); + } + if ( m_flHeatLevel > m_flMaxHeat ) + m_flHeatLevel = m_flMaxHeat; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : end - +// time - +//----------------------------------------------------------------------------- +void CFire::Scale( float end, float time ) +{ + CBaseFire *pEffect = m_hEffect; + if ( pEffect ) + { + pEffect->Scale( end, time ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : time - +//----------------------------------------------------------------------------- +void CFire::Extinguish( float heat ) +{ + if ( !m_bEnabled ) + return; + + m_lastDamage = gpGlobals->curtime + 0.5; + bool out = m_flHeatLevel > 0 ? true : false; + + m_flHeatLevel -= heat; + m_flHeatAbsorb += fire_extabsorb.GetFloat() * heat; + if ( m_flHeatAbsorb > fire_maxabsorb.GetFloat() ) + { + m_flHeatAbsorb = fire_maxabsorb.GetFloat(); + } + + // drift toward the average attack time after being sprayed + // some fires are heavily scripted so their attack looks weird + // once interacted with. Basically, this blends out the scripting + // as the fire is sprayed with the extinguisher. + float averageAttackTime = m_flMaxHeat * (FIRE_NORMAL_ATTACK_TIME/FIRE_MAX_HEAT_LEVEL); + m_flAttackTime = Approach( averageAttackTime, m_flAttackTime, 2 * gpGlobals->frametime ); + + if ( m_flHeatLevel <= 0 ) + { + m_flHeatLevel = 0; + if ( out ) + { + GoOut(); + } + } +} + +bool CFire::GoOut() +{ + //Signal death + m_OnExtinguished.FireOutput( this, this ); + + DestroyEffect(); + m_flHeatLevel -= 20; + if ( m_flHeatLevel > 0 ) + m_flHeatLevel = 0; + + m_flLastHeatLevel = m_flHeatLevel; + SetThink(NULL); + SetNextThink( TICK_NEVER_THINK ); + if ( m_spawnflags & SF_FIRE_DIE_PERMANENT ) + { + UTIL_Remove( this ); + return true; + } + SetToOutSize(); + + return false; +} + +//================================================== +// CEnvFireSource is a source of heat that the player +// cannot put out +//================================================== + +#define FIRESOURCE_THINK_TIME 0.25 // seconds to + +#define SF_FIRESOURCE_START_ON 0x0001 + +class CEnvFireSource : public CBaseEntity +{ + DECLARE_CLASS( CEnvFireSource, CBaseEntity ); +public: + void Spawn(); + void Think(); + void TurnOn(); + void TurnOff(); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + + DECLARE_DATADESC(); + +private: + bool m_bEnabled; + float m_radius; + float m_damage; +}; + +BEGIN_DATADESC( CEnvFireSource ) + + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + DEFINE_KEYFIELD( m_radius, FIELD_FLOAT, "fireradius" ), + DEFINE_KEYFIELD( m_damage,FIELD_FLOAT, "firedamage" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( env_firesource, CEnvFireSource ); + +void CEnvFireSource::Spawn() +{ + if ( m_spawnflags & SF_FIRESOURCE_START_ON ) + { + TurnOn(); + } + else + { + TurnOff(); + } +} + +void CEnvFireSource::Think() +{ + if ( !m_bEnabled ) + return; + SetNextThink( gpGlobals->curtime + FIRESOURCE_THINK_TIME ); + + CFire *pFires[128]; + int fireCount = FireSystem_GetFiresInSphere( pFires, ARRAYSIZE(pFires), false, GetAbsOrigin(), m_radius ); + + for ( int i = 0; i < fireCount; i++ ) + { + pFires[i]->AddHeat( m_damage * FIRESOURCE_THINK_TIME ); + } +} + +void CEnvFireSource::TurnOn() +{ + if ( m_bEnabled ) + return; + + m_bEnabled = true; + SetNextThink( gpGlobals->curtime ); +} + +void CEnvFireSource::TurnOff() +{ + if ( !m_bEnabled ) + return; + + m_bEnabled = false; + SetNextThink( TICK_NEVER_THINK ); +} +void CEnvFireSource::InputEnable( inputdata_t &inputdata ) +{ + TurnOn(); +} +void CEnvFireSource::InputDisable( inputdata_t &inputdata ) +{ + TurnOff(); +} + +//================================================== +// CEnvFireSensor detects changes in heat +//================================================== +#define SF_FIRESENSOR_START_ON 1 + +class CEnvFireSensor : public CBaseEntity +{ + DECLARE_CLASS( CEnvFireSensor, CBaseEntity ); +public: + void Spawn(); + void Think(); + void TurnOn(); + void TurnOff(); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + + DECLARE_DATADESC(); + +private: + bool m_bEnabled; + bool m_bHeatAtLevel; + float m_radius; + float m_targetLevel; + float m_targetTime; + float m_levelTime; + + COutputEvent m_OnHeatLevelStart; + COutputEvent m_OnHeatLevelEnd; +}; + +BEGIN_DATADESC( CEnvFireSensor ) + + DEFINE_KEYFIELD( m_radius, FIELD_FLOAT, "fireradius" ), + DEFINE_KEYFIELD( m_targetLevel, FIELD_FLOAT, "heatlevel" ), + DEFINE_KEYFIELD( m_targetTime, FIELD_FLOAT, "heattime" ), + + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bHeatAtLevel, FIELD_BOOLEAN ), + DEFINE_FIELD( m_levelTime, FIELD_FLOAT ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + DEFINE_OUTPUT( m_OnHeatLevelStart, "OnHeatLevelStart"), + DEFINE_OUTPUT( m_OnHeatLevelEnd, "OnHeatLevelEnd"), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( env_firesensor, CEnvFireSensor ); + +void CEnvFireSensor::Spawn() +{ + if ( m_spawnflags & SF_FIRESENSOR_START_ON ) + { + TurnOn(); + } + else + { + TurnOff(); + } +} + +void CEnvFireSensor::Think() +{ + if ( !m_bEnabled ) + return; + + float time = m_targetTime * 0.25; + if ( time < 0.1 ) + { + time = 0.1; + } + SetNextThink( gpGlobals->curtime + time ); + + float heat = 0; + CFire *pFires[128]; + int fireCount = FireSystem_GetFiresInSphere( pFires, ARRAYSIZE(pFires), true, GetAbsOrigin(), m_radius ); + for ( int i = 0; i < fireCount; i++ ) + { + heat += pFires[i]->GetHeatLevel(); + } + + if ( heat >= m_targetLevel ) + { + m_levelTime += time; + if ( m_levelTime >= m_targetTime ) + { + if ( !m_bHeatAtLevel ) + { + m_bHeatAtLevel = true; + m_OnHeatLevelStart.FireOutput( this, this ); + } + } + } + else + { + m_levelTime = 0; + if ( m_bHeatAtLevel ) + { + m_bHeatAtLevel = false; + m_OnHeatLevelEnd.FireOutput( this, this ); + } + } +} + +void CEnvFireSensor::TurnOn() +{ + if ( m_bEnabled ) + return; + + m_bEnabled = true; + SetNextThink( gpGlobals->curtime ); + m_bHeatAtLevel = false; + m_levelTime = 0; +} + +void CEnvFireSensor::TurnOff() +{ + if ( !m_bEnabled ) + return; + + m_bEnabled = false; + SetNextThink( TICK_NEVER_THINK ); + if ( m_bHeatAtLevel ) + { + m_bHeatAtLevel = false; + m_OnHeatLevelEnd.FireOutput( this, this ); + } + +} +void CEnvFireSensor::InputEnable( inputdata_t &inputdata ) +{ + TurnOn(); +} +void CEnvFireSensor::InputDisable( inputdata_t &inputdata ) +{ + TurnOff(); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CFire::DrawDebugTextOverlays( void ) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + + // print flame size + Q_snprintf(tempstr,sizeof(tempstr)," size: %f", m_flFireSize); + EntityText(text_offset,tempstr,0); + text_offset++; + } + return text_offset; +} -- cgit v1.2.3