//========= Copyright 1996-2005, 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" #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 ); 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<CBaseFire> 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<CFire *>(pEntity); if ( !FClassnameIs( pEntity, "env_fire" ) ) return ITERATION_CONTINUE; CFire *pFire = static_cast<CFire *>(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<CFire *>(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_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_PVSCHECK ); } //----------------------------------------------------------------------------- // 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 ); //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(); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 5821 | Knut Wikstrom |
Added Valve Source code. This is NOT to be commited to other than new code from Valve. |