//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "C_Env_Meteor.h" #include "fx_explosion.h" #include "tempentity.h" #include "c_tracer.h" //============================================================================= // // Meteor Factory Functions // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_MeteorFactory::CreateMeteor( int nID, int iType, const Vector &vecPosition, const Vector &vecDirection, float flSpeed, float flStartTime, float flDamageRadius, const Vector &vecTriggerMins, const Vector &vecTriggerMaxs ) { C_EnvMeteor::Create( nID, iType, vecPosition, vecDirection, flSpeed, flStartTime, flDamageRadius, vecTriggerMins, vecTriggerMaxs ); } //============================================================================= // // Meteor Spawner Functions // void RecvProxy_MeteorTargetPositions( const CRecvProxyData *pData, void *pStruct, void *pOut ) { CEnvMeteorSpawnerShared *pSpawner = ( CEnvMeteorSpawnerShared* )pStruct; pSpawner->m_aTargets[pData->m_iElement].m_vecPosition.x = pData->m_Value.m_Vector[0]; pSpawner->m_aTargets[pData->m_iElement].m_vecPosition.y = pData->m_Value.m_Vector[1]; pSpawner->m_aTargets[pData->m_iElement].m_vecPosition.z = pData->m_Value.m_Vector[2]; } void RecvProxy_MeteorTargetRadii( const CRecvProxyData *pData, void *pStruct, void *pOut ) { CEnvMeteorSpawnerShared *pSpawner = ( CEnvMeteorSpawnerShared* )pStruct; pSpawner->m_aTargets[pData->m_iElement].m_flRadius = pData->m_Value.m_Float; } void RecvProxyArrayLength_MeteorTargets( void *pStruct, int objectID, int currentArrayLength ) { CEnvMeteorSpawnerShared *pSpawner = ( CEnvMeteorSpawnerShared* )pStruct; if ( pSpawner->m_aTargets.Count() < currentArrayLength ) { pSpawner->m_aTargets.SetSize( currentArrayLength ); } } BEGIN_RECV_TABLE_NOBASE( CEnvMeteorSpawnerShared, DT_EnvMeteorSpawnerShared ) // Setup (read from) Worldcraft. RecvPropInt ( RECVINFO( m_iMeteorType ) ), RecvPropInt ( RECVINFO( m_bSkybox ) ), RecvPropFloat ( RECVINFO( m_flMinSpawnTime ) ), RecvPropFloat ( RECVINFO( m_flMaxSpawnTime ) ), RecvPropInt ( RECVINFO( m_nMinSpawnCount ) ), RecvPropInt ( RECVINFO( m_nMaxSpawnCount ) ), RecvPropFloat ( RECVINFO( m_flMinSpeed ) ), RecvPropFloat ( RECVINFO( m_flMaxSpeed ) ), // Setup through Init. RecvPropFloat ( RECVINFO( m_flStartTime ) ), RecvPropInt ( RECVINFO( m_nRandomSeed ) ), RecvPropVector ( RECVINFO( m_vecMinBounds ) ), RecvPropVector ( RECVINFO( m_vecMaxBounds ) ), RecvPropVector ( RECVINFO( m_vecTriggerMins ) ), RecvPropVector ( RECVINFO( m_vecTriggerMaxs ) ), // Target List RecvPropArray2( RecvProxyArrayLength_MeteorTargets, RecvPropVector( "meteortargetposition_array_element", 0, 0, 0, RecvProxy_MeteorTargetPositions ), 16, 0, "meteortargetposition_array" ), RecvPropArray2( RecvProxyArrayLength_MeteorTargets, RecvPropFloat( "meteortargetradius_array_element", 0, 0, 0, RecvProxy_MeteorTargetRadii ), 16, 0, "meteortargetradius_array" ) END_RECV_TABLE() // This table encodes the CBaseEntity data. IMPLEMENT_CLIENTCLASS_DT( C_EnvMeteorSpawner, DT_EnvMeteorSpawner, CEnvMeteorSpawner ) RecvPropDataTable ( RECVINFO_DT( m_SpawnerShared ), 0, &REFERENCE_RECV_TABLE( DT_EnvMeteorSpawnerShared ) ), RecvPropInt ( RECVINFO( m_fDisabled ) ), END_RECV_TABLE() //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C_EnvMeteorSpawner::C_EnvMeteorSpawner() { } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteorSpawner::OnDataChanged( DataUpdateType_t updateType ) { // Initialize the client side spawner. m_SpawnerShared.Init( &m_Factory, m_SpawnerShared.m_nRandomSeed, m_SpawnerShared.m_flStartTime, m_SpawnerShared.m_vecMinBounds, m_SpawnerShared.m_vecMaxBounds, m_SpawnerShared.m_vecTriggerMins, m_SpawnerShared.m_vecTriggerMaxs ); // Set the next think to be the next spawn interval. if ( !m_fDisabled ) { SetNextClientThink( m_SpawnerShared.m_flNextSpawnTime ); } } #if 0 // Will probably be used later!! //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteorSpawner::ReceiveMessage( int classID, bf_read &msg ) { if ( classID != GetClientClass()->m_ClassID ) { // message is for subclass BaseClass::ReceiveMessage( classID, msg ); return; } m_SpawnerShared.m_flStartTime = msg.ReadLong(); m_SpawnerShared.m_flNextSpawnTime = msg.ReadLong(); SetNextClientThink( m_SpawnerShared.m_flNextSpawnTime ); } #endif //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteorSpawner::ClientThink( void ) { SetNextClientThink( m_SpawnerShared.MeteorThink( gpGlobals->curtime ) ); } //============================================================================= // // Meteor Tail Functions // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C_EnvMeteorHead::C_EnvMeteorHead() { m_vecPos.Init(); m_vecPrevPos.Init(); m_flParticleScale = 1.0f; m_pSmokeEmitter = NULL; m_flSmokeSpawnInterval = 0.0f; m_hSmokeMaterial = INVALID_MATERIAL_HANDLE; m_flSmokeLifetime = 2.5f; m_bEmitSmoke = true; m_hFlareMaterial = INVALID_MATERIAL_HANDLE; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C_EnvMeteorHead::~C_EnvMeteorHead() { Destroy(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteorHead::Start( const Vector &vecOrigin, const Vector &vecDirection ) { // Emitters. m_pSmokeEmitter = CSimpleEmitter::Create( "MeteorTrail" ); // m_pFireEmitter = CSimpleEmitter::Create( "MeteorFire" ); if ( !m_pSmokeEmitter /*|| !m_pFireEmitter*/ ) return; // Smoke m_pSmokeEmitter->SetSortOrigin( vecOrigin ); m_hSmokeMaterial = m_pSmokeEmitter->GetPMaterial( "particle/SmokeStack" ); Assert( m_hSmokeMaterial != INVALID_MATERIAL_HANDLE ); // Fire // m_pFireEmitter->SetSortOrigin( vecOrigin ); // m_hFireMaterial = m_pFireEmitter->GetPMaterial( "particle/particle_fire" ); // Assert( m_hFireMaterial != INVALID_MATERIAL_HANDLE ); // Flare // m_hFlareMaterial = m_ParticleEffect.FindOrAddMaterial( "effects/redflare" ); VectorCopy( vecDirection, m_vecDirection ); VectorCopy( vecOrigin, m_vecPos ); m_bInitThink = true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteorHead::Destroy( void ) { m_pSmokeEmitter = NULL; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteorHead::MeteorHeadThink( const Vector &vecOrigin, float flTime ) { if ( m_bInitThink ) { VectorCopy( vecOrigin, m_vecPrevPos ); m_bInitThink = false; } // Update the position of the emitters. VectorCopy( vecOrigin, m_vecPos ); // Update Smoke if ( m_pSmokeEmitter.IsValid() && m_bEmitSmoke ) { m_pSmokeEmitter->SetSortOrigin( m_vecPos ); // Get distance covered Vector vecDelta; VectorSubtract( m_vecPos, m_vecPrevPos, vecDelta ); float flLength = vecDelta.Length(); int nParticleCount = flLength / 35.0f; if ( nParticleCount < 1 ) { nParticleCount = 1; } flLength /= nParticleCount; Vector vecPos; for( int iParticle = 0; iParticle < nParticleCount; ++iParticle ) { vecPos = m_vecPrevPos + ( m_vecDirection * ( flLength * iParticle ) ); // Add some noise to the position. Vector vecPosOffset; vecPosOffset.Random( -m_flSmokeSpawnRadius, m_flSmokeSpawnRadius ); VectorAdd( vecPosOffset, vecPos, vecPosOffset ); SimpleParticle *pParticle = ( SimpleParticle* )m_pSmokeEmitter->AddParticle( sizeof( SimpleParticle ), m_hSmokeMaterial, vecPosOffset ); if ( pParticle ) { pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = m_flSmokeLifetime; // Add just a little movement. pParticle->m_vecVelocity.Random( -5.0f, 5.0f ); pParticle->m_uchColor[0] = 255.0f; pParticle->m_uchColor[1] = 255.0f; pParticle->m_uchColor[2] = 255.0f; pParticle->m_uchStartSize = 70 * m_flParticleScale; pParticle->m_uchEndSize = 25 * m_flParticleScale; float flAlpha = random->RandomFloat( 0.5f, 1.0f ); pParticle->m_uchStartAlpha = flAlpha * 255; pParticle->m_uchEndAlpha = 0; pParticle->m_flRoll = random->RandomInt( 0, 360 ); pParticle->m_flRollDelta = random->RandomFloat( -1.0f, 1.0f ); } } } // Update Fire // if ( m_pFireEmitter && m_bEmitFire ) // { // } // Flare // Save off position. VectorCopy( m_vecPos, m_vecPrevPos ); } //============================================================================= // // Meteor Tail Functions // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C_EnvMeteorTail::C_EnvMeteorTail() { m_TailMaterialHandle = INVALID_MATERIAL_HANDLE; m_pParticleMgr = NULL; m_pParticle = NULL; m_flFadeTime = 0.5f; m_flWidth = 3.0f; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C_EnvMeteorTail::~C_EnvMeteorTail() { Destroy(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteorTail::Start( const Vector &vecOrigin, const Vector &vecDirection, float flSpeed ) { // Set the particle manager. m_pParticleMgr = ParticleMgr(); m_pParticleMgr->AddEffect( &m_ParticleEffect, this ); m_TailMaterialHandle = m_ParticleEffect.FindOrAddMaterial( "particle/guidedplasmaprojectile" ); m_pParticle = m_ParticleEffect.AddParticle( sizeof( StandardParticle_t ), m_TailMaterialHandle ); if ( m_pParticle ) { m_pParticle->m_Pos = vecOrigin; } VectorCopy( vecDirection, m_vecDirection ); m_flSpeed = flSpeed; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteorTail::Destroy( void ) { if ( m_pParticleMgr ) { m_pParticleMgr->RemoveEffect( &m_ParticleEffect ); m_pParticleMgr = NULL; } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteorTail::DrawFragment( ParticleDraw* pDraw, const Vector &vecStart, const Vector &vecDelta, const Vector4D &vecStartColor, const Vector4D &vecEndColor, float flStartV, float flEndV ) { if( !pDraw->GetMeshBuilder() ) return; // Clip the fragment. Vector vecVerts[4]; if ( !Tracer_ComputeVerts( vecStart, vecDelta, m_flWidth, vecVerts ) ) return; // NOTE: Gotta get the winding right so it's not backface culled // (we need to turn of backface culling for these bad boys) CMeshBuilder* pMeshBuilder = pDraw->GetMeshBuilder(); pMeshBuilder->Position3f( vecVerts[0].x, vecVerts[0].y, vecVerts[0].z ); pMeshBuilder->TexCoord2f( 0, 0.0f, flStartV ); pMeshBuilder->Color4fv( vecStartColor.Base() ); pMeshBuilder->AdvanceVertex(); pMeshBuilder->Position3f( vecVerts[1].x, vecVerts[1].y, vecVerts[1].z ); pMeshBuilder->TexCoord2f( 0, 1.0f, flStartV ); pMeshBuilder->Color4fv( vecStartColor.Base() ); pMeshBuilder->AdvanceVertex(); pMeshBuilder->Position3f( vecVerts[3].x, vecVerts[3].y, vecVerts[3].z ); pMeshBuilder->TexCoord2f( 0, 1.0f, flEndV ); pMeshBuilder->Color4fv( vecEndColor.Base() ); pMeshBuilder->AdvanceVertex(); pMeshBuilder->Position3f( vecVerts[2].x, vecVerts[2].y, vecVerts[2].z ); pMeshBuilder->TexCoord2f( 0, 0.0f, flEndV ); pMeshBuilder->Color4fv( vecEndColor.Base() ); pMeshBuilder->AdvanceVertex(); } void C_EnvMeteorTail::SimulateParticles( CParticleSimulateIterator *pIterator ) { Particle *pParticle = (Particle*)pIterator->GetFirst(); while ( pParticle ) { // Update the particle position. pParticle->m_Pos = GetLocalOrigin(); pParticle = (Particle*)pIterator->GetNext(); } } void C_EnvMeteorTail::RenderParticles( CParticleRenderIterator *pIterator ) { const Particle *pParticle = (const Particle *)pIterator->GetFirst(); while ( pParticle ) { // Now draw the tail fragments... Vector4D vecStartColor( 1.0f, 1.0f, 1.0f, 1.0f ); Vector4D vecEndColor( 1.0f, 1.0f, 1.0f, 0.0f ); Vector vecDelta, vecStartPos, vecEndPos; // Calculate the tail. Vector vecTailEnd; vecTailEnd = GetLocalOrigin() + ( m_vecDirection * -m_flSpeed ); // Transform particles into camera space. TransformParticle( m_pParticleMgr->GetModelView(), GetLocalOrigin(), vecStartPos ); TransformParticle( m_pParticleMgr->GetModelView(), vecTailEnd, vecEndPos ); float sortKey = vecStartPos.z; // Draw the tail fragment. VectorSubtract( vecStartPos, vecEndPos, vecDelta ); DrawFragment( pIterator->GetParticleDraw(), vecEndPos, vecDelta, vecEndColor, vecStartColor, 1.0f - vecEndColor[3], 1.0f - vecStartColor[3] ); pParticle = (const Particle *)pIterator->GetNext( sortKey ); } } //============================================================================= // // Meteor Functions // static g_MeteorCounter = 0; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C_EnvMeteor::C_EnvMeteor() { } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C_EnvMeteor::~C_EnvMeteor() { } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteor::ClientThink( void ) { // Get the current time. float flTime = gpGlobals->curtime; // Update the meteor. if ( m_Meteor.IsInSkybox( flTime ) ) { if ( m_Meteor.m_nLocation == METEOR_LOCATION_WORLD ) { WorldToSkyboxThink( flTime ); } else { SkyboxThink( flTime ); } } else { if ( m_Meteor.m_nLocation == METEOR_LOCATION_SKYBOX ) { SkyboxToWorldThink( flTime ); } else { WorldThink( flTime ); } } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteor::SkyboxThink( float flTime ) { float flDeltaTime = flTime - m_Meteor.m_flStartTime; if ( flDeltaTime > METEOR_MAX_LIFETIME ) { Destroy( this ); return; } // Check to see if the object is passive or not - act accordingly! if ( !m_Meteor.IsPassive( flTime ) ) { // Update meteor position. Vector origin; m_Meteor.GetPositionAtTime( flTime, origin ); SetLocalOrigin( origin ); // Update the position of the tail effect. m_TailEffect.SetLocalOrigin( GetLocalOrigin() ); m_HeadEffect.MeteorHeadThink( GetLocalOrigin(), flTime ); } // Add the entity to the active list - update! AddEntity(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteor::WorldToSkyboxThink( float flTime ) { // Move the meteor from the world into the skybox. m_Meteor.ConvertFromWorldToSkybox(); // Destroy the head effect. Recreate it. m_HeadEffect.Destroy(); m_HeadEffect.Start( m_Meteor.m_vecStartPosition, m_vecTravelDir ); m_HeadEffect.SetSmokeEmission( true ); m_HeadEffect.SetParticleScale( 1.0f / 16.0f ); m_HeadEffect.m_bInitThink = true; // Update to world model. SetModel( "models/props/common/meteorites/meteor05.mdl" ); // Update the meteor position (move into the skybox!) SetLocalOrigin( m_Meteor.m_vecStartPosition ); // Update (think). SkyboxThink( flTime ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteor::SkyboxToWorldThink( float flTime ) { // Move the meteor from the skybox into the world. m_Meteor.ConvertFromSkyboxToWorld(); // Destroy the head effect. Recreate it. m_HeadEffect.Destroy(); m_HeadEffect.Start( m_Meteor.m_vecStartPosition, m_vecTravelDir ); m_HeadEffect.SetSmokeEmission( true ); m_HeadEffect.SetParticleScale( 1.0f ); m_HeadEffect.m_bInitThink = true; // Update to world model. SetModel( "models/props/common/meteorites/meteor04.mdl" ); SetLocalOrigin( m_Meteor.m_vecStartPosition ); // Update (think). WorldThink( flTime ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteor::WorldThink( float flTime ) { // Update meteor position. Vector vecEndPosition; m_Meteor.GetPositionAtTime( flTime, vecEndPosition ); // m_Meteor must return the end position in world space for the trace to work. Assert( GetMoveParent() == NULL ); // Msg( "Client: Time = %lf, Position: %4.2f %4.2f %4.2f\n", flTime, vecEndPosition.x, vecEndPosition.y, vecEndPosition.z ); // Check to see if we struck the world. If so, cause an explosion. trace_t trace; Vector vecMin, vecMax; GetRenderBounds( vecMin, vecMax ); // NOTE: This code works only if we aren't in hierarchy!!! Assert( !GetMoveParent() ); CTraceFilterWorldOnly traceFilter; UTIL_TraceHull( GetAbsOrigin(), vecEndPosition, vecMin, vecMax, MASK_SOLID_BRUSHONLY, &traceFilter, &trace ); // Collision. if ( ( trace.fraction < 1.0f ) && !( trace.surface.flags & SURF_SKY ) ) { // Move up to the end. Vector vecEnd = GetAbsOrigin() + ( ( vecEndPosition - GetAbsOrigin() ) * trace.fraction ); // Create an explosion effect! BaseExplosionEffect().Create( vecEnd, 10, 32, TE_EXPLFLAG_NONE ); // Debugging Info!!!! // debugoverlay->AddBoxOverlay( vecEnd, Vector( -10, -10, -10 ), Vector( 10, 10, 10 ), QAngle( 0.0f, 0.0f, 0.0f ), 255, 0, 0, 0, 100 ); Destroy( this ); return; } else { // Move to the end. SetLocalOrigin( vecEndPosition ); } m_TailEffect.SetLocalOrigin( GetLocalOrigin() ); m_HeadEffect.MeteorHeadThink( GetLocalOrigin(), flTime ); // Add the entity to the active list - update! AddEntity(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C_EnvMeteor *C_EnvMeteor::Create( int nID, int iMeteorType, const Vector &vecOrigin, const Vector &vecDirection, float flSpeed, float flStartTime, float flDamageRadius, const Vector &vecTriggerMins, const Vector &vecTriggerMaxs ) { C_EnvMeteor *pMeteor = new C_EnvMeteor; if ( pMeteor ) { pMeteor->m_Meteor.Init( nID, flStartTime, METEOR_PASSIVE_TIME, vecOrigin, vecDirection, flSpeed, flDamageRadius, vecTriggerMins, vecTriggerMaxs ); // Initialize the meteor. pMeteor->InitializeAsClientEntity( "models/props/common/meteorites/meteor05.mdl", RENDER_GROUP_OPAQUE_ENTITY ); // Handle forward simulation. if ( ( pMeteor->m_Meteor.m_flStartTime + METEOR_MAX_LIFETIME ) < gpGlobals->curtime ) { Destroy( pMeteor ); } // Meteor Head and Tail pMeteor->SetTravelDirection( vecDirection ); pMeteor->m_HeadEffect.SetSmokeEmission( true ); pMeteor->m_HeadEffect.Start( vecOrigin, vecDirection ); pMeteor->m_HeadEffect.SetParticleScale( 1.0f / 16.0f ); pMeteor->m_TailEffect.Start( vecOrigin, vecDirection, flSpeed ); pMeteor->SetNextClientThink( CLIENT_THINK_ALWAYS ); } return pMeteor; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void C_EnvMeteor::Destroy( C_EnvMeteor *pMeteor ) { Assert( pMeteor->GetClientHandle() != INVALID_CLIENTENTITY_HANDLE ); ClientThinkList()->AddToDeleteList( pMeteor->GetClientHandle() ); }