// ClientInferno.cpp // Render client-side Inferno effects // Author: Michael Booth, February 2005 // Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved #include "cbase.h" #include "igamesystem.h" #include "hud_macros.h" #include "view.h" #include "enginesprite.h" #include "precache_register.h" #include "iefx.h" #include "dlight.h" #include "tier0/vprof.h" #include "debugoverlay_shared.h" #include "basecsgrenade_projectile.h" #include "clientinferno.h" // NOTE: This has to be the last file included! #include "tier0/memdbgon.h" PRECACHE_REGISTER_BEGIN( GLOBAL, InfernoMaterials ) PRECACHE( MATERIAL, "sprites/white" ) PRECACHE_REGISTER_END() ConVar InfernoDlightSpacing( "inferno_dlight_spacing", "200", FCVAR_CHEAT, "Inferno dlights are at least this far apart" ); ConVar InfernoDlights( "inferno_dlights", "30", 0, "Min FPS at which molotov dlights will be created" ); //ConVar InfernoParticles( "inferno_particles", "molotov_groundfire", FCVAR_REPLICATED | FCVAR_CHEAT ); ConVar InfernoFire( "inferno_fire", "2" ); enum FireMaskType { OLD_FIRE_MASK = 1, NEW_FIRE_MASK = 2 }; IMPLEMENT_CLIENTCLASS_DT( C_Inferno, DT_Inferno, CInferno ) RecvPropArray3( RECVINFO_ARRAY( m_fireXDelta ), RecvPropInt( RECVINFO(m_fireXDelta[0] ) ) ), RecvPropArray3( RECVINFO_ARRAY( m_fireYDelta ), RecvPropInt( RECVINFO(m_fireYDelta[0] ) ) ), RecvPropArray3( RECVINFO_ARRAY( m_fireZDelta ), RecvPropInt( RECVINFO(m_fireZDelta[0] ) ) ), RecvPropArray3( RECVINFO_ARRAY( m_bFireIsBurning ), RecvPropBool( RECVINFO(m_bFireIsBurning[0] ) ) ), //RecvPropArray3( RECVINFO_ARRAY( m_BurnNormal ), RecvPropVector( RECVINFO(m_BurnNormal[0] ) ) ), RecvPropInt( RECVINFO( m_fireCount ) ), END_RECV_TABLE() //----------------------------------------------------------------------------------------------- C_Inferno::C_Inferno() { m_maxFireHalfWidth = 30.0f; m_maxFireHeight = 80.0f; m_burnParticleEffect = NULL; } //----------------------------------------------------------------------------------------------- C_Inferno::~C_Inferno() { if ( m_burnParticleEffect.IsValid() ) { m_burnParticleEffect->StopEmission(); } } //----------------------------------------------------------------------------------------------- void C_Inferno::Spawn( void ) { BaseClass::Spawn(); m_fireCount = 0; m_lastFireCount = 0; m_drawableCount = 0; m_burnParticleEffect = NULL; m_minBounds = Vector( 0, 0, 0 ); m_maxBounds = Vector( 0, 0, 0 ); SetNextClientThink( CLIENT_THINK_ALWAYS ); } //----------------------------------------------------------------------------------------------- /** * Monitor changes and recompute render bounds */ void C_Inferno::ClientThink() { VPROF_BUDGET( "C_Inferno::ClientThink", "Magic" ); bool bIsAttachedToMovingObject = (GetMoveParent() != NULL) ? true : false; if (true || m_lastFireCount != m_fireCount || bIsAttachedToMovingObject ) { SynchronizeDrawables(); m_lastFireCount = m_fireCount; } bool bDidRecomputeBounds = false; // update Drawables for( int i=0; im_state ) { case STARTING: { float growRate = draw->m_maxSize/2.0f; draw->m_size = growRate * (gpGlobals->realtime - draw->m_stateTimestamp); if (draw->m_size > draw->m_maxSize) { draw->m_size = draw->m_maxSize; draw->SetState( BURNING ); } break; } case GOING_OUT: { float dieRate = draw->m_maxSize/2.0f; draw->m_size = draw->m_maxSize - dieRate * (gpGlobals->realtime - draw->m_stateTimestamp); if (draw->m_size <= 0.0f) { draw->SetState( FIRE_OUT ); // render bounds changed RecomputeBounds(); bDidRecomputeBounds = true; if ( GetRenderHandle() != INVALID_CLIENT_RENDER_HANDLE ) { ClientLeafSystem()->RenderableChanged( GetRenderHandle() ); } } break; } } } if( bIsAttachedToMovingObject && !bDidRecomputeBounds ) { RecomputeBounds(); } UpdateParticles(); } //-------------------------------------------------------------------------------------------------------- void C_Inferno::OnNewParticleEffect( const char *pszParticleName, CNewParticleEffect *pNewParticleEffect ) { if ( FStrEq( pszParticleName, GetParticleEffectName() ) ) { m_burnParticleEffect = pNewParticleEffect; } } //-------------------------------------------------------------------------------------------------------- void C_Inferno::OnParticleEffectDeleted( CNewParticleEffect *pParticleEffect ) { if ( m_burnParticleEffect == pParticleEffect ) { m_burnParticleEffect = NULL; } } //-------------------------------------------------------------------------------------------------------- void C_Inferno::UpdateParticles( void ) { if ( m_drawableCount > 0 && (InfernoFire.GetInt() & NEW_FIRE_MASK) != 0 ) { if ( !m_burnParticleEffect.IsValid() ) { MDLCACHE_CRITICAL_SECTION(); m_burnParticleEffect = ParticleProp()->Create( GetParticleEffectName(), PATTACH_ABSORIGIN_FOLLOW ); /* DevMsg( "inferno @ %f %f %f / %f %f %f\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, GetAbsAngles()[PITCH], GetAbsAngles()[YAW], GetAbsAngles()[ROLL] ); NDebugOverlay::Cross3D( GetAbsOrigin(), 5, 255, 0, 0, false, 30.0f ); NDebugOverlay::Cross3D( GetAbsOrigin(), 5, 128, 0, 0, true, 30.0f ); */ } else { for( int i=0; im_state >= FIRE_OUT ) { Vector vecCenter = draw->m_pos; //VectorLerp( m_minBounds, m_maxBounds, 0.5f, vecCenter ); draw->m_pos = vecCenter + Vector( 0, 0, -9999 ); draw->m_size = 0; // this sucks if ( i != 0 ) m_burnParticleEffect->SetControlPoint( i, vec3_invalid ); //engine->Con_NPrintf( i + 10, "0 0 0" ); //NDebugOverlay::Cross3D( draw->m_pos, 5, 0, 0, 255, false, 5.1f ); } else { //NDebugOverlay::Cross3D( draw->m_pos, 5, 0, 0, 255, false, 0.1f ); //NDebugOverlay::Line( GetAbsOrigin(), draw->m_pos, 0, 255, 0, true, 0.1f ); m_burnParticleEffect->SetControlPointEntity( i, NULL ); m_burnParticleEffect->SetControlPoint( i, draw->m_pos ); // FIXME - Set orientation to burn normal once we have per particle normals. //m_burnParticleEffect->SetControlPointOrientation( i, Orientation ); if ( i % 2 == 0 ) { //Elight, for perf reasons only for every other fire dlight_t *el = effects->CL_AllocElight( draw->m_dlightIndex ); el->origin = draw->m_pos; el->origin[2] += 64; el->color.r = 254; el->color.g = 100; el->color.b = 10; el->radius = random->RandomFloat(60, 120); el->die = gpGlobals->curtime + random->RandomFloat( 0.01, 0.025 ); el->color.exponent = 5; } } } SetNextClientThink( 0.1f ); m_burnParticleEffect->SetNeedsBBoxUpdate( true ); Vector vecCenter = GetRenderOrigin(); m_burnParticleEffect->SetSortOrigin( vecCenter ); //NDebugOverlay::Cross3D( vecCenter, 5, 255, 0, 255, false, 0.5f ); Vector vecMin, vecMax; GetRenderBounds( vecMin, vecMax ); //NDebugOverlay::Box( vecCenter, vecMin, vecMax, 255, 0, 255, 255, 0.5 ); } } else { if ( m_burnParticleEffect.IsValid() ) { m_burnParticleEffect->StopEmission(); // m_burnParticleEffect->SetRemoveFlag(); } } } //----------------------------------------------------------------------------------------------- void C_Inferno::GetRenderBounds( Vector& mins, Vector& maxs ) { if (m_drawableCount) { mins = m_minBounds - GetRenderOrigin(); maxs = m_maxBounds - GetRenderOrigin(); } else { mins = Vector( 0, 0, 0 ); maxs = Vector( 0, 0, 0 ); } } //----------------------------------------------------------------------------------------------- /** * Returns the bounds as an AABB in worldspace */ void C_Inferno::GetRenderBoundsWorldspace( Vector& mins, Vector& maxs ) { if (m_drawableCount) { mins = m_minBounds; maxs = m_maxBounds; } else { mins = Vector( 0, 0, 0 ); maxs = Vector( 0, 0, 0 ); } } //----------------------------------------------------------------------------------------------- void C_Inferno::RecomputeBounds( void ) { m_minBounds = GetAbsOrigin() + Vector( 64.9f, 64.9f, 64.9f ); m_maxBounds = GetAbsOrigin() + Vector( -64.9f, -64.9f, -64.9f ); for( int i=0; im_state == FIRE_OUT) continue; if (draw->m_pos.x - m_maxFireHalfWidth < m_minBounds.x) m_minBounds.x = draw->m_pos.x - m_maxFireHalfWidth; if (draw->m_pos.x + m_maxFireHalfWidth > m_maxBounds.x) m_maxBounds.x = draw->m_pos.x + m_maxFireHalfWidth; if (draw->m_pos.y - m_maxFireHalfWidth < m_minBounds.y) m_minBounds.y = draw->m_pos.y - m_maxFireHalfWidth; if (draw->m_pos.y + m_maxFireHalfWidth > m_maxBounds.y) m_maxBounds.y = draw->m_pos.y + m_maxFireHalfWidth; if (draw->m_pos.z < m_minBounds.z) m_minBounds.z = draw->m_pos.z; if (draw->m_pos.z + m_maxFireHeight > m_maxBounds.z) m_maxBounds.z = draw->m_pos.z + m_maxFireHeight; } } //----------------------------------------------------------------------------------------------- /** * Given a position, return the fire there */ C_Inferno::Drawable *C_Inferno::GetDrawable( const Vector &pos ) { for( int i=0; im_state != FIRE_OUT ) { info->m_state = FIRE_OUT; // render bounds changed RecomputeBounds(); if ( GetRenderHandle() != INVALID_CLIENT_RENDER_HANDLE ) { ClientLeafSystem()->RenderableChanged( GetRenderHandle() ); } } continue; } else if ( info ) { // existing fire continues to burn if (info->m_state == UNKNOWN) { info->m_state = BURNING; } } else if (m_drawableCount < MAX_INFERNO_FIRES) { // new fire info = &m_drawable[ m_drawableCount ]; info->SetState( STARTING ); info->m_pos = firePos; info->m_normal = fireNormal; info->m_frame = 0; info->m_framerate = random->RandomFloat( 0.04f, 0.06f ); info->m_mirror = (random->RandomInt( 0, 100 ) < 50); info->m_size = 0.0f; info->m_maxSize = random->RandomFloat( 70.0f, 90.0f ); bool closeDlight = false; for ( int i=0; i 0 ) { if ( m_drawable[i].m_pos.DistToSqr( firePos ) < InfernoDlightSpacing.GetFloat() * InfernoDlightSpacing.GetFloat() ) { closeDlight = true; break; } } } } if ( closeDlight ) { info->m_dlightIndex = 0; } else { info->m_dlightIndex = LIGHT_INDEX_TE_DYNAMIC + index + m_drawableCount; } // render bounds changed RecomputeBounds(); if ( GetRenderHandle() != INVALID_CLIENT_RENDER_HANDLE ) { ClientLeafSystem()->RenderableChanged( GetRenderHandle() ); } ++m_drawableCount; } } // any fires still in the UNKNOWN state are now GOING_OUT for( i=0; iGetModel( modelinfo->GetModelIndex( "sprites/fire1.vmt" ) ); if (model == NULL) return 0; sprite = (CEngineSprite *)modelinfo->GetModelExtraData( model ); if (sprite == NULL) return 0; material = sprite->GetMaterial( kRenderTransAdd ); if (material == NULL) return 0; CMatRenderContextPtr pRenderContext( materials ); pRenderContext->Bind( material ); IMesh *pMesh = pRenderContext->GetDynamicMesh(); if ( pMesh ) { // draw the actual flames for( int i=0; irealtime/m_drawable[i].m_framerate) % sprite->GetNumFrames(); sprite->SetFrame( kRenderTransAdd, frame ); DrawFire( &m_drawable[i], pMesh ); } } return 0; } //----------------------------------------------------------------------------------------------- /** * Render an individual fire sprite */ void C_Inferno::DrawFire( C_Inferno::Drawable *fire, IMesh *mesh ) { const float halfWidth = fire->m_size/3.0f; //unsigned char color[4] = { 255,255,255,255 }; unsigned char color[4] = { 150,150,150,255 }; CMeshBuilder meshBuilder; meshBuilder.Begin( mesh, MATERIAL_QUADS, 1 ); const Vector &right = (fire->m_mirror) ? -CurrentViewRight() : CurrentViewRight(); Vector up( 0.0f, 0.0f, 1.0f ); Vector top = fire->m_pos + up * fire->m_size; const Vector &bottom = fire->m_pos; Vector pos = top + right * halfWidth; meshBuilder.Position3fv( pos.Base() ); meshBuilder.Color4ubv( color ); meshBuilder.TexCoord2f( 0, 1, 0 ); meshBuilder.AdvanceVertex(); pos = bottom + right * halfWidth; meshBuilder.Position3fv( pos.Base() ); meshBuilder.Color4ubv( color ); meshBuilder.TexCoord2f( 0, 1, 1 ); meshBuilder.AdvanceVertex(); pos = bottom - right * halfWidth; meshBuilder.Position3fv( pos.Base() ); meshBuilder.Color4ubv( color ); meshBuilder.TexCoord2f( 0, 0, 1 ); meshBuilder.AdvanceVertex(); pos = top - right * halfWidth; meshBuilder.Position3fv( pos.Base() ); meshBuilder.Color4ubv( color ); meshBuilder.TexCoord2f( 0, 0, 0 ); meshBuilder.AdvanceVertex(); meshBuilder.End(); mesh->Draw(); if ( fire->m_dlightIndex > 0 && InfernoDlights.GetFloat() >= 1 ) { static float lastRealTime = -1.0f; float realFrameTime = gpGlobals->realtime - lastRealTime; if ( realFrameTime > 2 ) { realFrameTime = -1.0f; } if ( realFrameTime > 0 ) { static float AverageFPS = -1; static int high = -1; static int low = -1; int nFps = -1; const float NewWeight = 0.1f; float NewFrame = 1.0f / realFrameTime; if ( AverageFPS < 0.0f ) { AverageFPS = NewFrame; high = (int)AverageFPS; low = (int)AverageFPS; } else { AverageFPS *= ( 1.0f - NewWeight ) ; AverageFPS += ( ( NewFrame ) * NewWeight ); } int NewFrameInt = (int)NewFrame; if( NewFrameInt < low ) low = NewFrameInt; if( NewFrameInt > high ) high = NewFrameInt; nFps = static_cast( AverageFPS ); if ( nFps < InfernoDlights.GetFloat() ) { fire->m_dlightIndex = 0; lastRealTime = gpGlobals->realtime; return; } } lastRealTime = gpGlobals->realtime; // These are the dlight params from the Ep1 fire glows, with a slightly larger flicker // (radius delta is larger, starting from 250 instead of 400). float scale = fire->m_size / fire->m_maxSize * 1.5f; dlight_t *el = effects->CL_AllocElight( fire->m_dlightIndex ); el->origin = bottom; el->origin[2] += 16.0f * scale; el->color.r = 254; el->color.g = 100; el->color.b = 10; el->radius = random->RandomFloat(50, 131) * scale; el->die = gpGlobals->curtime + 0.1f; el->color.exponent = 5; /* dlight_t *dl = effects->CL_AllocDlight ( fire->m_dlightIndex ); dl->origin = bottom; dl->origin[2] += 16.0f * scale; dl->color.r = 254; dl->color.g = 174; dl->color.b = 10; dl->radius = random->RandomFloat(350,431) * scale; dl->die = gpGlobals->curtime + 0.1f; */ } } IMPLEMENT_CLIENTCLASS_DT( C_FireCrackerBlast, DT_FireCrackerBlast, CFireCrackerBlast ) END_RECV_TABLE()