//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Grenade used by the city scanner // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "grenade_homer.h" #include "weapon_ar2.h" #include "soundent.h" #include "decals.h" #include "shake.h" #include "smoke_trail.h" #include "ar2_explosion.h" #include "mathlib/mathlib.h" #include "game.h" #include "ndebugoverlay.h" #include "hl2_shareddefs.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "movevars_shared.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define HOMER_TRAIL0_LIFE 0.1 #define HOMER_TRAIL1_LIFE 0.2 #define HOMER_TRAIL2_LIFE 3.0// 1.0 extern short g_sModelIndexFireball; // (in combatweapon.cpp) holds the index for the smoke cloud ConVar sk_dmg_homer_grenade( "sk_dmg_homer_grenade","0" ); ConVar sk_homer_grenade_radius( "sk_homer_grenade_radius","0" ); BEGIN_DATADESC( CGrenadeHomer ) DEFINE_ARRAY( m_hRocketTrail, FIELD_EHANDLE, 3 ), DEFINE_FIELD( m_sFlySound, FIELD_STRING), DEFINE_FIELD( m_flNextFlySoundTime, FIELD_TIME), DEFINE_FIELD( m_flHomingStrength, FIELD_FLOAT), DEFINE_FIELD( m_flHomingDelay, FIELD_FLOAT), DEFINE_FIELD( m_flHomingRampUp, FIELD_FLOAT), DEFINE_FIELD( m_flHomingDuration, FIELD_FLOAT), DEFINE_FIELD( m_flHomingRampDown, FIELD_FLOAT), DEFINE_FIELD( m_flHomingSpeed, FIELD_FLOAT), DEFINE_FIELD( m_flSpinMagnitude, FIELD_FLOAT), DEFINE_FIELD( m_flSpinSpeed, FIELD_FLOAT), DEFINE_FIELD( m_nRocketTrailType, FIELD_INTEGER), // DEFINE_FIELD( m_spriteTexture, FIELD_INTEGER), DEFINE_FIELD( m_flHomingLaunchTime, FIELD_TIME), DEFINE_FIELD( m_flHomingStartTime, FIELD_TIME ), DEFINE_FIELD( m_flHomingEndTime, FIELD_TIME ), DEFINE_FIELD( m_flSpinOffset, FIELD_FLOAT), DEFINE_FIELD( m_hTarget, FIELD_EHANDLE), // Function pointers DEFINE_THINKFUNC( AimThink ), DEFINE_ENTITYFUNC( GrenadeHomerTouch ), END_DATADESC() LINK_ENTITY_TO_CLASS( grenade_homer, CGrenadeHomer ); ///------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ CGrenadeHomer* CGrenadeHomer::CreateGrenadeHomer( string_t sModelName, string_t sFlySound, const Vector &vecOrigin, const QAngle &vecAngles, edict_t *pentOwner ) { CGrenadeHomer *pGrenade = (CGrenadeHomer*)CreateEntityByName( "grenade_homer" ); if ( !pGrenade ) { Warning( "NULL Ent in Create!\n" ); return NULL; } if ( pGrenade->edict() ) { pGrenade->m_sFlySound = sFlySound; pGrenade->SetOwnerEntity( Instance( pentOwner ) ); pGrenade->SetLocalOrigin( vecOrigin ); pGrenade->SetLocalAngles( vecAngles ); pGrenade->SetModel( STRING(sModelName) ); } return pGrenade; } void CGrenadeHomer::Precache( void ) { m_spriteTexture = PrecacheModel( "sprites/lgtning.vmt" ); PrecacheScriptSound( "GrenadeHomer.StopSounds" ); if ( NULL_STRING != m_sFlySound ) { PrecacheScriptSound( STRING(m_sFlySound) ); } } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CGrenadeHomer::Spawn( void ) { Precache( ); SetSolid( SOLID_BBOX ); SetMoveType( MOVETYPE_FLY ); UTIL_SetSize(this, Vector(0, 0, 0), Vector(0, 0, 0)); m_flDamage = sk_dmg_homer_grenade.GetFloat(); m_DmgRadius = sk_homer_grenade_radius.GetFloat(); m_takedamage = DAMAGE_YES; m_iHealth = 1; SetGravity( 1.0 ); SetFriction( 0.8 ); SetSequence( 1 ); m_flHomingStrength = 0; m_flHomingDelay = 0; m_flHomingDuration = 0; SetCollisionGroup( HL2COLLISION_GROUP_HOMING_MISSILE ); } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CGrenadeHomer::SetSpin(float flSpinMagnitude, float flSpinSpeed) { m_flSpinMagnitude = flSpinMagnitude; m_flSpinSpeed = flSpinSpeed; m_flSpinOffset = random->RandomInt(-m_flSpinSpeed,m_flSpinSpeed); } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CGrenadeHomer::SetHoming(float flStrength, float flDelay, float flRampUp, float flDuration, float flRampDown) { m_flHomingStrength = flStrength; m_flHomingDelay = flDelay; m_flHomingRampUp = flRampUp; m_flHomingDuration = flDuration; m_flHomingRampDown = flRampDown; } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CGrenadeHomer::StartRocketTrail(void) { RocketTrail *pRocketTrail = RocketTrail::CreateRocketTrail(); if(pRocketTrail) { pRocketTrail->m_SpawnRate = 80; pRocketTrail->m_ParticleLifetime = 2; if ( m_nRocketTrailType == HOMER_SMOKE_TRAIL_ALIEN ) { pRocketTrail->m_StartColor.Init(0.5, 0.0, 0.5); } else { pRocketTrail->m_StartColor.Init(0.75, 0.75, 0.75); } pRocketTrail->m_Opacity = 0.35f; pRocketTrail->m_EndColor.Init(0.4,0.4,0.4); pRocketTrail->m_StartSize = 8; pRocketTrail->m_EndSize = 16; pRocketTrail->m_SpawnRadius = 3; pRocketTrail->m_MinSpeed = 2; pRocketTrail->m_MaxSpeed = 10; pRocketTrail->SetLifetime(120); pRocketTrail->FollowEntity(this); m_hRocketTrail[0] = pRocketTrail; } /* pRocketTrail = RocketTrail::CreateRocketTrail(); if(pRocketTrail) { pRocketTrail->m_SpawnRate = 100; pRocketTrail->m_ParticleLifetime = HOMER_TRAIL1_LIFE; if ( m_nRocketTrailType == HOMER_SMOKE_TRAIL_ALIEN ) { pRocketTrail->m_StartColor.Init(0.0, 0.0, 0.5); } else { pRocketTrail->m_StartColor.Init(0.5, 0.5, 0.0); } pRocketTrail->m_EndColor.Init(0.5,0.5,0.5); pRocketTrail->m_StartSize = 3; pRocketTrail->m_EndSize = 6; pRocketTrail->m_SpawnRadius = 1; pRocketTrail->m_MinSpeed = 15; pRocketTrail->m_MaxSpeed = 25; pRocketTrail->SetLifetime(120); pRocketTrail->FollowEntity(this); m_hRocketTrail[1] = pRocketTrail; } pRocketTrail = RocketTrail::CreateRocketTrail(); if(pRocketTrail) { pRocketTrail->m_SpawnRate = 50; pRocketTrail->m_ParticleLifetime = HOMER_TRAIL2_LIFE; if ( m_nRocketTrailType == HOMER_SMOKE_TRAIL_ALIEN ) { pRocketTrail->m_StartColor.Init(0.1, 0.0, 0.1); } else { pRocketTrail->m_StartColor.Init(0.1, 0.1, 0.1); } pRocketTrail->m_EndColor.Init(0.5,0.5,0.5); pRocketTrail->m_StartSize = 8; pRocketTrail->m_EndSize = 20; pRocketTrail->m_SpawnRadius = 1; pRocketTrail->m_MinSpeed = 15; pRocketTrail->m_MaxSpeed = 25; pRocketTrail->SetLifetime(120); pRocketTrail->FollowEntity(this); m_hRocketTrail[2] = pRocketTrail; } */ } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CGrenadeHomer::UpdateRocketTrail(float fScale) { if (m_hRocketTrail[0] == NULL) { StartRocketTrail(); } if (m_hRocketTrail[0]) { m_hRocketTrail[0]->m_ParticleLifetime = fScale*HOMER_TRAIL0_LIFE; } if (m_hRocketTrail[1]) { m_hRocketTrail[1]->m_ParticleLifetime = fScale*HOMER_TRAIL1_LIFE; } if (m_hRocketTrail[2]) { m_hRocketTrail[2]->m_ParticleLifetime = fScale*HOMER_TRAIL2_LIFE; } } void CGrenadeHomer::StopRocketTrail() { // Stop emitting smoke for (int i=0;i<3;i++) { if(m_hRocketTrail[i]) { m_hRocketTrail[i]->SetEmit(false); UTIL_Remove( m_hRocketTrail[i] ); m_hRocketTrail[i] = NULL; } } } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CGrenadeHomer::Launch( CBaseEntity* pOwner, CBaseEntity* pTarget, const Vector& vInitVelocity, float flHomingSpeed, float flGravity, int nRocketTrailType) { SetOwnerEntity( pOwner ); m_hTarget = pTarget; SetAbsVelocity( vInitVelocity ); m_flHomingSpeed = flHomingSpeed; SetGravity( flGravity ); m_nRocketTrailType = nRocketTrailType; // ---------------------------- // Initialize homing parameters // ---------------------------- m_flHomingLaunchTime = gpGlobals->curtime; // ------------- // Smoke trail. // ------------- if ( (m_nRocketTrailType == HOMER_SMOKE_TRAIL_ON) || (m_nRocketTrailType == HOMER_SMOKE_TRAIL_ALIEN) ) { StartRocketTrail(); } SetUse( &CGrenadeHomer::DetonateUse ); SetTouch( &CGrenadeHomer::GrenadeHomerTouch ); SetThink( &CGrenadeHomer::AimThink ); AimThink(); SetNextThink( gpGlobals->curtime ); // Issue danger! if ( pTarget ) { // Figure out how long it'll take for me to reach the target. float flDist = ( pTarget->WorldSpaceCenter() - WorldSpaceCenter() ).Length(); float flTime = MAX( 0.5, flDist / GetAbsVelocity().Length() ); CSoundEnt::InsertSound ( SOUND_DANGER, m_hTarget->GetAbsOrigin(), 300, flTime, pOwner ); } } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ void CGrenadeHomer::Event_Killed( const CTakeDamageInfo &info ) { Detonate( ); } void CGrenadeHomer::GrenadeHomerTouch( CBaseEntity *pOther ) { Assert( pOther ); // Don't take damage from other homing grenades so can shoot in vollies if (FClassnameIs( pOther, "grenade_homer") || !pOther->IsSolid() ) { return; } // ---------------------------------- // If I hit the sky, don't explode // ---------------------------------- trace_t tr; UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + GetAbsVelocity(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr); if (tr.surface.flags & SURF_SKY) { StopRocketTrail(); UTIL_Remove( this ); } else { Detonate(); } } void CGrenadeHomer::Detonate(void) { StopRocketTrail(); StopSound(entindex(), CHAN_BODY, STRING(m_sFlySound)); m_takedamage = DAMAGE_NO; CPASFilter filter( GetAbsOrigin() ); te->Explosion( filter, 0.0, &GetAbsOrigin(), g_sModelIndexFireball, 2.0, 15, TE_EXPLFLAG_NONE, m_DmgRadius, m_flDamage ); // int magnitude = 1.0; // int colorRamp = random->RandomInt( 128, 255 ); if ( m_nRocketTrailType == HOMER_SMOKE_TRAIL_ALIEN ) { // Add a shockring CBroadcastRecipientFilter filter3; te->BeamRingPoint( filter3, 0, GetAbsOrigin(), //origin 16, //start radius 1000, //end radius m_spriteTexture, //texture 0, //halo index 0, //start frame 2, //framerate 0.3f, //life 128, //width 16, //spread 0, //amplitude 100, //r 0, //g 200, //b 50, //a 128 //speed ); // Add a shockring CBroadcastRecipientFilter filter4; te->BeamRingPoint( filter4, 0, GetAbsOrigin(), //origin 16, //start radius 500, //end radius m_spriteTexture, //texture 0, //halo index 0, //start frame 2, //framerate 0.3f, //life 128, //width 16, //spread 0, //amplitude 200, //r 0, //g 100, //b 50, //a 128 //speed ); } Vector vecForward = GetAbsVelocity(); VectorNormalize(vecForward); trace_t tr; UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + 60*vecForward, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, & tr); UTIL_DecalTrace( &tr, "Scorch" ); UTIL_ScreenShake( GetAbsOrigin(), 25.0, 150.0, 1.0, 750, SHAKE_START ); RadiusDamage ( CTakeDamageInfo( this, GetOwnerEntity(), m_flDamage, DMG_BLAST ), GetAbsOrigin(), m_DmgRadius, CLASS_NONE, NULL ); CPASAttenuationFilter filter2( this, "GrenadeHomer.StopSounds" ); EmitSound( filter2, entindex(), "GrenadeHomer.StopSounds" ); UTIL_Remove( this ); } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- void CGrenadeHomer::PlayFlySound(void) { if (gpGlobals->curtime > m_flNextFlySoundTime) { CPASAttenuationFilter filter( this, 0.8 ); EmitSound_t ep; ep.m_nChannel = CHAN_BODY; ep.m_pSoundName = STRING(m_sFlySound); ep.m_flVolume = 1.0f; ep.m_SoundLevel = SNDLVL_NORM; ep.m_nPitch = 100; EmitSound( filter, entindex(), ep ); m_flNextFlySoundTime = gpGlobals->curtime + 1.0; } } //------------------------------------------------------------------------------ // Purpose : Move toward targetmap // Input : // Output : //------------------------------------------------------------------------------ void CGrenadeHomer::AimThink( void ) { // Blow up the missile if we have an explicit detonate time that // has been reached if (m_flDetonateTime != 0 && gpGlobals->curtime > m_flDetonateTime) { Detonate(); return; } PlayFlySound(); Vector vTargetPos = vec3_origin; Vector vTargetDir; float flCurHomingStrength = 0; // ------------------------------------------------ // If I'm homing // ------------------------------------------------ if (m_hTarget != NULL) { vTargetPos = m_hTarget->EyePosition(); vTargetDir = vTargetPos - GetAbsOrigin(); VectorNormalize(vTargetDir); // -------------------------------------------------- // If my target is far away do some primitive // obstacle avoidance // -------------------------------------------------- if ((vTargetPos - GetAbsOrigin()).Length() > 200) { Vector vTravelDir = GetAbsVelocity(); VectorNormalize(vTravelDir); vTravelDir *= 50; trace_t tr; UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vTravelDir, MASK_SHOT, m_hTarget, COLLISION_GROUP_NONE, &tr ); if (tr.fraction != 1.0) { // Head off in normal float dotPr = DotProduct(vTravelDir,tr.plane.normal); Vector vBounce = -dotPr * tr.plane.normal; vBounce.z = 0; VectorNormalize(vBounce); vTargetDir += vBounce; VectorNormalize(vTargetDir); // DEBUG TOOL //NDebugOverlay::Line(GetOrigin(), GetOrigin()+vTravelDir, 255,0,0, true, 20); //NDebugOverlay::Line(GetOrigin(), GetOrigin()+(12*tr.plane.normal), 0,0,255, true, 20); //NDebugOverlay::Line(GetOrigin(), GetOrigin()+(vTargetDir), 0,255,0, true, 20); } } float flTargetSpeed = GetAbsVelocity().Length(); float flHomingRampUpStartTime = m_flHomingLaunchTime + m_flHomingDelay; float flHomingSustainStartTime = flHomingRampUpStartTime + m_flHomingRampUp; float flHomingRampDownStartTime = flHomingSustainStartTime + m_flHomingDuration; float flHomingEndHomingTime = flHomingRampDownStartTime + m_flHomingRampDown; // --------- // Delay // --------- if (gpGlobals->curtime < flHomingRampUpStartTime) { flCurHomingStrength = 0; flTargetSpeed = 0; } // ---------- // Ramp Up // ---------- else if (gpGlobals->curtime < flHomingSustainStartTime) { float flAge = gpGlobals->curtime - flHomingRampUpStartTime; flCurHomingStrength = m_flHomingStrength * (flAge/m_flHomingRampUp); flTargetSpeed = flCurHomingStrength * m_flHomingSpeed; } // ---------- // Sustain // ---------- else if (gpGlobals->curtime < flHomingRampDownStartTime) { flCurHomingStrength = m_flHomingStrength; flTargetSpeed = m_flHomingSpeed; } // ----------- // Ramp Down // ----------- else if (gpGlobals->curtime < flHomingEndHomingTime) { float flAge = gpGlobals->curtime - flHomingRampDownStartTime; flCurHomingStrength = m_flHomingStrength * (1-(flAge/m_flHomingRampDown)); flTargetSpeed = m_flHomingSpeed; } // --------------- // Set Homing // --------------- if (flCurHomingStrength > 0) { // ------------- // Smoke trail. // ------------- if (m_nRocketTrailType == HOMER_SMOKE_TRAIL_ON_HOMING) { UpdateRocketTrail(flCurHomingStrength); } // Extract speed and direction Vector vCurDir = GetAbsVelocity(); float flCurSpeed = VectorNormalize(vCurDir); flTargetSpeed = MAX(flTargetSpeed, flCurSpeed); // Add in homing direction Vector vecNewVelocity = GetAbsVelocity(); float flTimeToUse = gpGlobals->frametime; while (flTimeToUse > 0) { vecNewVelocity = (flCurHomingStrength * vTargetDir) + ((1 - flCurHomingStrength) * vCurDir); flTimeToUse = -0.1; } VectorNormalize(vecNewVelocity); vecNewVelocity *= flTargetSpeed; SetAbsVelocity( vecNewVelocity ); } } // ---------------------------------------------------------------------------------------- // Add time-coherent noise to the current velocity // ---------------------------------------------------------------------------------------- Vector vecImpulse( 0, 0, 0 ); if (m_flSpinMagnitude > 0) { vecImpulse.x += m_flSpinMagnitude*sin(m_flSpinSpeed * gpGlobals->curtime + m_flSpinOffset); vecImpulse.y += m_flSpinMagnitude*cos(m_flSpinSpeed * gpGlobals->curtime + m_flSpinOffset); vecImpulse.z -= m_flSpinMagnitude*cos(m_flSpinSpeed * gpGlobals->curtime + m_flSpinOffset); } // Add in gravity vecImpulse.z -= GetGravity() * GetCurrentGravity() * gpGlobals->frametime; ApplyAbsVelocityImpulse( vecImpulse ); QAngle angles; VectorAngles( GetAbsVelocity(), angles ); SetLocalAngles( angles ); #if 0 // BUBBLE if( gpGlobals->curtime > m_flNextWarnTime ) { // Make a bubble of warning sound in front of me. const float WARN_INTERVAL = 0.25f; float flSpeed = GetAbsVelocity().Length(); Vector vecWarnLocation; // warn a little bit ahead of us, please. vecWarnLocation = GetAbsOrigin() + GetAbsVelocity() * 0.75; // Make a bubble of warning ahead of the missile. CSoundEnt::InsertSound ( SOUND_DANGER, vecWarnLocation, flSpeed * WARN_INTERVAL, 0.5 ); #if 0 Vector vecRight, vecForward; AngleVectors( GetAbsAngles(), &vecForward, &vecRight, NULL ); NDebugOverlay::Line( vecWarnLocation, vecWarnLocation + vecForward * flSpeed * WARN_INTERVAL * 0.5, 255,255,0, true, 10); NDebugOverlay::Line( vecWarnLocation, vecWarnLocation - vecForward * flSpeed * WARN_INTERVAL * 0.5, 255,255,0, true, 10); NDebugOverlay::Line( vecWarnLocation, vecWarnLocation + vecRight * flSpeed * WARN_INTERVAL * 0.5, 255,255,0, true, 10); NDebugOverlay::Line( vecWarnLocation, vecWarnLocation - vecRight * flSpeed * WARN_INTERVAL * 0.5, 255,255,0, true, 10); #endif m_flNextWarnTime = gpGlobals->curtime + WARN_INTERVAL; } #endif // BUBBLE SetNextThink( gpGlobals->curtime + 0.1f ); } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ int CGrenadeHomer::OnTakeDamage( const CTakeDamageInfo &info ) { // Don't take damage from other homing grenades so can shoot in vollies if (FClassnameIs( info.GetInflictor(), "grenade_homer")) { return 0; } return BaseClass::OnTakeDamage( info ); } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ CGrenadeHomer::CGrenadeHomer(void) { for (int i=0;i<3;i++) { m_hRocketTrail[i] = NULL; } }