//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "cbase.h" #include "ai_default.h" #include "ai_task.h" #include "ai_schedule.h" #include "ai_node.h" #include "ai_hull.h" #include "ai_hint.h" #include "ai_memory.h" #include "ai_route.h" #include "ai_motor.h" #include "soundent.h" #include "game.h" #include "npcevent.h" #include "entitylist.h" #include "activitylist.h" #include "hl1_basegrenade.h" #include "animation.h" #include "IEffects.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "ammodef.h" #include "soundenvelope.h" #include "hl1_CBaseHelicopter.h" #include "ndebugoverlay.h" #include "smoke_trail.h" #include "beam_shared.h" #include "grenade_homer.h" #define HOMER_TRAIL0_LIFE 0.1 #define HOMER_TRAIL1_LIFE 0.2 #define HOMER_TRAIL2_LIFE 3.0// 1.0 #define SF_NOTRANSITION 128 extern short g_sModelIndexFireball; class CNPC_Apache : public CBaseHelicopter { DECLARE_CLASS( CNPC_Apache, CBaseHelicopter ); public: DECLARE_DATADESC(); void Spawn( void ); void Precache( void ); int BloodColor( void ) { return DONT_BLEED; } Class_T Classify( void ) { return CLASS_HUMAN_MILITARY; }; void InitializeRotorSound( void ); void LaunchRocket( Vector &viewDir, int damage, int radius, Vector vecLaunchPoint ); void Flight( void ); bool FireGun( void ); void AimRocketGun( void ); void FireRocket( void ); void DyingThink( void ); int ObjectCaps( void ); void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ); /* bool OnInternalDrawModel( ClientModelRenderInfo_t *pInfo ) { BaseClass::OnInteralDrawModel( pInfo ); Vector origin = GetAbsOrigin(); origin.z += 32; SetAbsOrigin( origin ); }*/ /*( void SetAbsOrigin( const Vector& absOrigin ) { ((Vector&)absOrigin).z += 32; BaseClass::SetAbsOrigin( absOrigin ); }*/ /* int Save( CSave &save ); int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; void Spawn( void ); void Precache( void ); void Killed( entvars_t *pevAttacker, int iGib ); void GibMonster( void ); void EXPORT HuntThink( void ); void EXPORT FlyTouch( CBaseEntity *pOther ); void EXPORT CrashTouch( CBaseEntity *pOther ); void EXPORT DyingThink( void ); void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); void EXPORT NullThink( void ); void ShowDamage( void ); void Flight( void ); void FireRocket( void ); BOOL FireGun( void ); int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType);*/ int m_iRockets; float m_flForce; float m_flNextRocket; int m_iAmmoType; Vector m_vecTarget; Vector m_posTarget; Vector m_vecDesired; Vector m_posDesired; Vector m_vecGoal; QAngle m_angGun; int m_iSoundState; // don't save this int m_iExplode; int m_iBodyGibs; int m_nDebrisModel; float m_flGoalSpeed; CHandle m_hSmoke; CBeam *m_pBeam; }; BEGIN_DATADESC( CNPC_Apache ) DEFINE_FIELD( m_iRockets, FIELD_INTEGER ), DEFINE_FIELD( m_flForce, FIELD_FLOAT ), DEFINE_FIELD( m_flNextRocket, FIELD_TIME ), DEFINE_FIELD( m_vecTarget, FIELD_VECTOR ), DEFINE_FIELD( m_posTarget, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecDesired, FIELD_VECTOR ), DEFINE_FIELD( m_posDesired, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecGoal, FIELD_VECTOR ), DEFINE_FIELD( m_angGun, FIELD_VECTOR ), DEFINE_FIELD( m_flLastSeen, FIELD_TIME ), DEFINE_FIELD( m_flPrevSeen, FIELD_TIME ), // DEFINE_FIELD( m_iSoundState, FIELD_INTEGER ), // DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ), // DEFINE_FIELD( m_iExplode, FIELD_INTEGER ), // DEFINE_FIELD( m_iBodyGibs, FIELD_INTEGER ), DEFINE_FIELD( m_pBeam, FIELD_CLASSPTR ), DEFINE_FIELD( m_flGoalSpeed, FIELD_FLOAT ), DEFINE_FIELD( m_hSmoke, FIELD_EHANDLE ), END_DATADESC() ConVar sk_apache_health( "sk_apache_health","100"); static Vector s_vecSurroundingMins( -300, -300, -172); static Vector s_vecSurroundingMaxs(300, 300, 8); void CNPC_Apache::Spawn( void ) { Precache( ); SetModel( "models/apache.mdl" ); BaseClass::Spawn(); AddFlag( FL_NPC ); SetSolid( SOLID_BBOX ); SetMoveType( MOVETYPE_STEP ); AddFlag( FL_FLY ); m_iHealth = sk_apache_health.GetFloat(); m_flFieldOfView = -0.707; // 270 degrees m_fHelicopterFlags = BITS_HELICOPTER_MISSILE_ON | BITS_HELICOPTER_GUN_ON; InitBoneControllers(); SetRenderColor( 255, 255, 255, 255 ); m_iRockets = 10; UTIL_SetSize( this, Vector( -32, -32, -32 ), Vector( 32, 32, 32 ) ); //CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &s_vecSurroundingMins, &s_vecSurroundingMaxs ); //AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST ); m_hSmoke = NULL; } LINK_ENTITY_TO_CLASS ( monster_apache, CNPC_Apache ); int CNPC_Apache::ObjectCaps( void ) { if ( GetSpawnFlags() & SF_NOTRANSITION ) return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; else return BaseClass::ObjectCaps(); } void CNPC_Apache::Precache( void ) { // Get to tha chopper! PrecacheModel( "models/apache.mdl" ); PrecacheScriptSound( "Apache.Rotor" ); m_nDebrisModel = PrecacheModel( "models/metalplategibs_green.mdl" ); // Gun PrecacheScriptSound( "Apache.FireGun" ); m_iAmmoType = GetAmmoDef()->Index("9mmRound"); // Rockets UTIL_PrecacheOther( "grenade_homer" ); PrecacheScriptSound( "Apache.RPG" ); PrecacheModel( "models/weapons/w_missile.mdl" ); BaseClass::Precache(); } void CNPC_Apache::InitializeRotorSound( void ) { CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); CPASAttenuationFilter filter( this ); m_pRotorSound = controller.SoundCreate( filter, entindex(), "Apache.Rotor" ); BaseClass::InitializeRotorSound(); } void CNPC_Apache::Flight( void ) { StudioFrameAdvance( ); float flDistToDesiredPosition = (GetAbsOrigin() - m_vecDesiredPosition).Length(); // NDebugOverlay::Line(GetAbsOrigin(), m_vecDesiredPosition, 0,0,255, true, 0.1); if (m_flGoalSpeed < 800 ) m_flGoalSpeed += GetAcceleration(); // Vector vecGoalOrientation; if (flDistToDesiredPosition > 250) // 500 { Vector v1 = (m_vecTargetPosition - GetAbsOrigin()); Vector v2 = (m_vecDesiredPosition - GetAbsOrigin()); VectorNormalize( v1 ); VectorNormalize( v2 ); if (m_flLastSeen + 90 > gpGlobals->curtime && DotProduct( v1, v2 ) > 0.25) { m_vecGoalOrientation = ( m_vecTargetPosition - GetAbsOrigin()); } else { m_vecGoalOrientation = (m_vecDesiredPosition - GetAbsOrigin()); } VectorNormalize( m_vecGoalOrientation ); } else { AngleVectors( GetGoalEnt()->GetAbsAngles(), &m_vecGoalOrientation ); } // SetGoalOrientation( vecGoalOrientation ); if (GetGoalEnt()) { // ALERT( at_console, "%.0f\n", flLength ); if ( HasReachedTarget() ) { // If we get this close to the desired position, it's assumed that we've reached // the desired position, so move on. // Fire target that I've reached my goal m_AtTarget.FireOutput( GetGoalEnt(), this ); OnReachedTarget( GetGoalEnt() ); SetGoalEnt( gEntList.FindEntityByName( NULL, GetGoalEnt()->m_target ) ); if (GetGoalEnt()) { m_vecDesiredPosition = GetGoalEnt()->GetAbsOrigin(); // Vector vecGoalOrientation; AngleVectors( GetGoalEnt()->GetAbsAngles(), &m_vecGoalOrientation ); // SetGoalOrientation( vecGoalOrientation ); flDistToDesiredPosition = (GetAbsOrigin() - m_vecDesiredPosition).Length(); } } } else { // If we can't find a new target, just stay where we are. m_vecDesiredPosition = GetAbsOrigin(); } // tilt model 5 degrees QAngle angAdj = QAngle( 5.0, 0, 0 ); // estimate where I'll be facing in one seconds Vector forward, right, up; AngleVectors( GetAbsAngles() + GetLocalAngularVelocity() * 2 + angAdj, &forward, &right, &up ); // Vector vecEst1 = GetAbsOrigin() + pev->velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 ); // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right ); QAngle angVel = GetLocalAngularVelocity(); float flSide = DotProduct( m_vecGoalOrientation, right ); if (flSide < 0) { if ( angVel.y < 60) { angVel.y += 8; // 9 * (3.0/2.0); } } else { if ( angVel.y > -60) { angVel.y -= 8; // 9 * (3.0/2.0); } } angVel.y *= 0.98; SetLocalAngularVelocity( angVel ); Vector vecVel = GetAbsVelocity(); // estimate where I'll be in two seconds AngleVectors( GetAbsAngles() + GetLocalAngularVelocity() * 1 + angAdj, &forward, &right, &up ); Vector vecEst = GetAbsOrigin() + vecVel * 2.0 + up * m_flForce * 20 - Vector( 0, 0, 384 * 2 ); // add immediate force AngleVectors( GetAbsAngles() + angAdj, &forward, &right, &up ); vecVel.x += up.x * m_flForce; vecVel.y += up.y * m_flForce; vecVel.z += up.z * m_flForce; // add gravity vecVel.z -= 38.4; // 32ft/sec float flSpeed = vecVel.Length(); float flDir = DotProduct( Vector( forward.x, forward.y, 0 ), Vector( vecVel.x, vecVel.y, 0 ) ); if (flDir < 0) flSpeed = -flSpeed; float flDist = DotProduct( m_vecDesiredPosition - vecEst, forward ); // float flSlip = DotProduct( pev->velocity, gpGlobals->v_right ); float flSlip = -DotProduct( m_vecDesiredPosition - vecEst, right ); angVel = GetLocalAngularVelocity(); // fly sideways if (flSlip > 0) { if (GetAbsAngles().z > -30 && angVel.z > -15) angVel.z -= 4; else angVel.z += 2; } else { if (GetAbsAngles().z < 30 && angVel.z < 15) angVel.z += 4; else angVel.z -= 2; } SetLocalAngularVelocity( angVel ); // sideways drag vecVel.x = vecVel.x * (1.0 - fabs( right.x ) * 0.05); vecVel.y = vecVel.y * (1.0 - fabs( right.y ) * 0.05); vecVel.z = vecVel.z * (1.0 - fabs( right.z ) * 0.05); // general drag vecVel = vecVel * 0.995; // Set final computed velocity SetAbsVelocity( vecVel ); // apply power to stay correct height if (m_flForce < 80 && vecEst.z < m_vecDesiredPosition.z) { m_flForce += 12; } else if (m_flForce > 30) { if (vecEst.z > m_vecDesiredPosition.z) m_flForce -= 8; } angVel = GetLocalAngularVelocity(); // pitch forward or back to get to target if (flDist > 0 && flSpeed < m_flGoalSpeed && GetAbsAngles().x + angVel.x < 40) { // ALERT( at_console, "F " ); // lean forward angVel.x += 12.0; } else if (flDist < 0 && flSpeed > -50 && GetAbsAngles().x + angVel.x > -20) { // ALERT( at_console, "B " ); // lean backward angVel.x -= 12.0; } else if (GetAbsAngles().x + angVel.x < 0) { // ALERT( at_console, "f " ); angVel.x += 4.0; } else if (GetAbsAngles().x + angVel.x > 0) { // ALERT( at_console, "b " ); angVel.x -= 4.0; } // Set final computed angular velocity SetLocalAngularVelocity( angVel ); // ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", GetAbsOrigin().x, pev->velocity.x, flDist, flSpeed, GetAbsAngles().x, pev->avelocity.x, m_flForce ); // ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", GetAbsOrigin().z, pev->velocity.z, vecEst.z, m_posDesired.z, m_flForce ); } //------------------------------------------------------------------------------ // Purpose : // Input : // Output : //------------------------------------------------------------------------------ #define CHOPPER_AP_GUN_TIP 0 #define CHOPPER_AP_GUN_BASE 1 #define CHOPPER_BC_GUN_YAW 0 #define CHOPPER_BC_GUN_PITCH 1 #define CHOPPER_BC_POD_PITCH 2 bool CNPC_Apache::FireGun( ) { if ( !GetEnemy() ) return false; Vector vForward, vRight, vUp; AngleVectors( GetAbsAngles(), &vForward, &vUp, &vRight ); Vector posGun; QAngle angGun; GetAttachment( 1, posGun, angGun ); Vector vecTarget = (m_vecTargetPosition - posGun); VectorNormalize( vecTarget ); Vector vecOut; vecOut.x = DotProduct( vForward, vecTarget ); vecOut.y = -DotProduct( vUp, vecTarget ); vecOut.z = DotProduct( vRight, vecTarget ); QAngle angles; VectorAngles( vecOut, angles ); angles.y = AngleNormalize(angles.y); angles.x = AngleNormalize(angles.x); if (angles.x > m_angGun.x) m_angGun.x = MIN( angles.x, m_angGun.x + 12 ); if (angles.x < m_angGun.x) m_angGun.x = MAX( angles.x, m_angGun.x - 12 ); if (angles.y > m_angGun.y) m_angGun.y = MIN( angles.y, m_angGun.y + 12 ); if (angles.y < m_angGun.y) m_angGun.y = MAX( angles.y, m_angGun.y - 12 ); // hacks - shouldn't be hardcoded, oh well. // limit it so it doesn't pop if you try to set it to the max value m_angGun.y = clamp( m_angGun.y, -89.9, 89.9 ); m_angGun.x = clamp( m_angGun.x, -9.9, 44.9 ); m_angGun.y = SetBoneController( 0, m_angGun.y ); m_angGun.x = SetBoneController( 1, m_angGun.x ); Vector posBarrel; QAngle angBarrel; GetAttachment( 0, posBarrel, angBarrel ); Vector forward; AngleVectors( angBarrel + m_angGun, &forward ); Vector2D vec2LOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).AsVector2D(); vec2LOS.NormalizeInPlace(); float flDot = vec2LOS.Dot( forward.AsVector2D() ); //forward // NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( forward * 200 ), 255,0,0, false, 0.1); //LOS // NDebugOverlay::Line( posGun, m_vecTargetPosition , 0,0,255, false, 0.1); // NDebugOverlay::Box( GetAbsOrigin(), s_vecSurroundingMins, s_vecSurroundingMaxs, 0, 255,0, false, 0.1); if ( flDot > 0.98 ) { CPASAttenuationFilter filter( this, 0.2f ); EmitSound( filter, entindex(), "Apache.FireGun" );//<>temp sound // gun is a bit dodgy, just fire at the target if we are close FireBullets( 1, posGun, vecTarget, VECTOR_CONE_4DEGREES, 8192, m_iAmmoType, 2 ); return true; } return false; } void CNPC_Apache::FireRocket( void ) { static float side = 1.0; static int count; Vector vForward, vRight, vUp; AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp ); Vector vecSrc = GetAbsOrigin() + 1.5 * ( vForward * 21 + vRight * 70 * side + vUp * -79 ); // pick firing pod to launch from switch( m_iRockets % 5) { case 0: vecSrc = vecSrc + vRight * 10; break; case 1: vecSrc = vecSrc - vRight * 10; break; case 2: vecSrc = vecSrc + vUp * 10; break; case 3: vecSrc = vecSrc - vUp * 10; break; case 4: break; } Vector vTargetDir = m_vecTargetPosition - GetAbsOrigin(); VectorNormalize ( vTargetDir ); LaunchRocket( vTargetDir, 100, 150, vecSrc); m_iRockets--; side = - side; } void CNPC_Apache::AimRocketGun( void ) { Vector vForward, vRight, vUp; if (m_iRockets <= 0) return; Vector vTargetDir = m_vecTargetPosition - GetAbsOrigin(); VectorNormalize ( vTargetDir ); AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp ); Vector vecEst = ( vForward * 800 + GetAbsVelocity()); VectorNormalize ( vecEst ); trace_t tr1; UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecEst * 4096, MASK_ALL, this, COLLISION_GROUP_NONE, &tr1); // NDebugOverlay::Line(GetAbsOrigin(), tr1.endpos, 255,255,0, false, 0.1); UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vTargetDir * 4096, MASK_ALL, this, COLLISION_GROUP_NONE, &tr1); // NDebugOverlay::Line(GetAbsOrigin(), tr1.endpos, 0,255,0, false, 0.1); // ALERT( at_console, "%d %d %d %4.2f\n", GetAbsAngles().x < 0, DotProduct( pev->velocity, gpGlobals->v_forward ) > -100, m_flNextRocket < gpGlobals->curtime, DotProduct( m_vecTarget, vecEst ) ); if ((m_iRockets % 2) == 1) { FireRocket( ); m_flNextRocket = gpGlobals->curtime + 0.5; if (m_iRockets <= 0) { m_flNextRocket = gpGlobals->curtime + 10; m_iRockets = 10; } } else if (DotProduct( GetAbsVelocity(), vForward ) > -100 && m_flNextRocket < gpGlobals->curtime) { if (m_flLastSeen + 60 > gpGlobals->curtime) { if (GetEnemy() != NULL) { // make sure it's a good shot //if (DotProduct( vTargetDir, vecEst) > 0.5) { trace_t tr; UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecEst * 4096, MASK_ALL, this, COLLISION_GROUP_NONE, &tr); // NDebugOverlay::Line(GetAbsOrigin(), tr.endpos, 255,0,255, false, 5); // if ((tr.endpos - m_vecTargetPosition).Length() < 512) if ((tr.endpos - m_vecTargetPosition).Length() < 1024) FireRocket( ); } } else { trace_t tr; UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecEst * 4096, MASK_ALL, this, COLLISION_GROUP_NONE, &tr); // just fire when close if ((tr.endpos - m_vecTargetPosition).Length() < 512) FireRocket( ); } } } } #define MISSILE_HOMING_STRENGTH 0.3 #define MISSILE_HOMING_DELAY 5.0 #define MISSILE_HOMING_RAMP_UP 0.5 #define MISSILE_HOMING_DURATION 1.0 #define MISSILE_HOMING_RAMP_DOWN 0.5 void CNPC_Apache::LaunchRocket( Vector &viewDir, int damage, int radius, Vector vecLaunchPoint ) { CGrenadeHomer *pGrenade = CGrenadeHomer::CreateGrenadeHomer( MAKE_STRING("models/weapons/w_missile.mdl"), MAKE_STRING( "Apache.RPG" ), vecLaunchPoint, vec3_angle, edict() ); pGrenade->Spawn( ); pGrenade->SetHoming(MISSILE_HOMING_STRENGTH, MISSILE_HOMING_DELAY, MISSILE_HOMING_RAMP_UP, MISSILE_HOMING_DURATION, MISSILE_HOMING_RAMP_DOWN); pGrenade->SetDamage( damage ); pGrenade->m_DmgRadius = radius; pGrenade->Launch( this, GetEnemy(), viewDir * 1500, 500, 0, HOMER_SMOKE_TRAIL_ON ); } //----------------------------------------------------------------------------- // Purpose: Lame, temporary death //----------------------------------------------------------------------------- void CNPC_Apache::DyingThink( void ) { StudioFrameAdvance( ); SetNextThink( gpGlobals->curtime + 0.1f ); if( gpGlobals->curtime > m_flNextCrashExplosion ) { CPASFilter pasFilter( GetAbsOrigin() ); Vector pos; pos = GetAbsOrigin(); pos.x += random->RandomFloat( -150, 150 ); pos.y += random->RandomFloat( -150, 150 ); pos.z += random->RandomFloat( -150, -50 ); te->Explosion( pasFilter, 0.0, &pos, g_sModelIndexFireball, 10, 15, TE_EXPLFLAG_NONE, 100, 0 ); Vector vecSize = Vector( 500, 500, 60 ); CPVSFilter pvsFilter( GetAbsOrigin() ); te->BreakModel( pvsFilter, 0.0, GetAbsOrigin(), vec3_angle, vecSize, vec3_origin, m_nDebrisModel, 100, 0, 2.5, BREAK_METAL ); m_flNextCrashExplosion = gpGlobals->curtime + random->RandomFloat( 0.3, 0.5 ); } QAngle angVel = GetLocalAngularVelocity(); if( angVel.y < 400 ) { angVel.y *= 1.1; SetLocalAngularVelocity( angVel ); } Vector vecImpulse( 0, 0, -38.4 ); // gravity - 32ft/sec ApplyAbsVelocityImpulse( vecImpulse ); if( m_hSmoke ) { m_hSmoke->SetLifetime(0.1f); m_hSmoke = NULL; } } void CNPC_Apache::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) { CTakeDamageInfo dmgInfo = info; // HITGROUPS don't work currently. // ignore blades //if (ptr->hitgroup == 6 && (info.GetDamageType() & (DMG_ENERGYBEAM|DMG_BULLET|DMG_CLUB))) // return; // hit hard, hits cockpit if (info.GetDamage() > 50 || ptr->hitgroup == 1 || ptr->hitgroup == 2 || ptr->hitgroup == 3 ) { // ALERT( at_console, "%map .0f\n", flDamage ); AddMultiDamage( dmgInfo, this ); if ( info.GetDamage() > 50 ) { if ( m_hSmoke == NULL && (m_hSmoke = SmokeTrail::CreateSmokeTrail()) != NULL ) { m_hSmoke->m_Opacity = 1.0f; m_hSmoke->m_SpawnRate = 60; m_hSmoke->m_ParticleLifetime = 1.3f; m_hSmoke->m_StartColor.Init( 0.65f, 0.65f , 0.65f ); m_hSmoke->m_EndColor.Init( 0.65f, 0.65f, 0.65f ); m_hSmoke->m_StartSize = 12; m_hSmoke->m_EndSize = 64; m_hSmoke->m_SpawnRadius = 8; m_hSmoke->m_MinSpeed = 2; m_hSmoke->m_MaxSpeed = 24; m_hSmoke->SetLifetime( 1e6 ); m_hSmoke->FollowEntity( this ); } } } else { // do half damage in the body dmgInfo.ScaleDamage(0.5); AddMultiDamage( dmgInfo, this ); g_pEffects->Ricochet( ptr->endpos, ptr->plane.normal ); } if ( m_iHealth < 10 ) { if ( m_hSmoke == NULL && (m_hSmoke = SmokeTrail::CreateSmokeTrail()) != NULL ) { m_hSmoke->m_Opacity = 1.0f; m_hSmoke->m_SpawnRate = 60; m_hSmoke->m_ParticleLifetime = 1.3f; m_hSmoke->m_StartColor.Init( 0.65f, 0.65f , 0.65f ); m_hSmoke->m_EndColor.Init( 0.65f, 0.65f, 0.65f ); m_hSmoke->m_StartSize = 12; m_hSmoke->m_EndSize = 64; m_hSmoke->m_SpawnRadius = 8; m_hSmoke->m_MinSpeed = 2; m_hSmoke->m_MaxSpeed = 24; m_hSmoke->SetLifetime( 1e6 ); m_hSmoke->FollowEntity( this ); } } }