diff options
Diffstat (limited to 'game/server/hl2/weapon_rpg.cpp')
| -rw-r--r-- | game/server/hl2/weapon_rpg.cpp | 2376 |
1 files changed, 2376 insertions, 0 deletions
diff --git a/game/server/hl2/weapon_rpg.cpp b/game/server/hl2/weapon_rpg.cpp new file mode 100644 index 0000000..e627859 --- /dev/null +++ b/game/server/hl2/weapon_rpg.cpp @@ -0,0 +1,2376 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "basehlcombatweapon.h" +#include "basecombatcharacter.h" +#include "movie_explosion.h" +#include "soundent.h" +#include "player.h" +#include "rope.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "explode.h" +#include "util.h" +#include "in_buttons.h" +#include "weapon_rpg.h" +#include "shake.h" +#include "ai_basenpc.h" +#include "ai_squad.h" +#include "te_effect_dispatch.h" +#include "triggers.h" +#include "smoke_trail.h" +#include "collisionutils.h" +#include "hl2_shareddefs.h" +#include "rumble_shared.h" +#include "gamestats.h" + +#ifdef PORTAL + #include "portal_util_shared.h" +#endif + +#ifdef HL2_DLL + extern int g_interactionPlayerLaunchedRPG; +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define RPG_SPEED 1500 + +static ConVar sk_apc_missile_damage("sk_apc_missile_damage", "15"); +ConVar rpg_missle_use_custom_detonators( "rpg_missle_use_custom_detonators", "1" ); + +#define APC_MISSILE_DAMAGE sk_apc_missile_damage.GetFloat() + +const char *g_pLaserDotThink = "LaserThinkContext"; + +//----------------------------------------------------------------------------- +// Laser Dot +//----------------------------------------------------------------------------- +class CLaserDot : public CSprite +{ + DECLARE_CLASS( CLaserDot, CSprite ); +public: + + CLaserDot( void ); + ~CLaserDot( void ); + + static CLaserDot *Create( const Vector &origin, CBaseEntity *pOwner = NULL, bool bVisibleDot = true ); + + void SetTargetEntity( CBaseEntity *pTarget ) { m_hTargetEnt = pTarget; } + CBaseEntity *GetTargetEntity( void ) { return m_hTargetEnt; } + + void SetLaserPosition( const Vector &origin, const Vector &normal ); + Vector GetChasePosition(); + void TurnOn( void ); + void TurnOff( void ); + bool IsOn() const { return m_bIsOn; } + + void Toggle( void ); + + void LaserThink( void ); + + int ObjectCaps() { return (BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + + void MakeInvisible( void ); + +protected: + Vector m_vecSurfaceNormal; + EHANDLE m_hTargetEnt; + bool m_bVisibleLaserDot; + bool m_bIsOn; + + DECLARE_DATADESC(); +public: + CLaserDot *m_pNext; +}; + +// a list of laser dots to search quickly +CEntityClassList<CLaserDot> g_LaserDotList; +template <> CLaserDot *CEntityClassList<CLaserDot>::m_pClassList = NULL; +CLaserDot *GetLaserDotList() +{ + return g_LaserDotList.m_pClassList; +} + +BEGIN_DATADESC( CMissile ) + + DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ), + DEFINE_FIELD( m_hRocketTrail, FIELD_EHANDLE ), + DEFINE_FIELD( m_flAugerTime, FIELD_TIME ), + DEFINE_FIELD( m_flMarkDeadTime, FIELD_TIME ), + DEFINE_FIELD( m_flGracePeriodEndsAt, FIELD_TIME ), + DEFINE_FIELD( m_flDamage, FIELD_FLOAT ), + DEFINE_FIELD( m_bCreateDangerSounds, FIELD_BOOLEAN ), + + // Function Pointers + DEFINE_FUNCTION( MissileTouch ), + DEFINE_FUNCTION( AccelerateThink ), + DEFINE_FUNCTION( AugerThink ), + DEFINE_FUNCTION( IgniteThink ), + DEFINE_FUNCTION( SeekThink ), + +END_DATADESC() +LINK_ENTITY_TO_CLASS( rpg_missile, CMissile ); + +class CWeaponRPG; + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CMissile::CMissile() +{ + m_hRocketTrail = NULL; + m_bCreateDangerSounds = false; +} + +CMissile::~CMissile() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +void CMissile::Precache( void ) +{ + PrecacheModel( "models/weapons/w_missile.mdl" ); + PrecacheModel( "models/weapons/w_missile_launch.mdl" ); + PrecacheModel( "models/weapons/w_missile_closed.mdl" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +void CMissile::Spawn( void ) +{ + Precache(); + + SetSolid( SOLID_BBOX ); + SetModel("models/weapons/w_missile_launch.mdl"); + UTIL_SetSize( this, -Vector(4,4,4), Vector(4,4,4) ); + + SetTouch( &CMissile::MissileTouch ); + + SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + SetThink( &CMissile::IgniteThink ); + + SetNextThink( gpGlobals->curtime + 0.3f ); + SetDamage( 200.0f ); + + m_takedamage = DAMAGE_YES; + m_iHealth = m_iMaxHealth = 100; + m_bloodColor = DONT_BLEED; + m_flGracePeriodEndsAt = 0; + + AddFlag( FL_OBJECT ); +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CMissile::Event_Killed( const CTakeDamageInfo &info ) +{ + m_takedamage = DAMAGE_NO; + + ShotDown(); +} + +unsigned int CMissile::PhysicsSolidMaskForEntity( void ) const +{ + return BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +int CMissile::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + if ( ( info.GetDamageType() & (DMG_MISSILEDEFENSE | DMG_AIRBOAT) ) == false ) + return 0; + + bool bIsDamaged; + if( m_iHealth <= AugerHealth() ) + { + // This missile is already damaged (i.e., already running AugerThink) + bIsDamaged = true; + } + else + { + // This missile isn't damaged enough to wobble in flight yet + bIsDamaged = false; + } + + int nRetVal = BaseClass::OnTakeDamage_Alive( info ); + + if( !bIsDamaged ) + { + if ( m_iHealth <= AugerHealth() ) + { + ShotDown(); + } + } + + return nRetVal; +} + + +//----------------------------------------------------------------------------- +// Purpose: Stops any kind of tracking and shoots dumb +//----------------------------------------------------------------------------- +void CMissile::DumbFire( void ) +{ + SetThink( NULL ); + SetMoveType( MOVETYPE_FLY ); + + SetModel("models/weapons/w_missile.mdl"); + UTIL_SetSize( this, vec3_origin, vec3_origin ); + + EmitSound( "Missile.Ignite" ); + + // Smoke trail. + CreateSmokeTrail(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMissile::SetGracePeriod( float flGracePeriod ) +{ + m_flGracePeriodEndsAt = gpGlobals->curtime + flGracePeriod; + + // Go non-solid until the grace period ends + AddSolidFlags( FSOLID_NOT_SOLID ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CMissile::AccelerateThink( void ) +{ + Vector vecForward; + + // !!!UNDONE - make this work exactly the same as HL1 RPG, lest we have looping sound bugs again! + EmitSound( "Missile.Accelerate" ); + + // SetEffects( EF_LIGHT ); + + AngleVectors( GetLocalAngles(), &vecForward ); + SetAbsVelocity( vecForward * RPG_SPEED ); + + SetThink( &CMissile::SeekThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); +} + +#define AUGER_YDEVIANCE 20.0f +#define AUGER_XDEVIANCEUP 8.0f +#define AUGER_XDEVIANCEDOWN 1.0f + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CMissile::AugerThink( void ) +{ + // If we've augered long enough, then just explode + if ( m_flAugerTime < gpGlobals->curtime ) + { + Explode(); + return; + } + + if ( m_flMarkDeadTime < gpGlobals->curtime ) + { + m_lifeState = LIFE_DYING; + } + + QAngle angles = GetLocalAngles(); + + angles.y += random->RandomFloat( -AUGER_YDEVIANCE, AUGER_YDEVIANCE ); + angles.x += random->RandomFloat( -AUGER_XDEVIANCEDOWN, AUGER_XDEVIANCEUP ); + + SetLocalAngles( angles ); + + Vector vecForward; + + AngleVectors( GetLocalAngles(), &vecForward ); + + SetAbsVelocity( vecForward * 1000.0f ); + + SetNextThink( gpGlobals->curtime + 0.05f ); +} + +//----------------------------------------------------------------------------- +// Purpose: Causes the missile to spiral to the ground and explode, due to damage +//----------------------------------------------------------------------------- +void CMissile::ShotDown( void ) +{ + CEffectData data; + data.m_vOrigin = GetAbsOrigin(); + + DispatchEffect( "RPGShotDown", data ); + + if ( m_hRocketTrail != NULL ) + { + m_hRocketTrail->m_bDamaged = true; + } + + SetThink( &CMissile::AugerThink ); + SetNextThink( gpGlobals->curtime ); + m_flAugerTime = gpGlobals->curtime + 1.5f; + m_flMarkDeadTime = gpGlobals->curtime + 0.75; + + // Let the RPG start reloading immediately + if ( m_hOwner != NULL ) + { + m_hOwner->NotifyRocketDied(); + m_hOwner = NULL; + } +} + + +//----------------------------------------------------------------------------- +// The actual explosion +//----------------------------------------------------------------------------- +void CMissile::DoExplosion( void ) +{ + // Explode + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), GetDamage(), CMissile::EXPLOSION_RADIUS, + SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0.0f, this); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMissile::Explode( void ) +{ + // Don't explode against the skybox. Just pretend that + // the missile flies off into the distance. + Vector forward; + + GetVectors( &forward, NULL, NULL ); + + trace_t tr; + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + forward * 16, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + m_takedamage = DAMAGE_NO; + SetSolid( SOLID_NONE ); + if( tr.fraction == 1.0 || !(tr.surface.flags & SURF_SKY) ) + { + DoExplosion(); + } + + if( m_hRocketTrail ) + { + m_hRocketTrail->SetLifetime(0.1f); + m_hRocketTrail = NULL; + } + + if ( m_hOwner != NULL ) + { + m_hOwner->NotifyRocketDied(); + m_hOwner = NULL; + } + + StopSound( "Missile.Ignite" ); + UTIL_Remove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOther - +//----------------------------------------------------------------------------- +void CMissile::MissileTouch( CBaseEntity *pOther ) +{ + Assert( pOther ); + + // Don't touch triggers (but DO hit weapons) + if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER|FSOLID_VOLUME_CONTENTS) && pOther->GetCollisionGroup() != COLLISION_GROUP_WEAPON ) + { + // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them. + if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) + return; + } + + Explode(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMissile::CreateSmokeTrail( void ) +{ + if ( m_hRocketTrail ) + return; + + // Smoke trail. + if ( (m_hRocketTrail = RocketTrail::CreateRocketTrail()) != NULL ) + { + m_hRocketTrail->m_Opacity = 0.2f; + m_hRocketTrail->m_SpawnRate = 100; + m_hRocketTrail->m_ParticleLifetime = 0.5f; + m_hRocketTrail->m_StartColor.Init( 0.65f, 0.65f , 0.65f ); + m_hRocketTrail->m_EndColor.Init( 0.0, 0.0, 0.0 ); + m_hRocketTrail->m_StartSize = 8; + m_hRocketTrail->m_EndSize = 32; + m_hRocketTrail->m_SpawnRadius = 4; + m_hRocketTrail->m_MinSpeed = 2; + m_hRocketTrail->m_MaxSpeed = 16; + + m_hRocketTrail->SetLifetime( 999 ); + m_hRocketTrail->FollowEntity( this, "0" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMissile::IgniteThink( void ) +{ + SetMoveType( MOVETYPE_FLY ); + SetModel("models/weapons/w_missile.mdl"); + UTIL_SetSize( this, vec3_origin, vec3_origin ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + + //TODO: Play opening sound + + Vector vecForward; + + EmitSound( "Missile.Ignite" ); + + AngleVectors( GetLocalAngles(), &vecForward ); + SetAbsVelocity( vecForward * RPG_SPEED ); + + SetThink( &CMissile::SeekThink ); + SetNextThink( gpGlobals->curtime ); + + if ( m_hOwner && m_hOwner->GetOwner() ) + { + CBasePlayer *pPlayer = ToBasePlayer( m_hOwner->GetOwner() ); + + if ( pPlayer ) + { + color32 white = { 255,225,205,64 }; + UTIL_ScreenFade( pPlayer, white, 0.1f, 0.0f, FFADE_IN ); + + pPlayer->RumbleEffect( RUMBLE_RPG_MISSILE, 0, RUMBLE_FLAG_RESTART ); + } + } + + CreateSmokeTrail(); +} + + +//----------------------------------------------------------------------------- +// Gets the shooting position +//----------------------------------------------------------------------------- +void CMissile::GetShootPosition( CLaserDot *pLaserDot, Vector *pShootPosition ) +{ + if ( pLaserDot->GetOwnerEntity() != NULL ) + { + //FIXME: Do we care this isn't exactly the muzzle position? + *pShootPosition = pLaserDot->GetOwnerEntity()->WorldSpaceCenter(); + } + else + { + *pShootPosition = pLaserDot->GetChasePosition(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#define RPG_HOMING_SPEED 0.125f + +void CMissile::ComputeActualDotPosition( CLaserDot *pLaserDot, Vector *pActualDotPosition, float *pHomingSpeed ) +{ + *pHomingSpeed = RPG_HOMING_SPEED; + if ( pLaserDot->GetTargetEntity() ) + { + *pActualDotPosition = pLaserDot->GetChasePosition(); + return; + } + + Vector vLaserStart; + GetShootPosition( pLaserDot, &vLaserStart ); + + //Get the laser's vector + Vector vLaserDir; + VectorSubtract( pLaserDot->GetChasePosition(), vLaserStart, vLaserDir ); + + //Find the length of the current laser + float flLaserLength = VectorNormalize( vLaserDir ); + + //Find the length from the missile to the laser's owner + float flMissileLength = GetAbsOrigin().DistTo( vLaserStart ); + + //Find the length from the missile to the laser's position + Vector vecTargetToMissile; + VectorSubtract( GetAbsOrigin(), pLaserDot->GetChasePosition(), vecTargetToMissile ); + float flTargetLength = VectorNormalize( vecTargetToMissile ); + + // See if we should chase the line segment nearest us + if ( ( flMissileLength < flLaserLength ) || ( flTargetLength <= 512.0f ) ) + { + *pActualDotPosition = UTIL_PointOnLineNearestPoint( vLaserStart, pLaserDot->GetChasePosition(), GetAbsOrigin() ); + *pActualDotPosition += ( vLaserDir * 256.0f ); + } + else + { + // Otherwise chase the dot + *pActualDotPosition = pLaserDot->GetChasePosition(); + } + +// NDebugOverlay::Line( pLaserDot->GetChasePosition(), vLaserStart, 0, 255, 0, true, 0.05f ); +// NDebugOverlay::Line( GetAbsOrigin(), *pActualDotPosition, 255, 0, 0, true, 0.05f ); +// NDebugOverlay::Cross3D( *pActualDotPosition, -Vector(4,4,4), Vector(4,4,4), 255, 0, 0, true, 0.05f ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMissile::SeekThink( void ) +{ + CBaseEntity *pBestDot = NULL; + float flBestDist = MAX_TRACE_LENGTH; + float dotDist; + + // If we have a grace period, go solid when it ends + if ( m_flGracePeriodEndsAt ) + { + if ( m_flGracePeriodEndsAt < gpGlobals->curtime ) + { + RemoveSolidFlags( FSOLID_NOT_SOLID ); + m_flGracePeriodEndsAt = 0; + } + } + + //Search for all dots relevant to us + for( CLaserDot *pEnt = GetLaserDotList(); pEnt != NULL; pEnt = pEnt->m_pNext ) + { + if ( !pEnt->IsOn() ) + continue; + + if ( pEnt->GetOwnerEntity() != GetOwnerEntity() ) + continue; + + dotDist = (GetAbsOrigin() - pEnt->GetAbsOrigin()).Length(); + + //Find closest + if ( dotDist < flBestDist ) + { + pBestDot = pEnt; + flBestDist = dotDist; + } + } + + if( hl2_episodic.GetBool() ) + { + if( flBestDist <= ( GetAbsVelocity().Length() * 2.5f ) && FVisible( pBestDot->GetAbsOrigin() ) ) + { + // Scare targets + CSoundEnt::InsertSound( SOUND_DANGER, pBestDot->GetAbsOrigin(), CMissile::EXPLOSION_RADIUS, 0.2f, pBestDot, SOUNDENT_CHANNEL_REPEATED_DANGER, NULL ); + } + } + + if ( rpg_missle_use_custom_detonators.GetBool() ) + { + for ( int i = gm_CustomDetonators.Count() - 1; i >=0; --i ) + { + CustomDetonator_t &detonator = gm_CustomDetonators[i]; + if ( !detonator.hEntity ) + { + gm_CustomDetonators.FastRemove( i ); + } + else + { + const Vector &vPos = detonator.hEntity->CollisionProp()->WorldSpaceCenter(); + if ( detonator.halfHeight > 0 ) + { + if ( fabsf( vPos.z - GetAbsOrigin().z ) < detonator.halfHeight ) + { + if ( ( GetAbsOrigin().AsVector2D() - vPos.AsVector2D() ).LengthSqr() < detonator.radiusSq ) + { + Explode(); + return; + } + } + } + else + { + if ( ( GetAbsOrigin() - vPos ).LengthSqr() < detonator.radiusSq ) + { + Explode(); + return; + } + } + } + } + } + + //If we have a dot target + if ( pBestDot == NULL ) + { + //Think as soon as possible + SetNextThink( gpGlobals->curtime ); + return; + } + + CLaserDot *pLaserDot = (CLaserDot *)pBestDot; + Vector targetPos; + + float flHomingSpeed; + Vector vecLaserDotPosition; + ComputeActualDotPosition( pLaserDot, &targetPos, &flHomingSpeed ); + + if ( IsSimulatingOnAlternateTicks() ) + flHomingSpeed *= 2; + + Vector vTargetDir; + VectorSubtract( targetPos, GetAbsOrigin(), vTargetDir ); + float flDist = VectorNormalize( vTargetDir ); + + if( pLaserDot->GetTargetEntity() != NULL && flDist <= 240.0f && hl2_episodic.GetBool() ) + { + // Prevent the missile circling the Strider like a Halo in ep1_c17_06. If the missile gets within 20 + // feet of a Strider, tighten up the turn speed of the missile so it can break the halo and strike. (sjb 4/27/2006) + if( pLaserDot->GetTargetEntity()->ClassMatches( "npc_strider" ) ) + { + flHomingSpeed *= 1.75f; + } + } + + Vector vDir = GetAbsVelocity(); + float flSpeed = VectorNormalize( vDir ); + Vector vNewVelocity = vDir; + if ( gpGlobals->frametime > 0.0f ) + { + if ( flSpeed != 0 ) + { + vNewVelocity = ( flHomingSpeed * vTargetDir ) + ( ( 1 - flHomingSpeed ) * vDir ); + + // This computation may happen to cancel itself out exactly. If so, slam to targetdir. + if ( VectorNormalize( vNewVelocity ) < 1e-3 ) + { + vNewVelocity = (flDist != 0) ? vTargetDir : vDir; + } + } + else + { + vNewVelocity = vTargetDir; + } + } + + QAngle finalAngles; + VectorAngles( vNewVelocity, finalAngles ); + SetAbsAngles( finalAngles ); + + vNewVelocity *= flSpeed; + SetAbsVelocity( vNewVelocity ); + + if( GetAbsVelocity() == vec3_origin ) + { + // Strange circumstances have brought this missile to halt. Just blow it up. + Explode(); + return; + } + + // Think as soon as possible + SetNextThink( gpGlobals->curtime ); + +#ifdef HL2_EPISODIC + + if ( m_bCreateDangerSounds == true ) + { + trace_t tr; + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + GetAbsVelocity() * 0.5, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + + CSoundEnt::InsertSound( SOUND_DANGER, tr.endpos, 100, 0.2, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// Input : &vecOrigin - +// &vecAngles - +// NULL - +// +// Output : CMissile +//----------------------------------------------------------------------------- +CMissile *CMissile::Create( const Vector &vecOrigin, const QAngle &vecAngles, edict_t *pentOwner = NULL ) +{ + //CMissile *pMissile = (CMissile *)CreateEntityByName("rpg_missile" ); + CMissile *pMissile = (CMissile *) CBaseEntity::Create( "rpg_missile", vecOrigin, vecAngles, CBaseEntity::Instance( pentOwner ) ); + pMissile->SetOwnerEntity( Instance( pentOwner ) ); + pMissile->Spawn(); + pMissile->AddEffects( EF_NOSHADOW ); + + Vector vecForward; + AngleVectors( vecAngles, &vecForward ); + + pMissile->SetAbsVelocity( vecForward * 300 + Vector( 0,0, 128 ) ); + + return pMissile; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CUtlVector<CMissile::CustomDetonator_t> CMissile::gm_CustomDetonators; + +void CMissile::AddCustomDetonator( CBaseEntity *pEntity, float radius, float height ) +{ + int i = gm_CustomDetonators.AddToTail(); + gm_CustomDetonators[i].hEntity = pEntity; + gm_CustomDetonators[i].radiusSq = Square( radius ); + gm_CustomDetonators[i].halfHeight = height * 0.5f; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMissile::RemoveCustomDetonator( CBaseEntity *pEntity ) +{ + for ( int i = 0; i < gm_CustomDetonators.Count(); i++ ) + { + if ( gm_CustomDetonators[i].hEntity == pEntity ) + { + gm_CustomDetonators.FastRemove( i ); + break; + } + } +} + + +//----------------------------------------------------------------------------- +// This entity is used to create little force boxes that the helicopter +// should avoid. +//----------------------------------------------------------------------------- +class CInfoAPCMissileHint : public CBaseEntity +{ + DECLARE_DATADESC(); + +public: + DECLARE_CLASS( CInfoAPCMissileHint, CBaseEntity ); + + virtual void Spawn( ); + virtual void Activate(); + virtual void UpdateOnRemove(); + + static CBaseEntity *FindAimTarget( CBaseEntity *pMissile, const char *pTargetName, + const Vector &vecCurrentTargetPos, const Vector &vecCurrentTargetVel ); + +private: + EHANDLE m_hTarget; + + typedef CHandle<CInfoAPCMissileHint> APCMissileHintHandle_t; + static CUtlVector< APCMissileHintHandle_t > s_APCMissileHints; +}; + + +//----------------------------------------------------------------------------- +// +// This entity is used to create little force boxes that the helicopters should avoid. +// +//----------------------------------------------------------------------------- +CUtlVector< CInfoAPCMissileHint::APCMissileHintHandle_t > CInfoAPCMissileHint::s_APCMissileHints; + +LINK_ENTITY_TO_CLASS( info_apc_missile_hint, CInfoAPCMissileHint ); + +BEGIN_DATADESC( CInfoAPCMissileHint ) + DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Spawn, remove +//----------------------------------------------------------------------------- +void CInfoAPCMissileHint::Spawn( ) +{ + SetModel( STRING( GetModelName() ) ); + SetSolid( SOLID_BSP ); + AddSolidFlags( FSOLID_NOT_SOLID ); + AddEffects( EF_NODRAW ); +} + +void CInfoAPCMissileHint::Activate( ) +{ + BaseClass::Activate(); + + m_hTarget = gEntList.FindEntityByName( NULL, m_target ); + if ( m_hTarget == NULL ) + { + DevWarning( "%s: Could not find target '%s'!\n", GetClassname(), STRING( m_target ) ); + } + else + { + s_APCMissileHints.AddToTail( this ); + } +} + +void CInfoAPCMissileHint::UpdateOnRemove( ) +{ + s_APCMissileHints.FindAndRemove( this ); + BaseClass::UpdateOnRemove(); +} + + +//----------------------------------------------------------------------------- +// Where are how should we avoid? +//----------------------------------------------------------------------------- +#define HINT_PREDICTION_TIME 3.0f + +CBaseEntity *CInfoAPCMissileHint::FindAimTarget( CBaseEntity *pMissile, const char *pTargetName, + const Vector &vecCurrentEnemyPos, const Vector &vecCurrentEnemyVel ) +{ + if ( !pTargetName ) + return NULL; + + float flOOSpeed = pMissile->GetAbsVelocity().Length(); + if ( flOOSpeed != 0.0f ) + { + flOOSpeed = 1.0f / flOOSpeed; + } + + for ( int i = s_APCMissileHints.Count(); --i >= 0; ) + { + CInfoAPCMissileHint *pHint = s_APCMissileHints[i]; + if ( !pHint->NameMatches( pTargetName ) ) + continue; + + if ( !pHint->m_hTarget ) + continue; + + Vector vecMissileToHint, vecMissileToEnemy; + VectorSubtract( pHint->m_hTarget->WorldSpaceCenter(), pMissile->GetAbsOrigin(), vecMissileToHint ); + VectorSubtract( vecCurrentEnemyPos, pMissile->GetAbsOrigin(), vecMissileToEnemy ); + float flDistMissileToHint = VectorNormalize( vecMissileToHint ); + VectorNormalize( vecMissileToEnemy ); + if ( DotProduct( vecMissileToHint, vecMissileToEnemy ) < 0.866f ) + continue; + + // Determine when the target will be inside the volume. + // Project at most 3 seconds in advance + Vector vecRayDelta; + VectorMultiply( vecCurrentEnemyVel, HINT_PREDICTION_TIME, vecRayDelta ); + + BoxTraceInfo_t trace; + if ( !IntersectRayWithOBB( vecCurrentEnemyPos, vecRayDelta, pHint->CollisionProp()->CollisionToWorldTransform(), + pHint->CollisionProp()->OBBMins(), pHint->CollisionProp()->OBBMaxs(), 0.0f, &trace )) + { + continue; + } + + // Determine the amount of time it would take the missile to reach the target + // If we can reach the target within the time it takes for the enemy to reach the + float tSqr = flDistMissileToHint * flOOSpeed / HINT_PREDICTION_TIME; + if ( (tSqr < (trace.t1 * trace.t1)) || (tSqr > (trace.t2 * trace.t2)) ) + continue; + + return pHint->m_hTarget; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// a list of missiles to search quickly +//----------------------------------------------------------------------------- +CEntityClassList<CAPCMissile> g_APCMissileList; +template <> CAPCMissile *CEntityClassList<CAPCMissile>::m_pClassList = NULL; +CAPCMissile *GetAPCMissileList() +{ + return g_APCMissileList.m_pClassList; +} + +//----------------------------------------------------------------------------- +// Finds apc missiles in cone +//----------------------------------------------------------------------------- +CAPCMissile *FindAPCMissileInCone( const Vector &vecOrigin, const Vector &vecDirection, float flAngle ) +{ + float flCosAngle = cos( DEG2RAD( flAngle ) ); + for( CAPCMissile *pEnt = GetAPCMissileList(); pEnt != NULL; pEnt = pEnt->m_pNext ) + { + if ( !pEnt->IsSolid() ) + continue; + + Vector vecDelta; + VectorSubtract( pEnt->GetAbsOrigin(), vecOrigin, vecDelta ); + VectorNormalize( vecDelta ); + float flDot = DotProduct( vecDelta, vecDirection ); + if ( flDot > flCosAngle ) + return pEnt; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// +// Specialized version of the missile +// +//----------------------------------------------------------------------------- +#define MAX_HOMING_DISTANCE 2250.0f +#define MIN_HOMING_DISTANCE 1250.0f +#define MAX_NEAR_HOMING_DISTANCE 1750.0f +#define MIN_NEAR_HOMING_DISTANCE 1000.0f +#define DOWNWARD_BLEND_TIME_START 0.2f +#define MIN_HEIGHT_DIFFERENCE 250.0f +#define MAX_HEIGHT_DIFFERENCE 550.0f +#define CORRECTION_TIME 0.2f +#define APC_LAUNCH_HOMING_SPEED 0.1f +#define APC_HOMING_SPEED 0.025f +#define HOMING_SPEED_ACCEL 0.01f + +BEGIN_DATADESC( CAPCMissile ) + + DEFINE_FIELD( m_flReachedTargetTime, FIELD_TIME ), + DEFINE_FIELD( m_flIgnitionTime, FIELD_TIME ), + DEFINE_FIELD( m_bGuidingDisabled, FIELD_BOOLEAN ), + DEFINE_FIELD( m_hSpecificTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_strHint, FIELD_STRING ), + DEFINE_FIELD( m_flLastHomingSpeed, FIELD_FLOAT ), +// DEFINE_FIELD( m_pNext, FIELD_CLASSPTR ), + + DEFINE_THINKFUNC( BeginSeekThink ), + DEFINE_THINKFUNC( AugerStartThink ), + DEFINE_THINKFUNC( ExplodeThink ), + DEFINE_THINKFUNC( APCSeekThink ), + + DEFINE_FUNCTION( APCMissileTouch ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( apc_missile, CAPCMissile ); + +CAPCMissile *CAPCMissile::Create( const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecVelocity, CBaseEntity *pOwner ) +{ + CAPCMissile *pMissile = (CAPCMissile *)CBaseEntity::Create( "apc_missile", vecOrigin, vecAngles, pOwner ); + pMissile->SetOwnerEntity( pOwner ); + pMissile->Spawn(); + pMissile->SetAbsVelocity( vecVelocity ); + pMissile->AddFlag( FL_NOTARGET ); + pMissile->AddEffects( EF_NOSHADOW ); + return pMissile; +} + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CAPCMissile::CAPCMissile() +{ + g_APCMissileList.Insert( this ); +} + +CAPCMissile::~CAPCMissile() +{ + g_APCMissileList.Remove( this ); +} + + +//----------------------------------------------------------------------------- +// Shared initialization code +//----------------------------------------------------------------------------- +void CAPCMissile::Init() +{ + SetMoveType( MOVETYPE_FLY ); + SetModel("models/weapons/w_missile.mdl"); + UTIL_SetSize( this, vec3_origin, vec3_origin ); + CreateSmokeTrail(); + SetTouch( &CAPCMissile::APCMissileTouch ); + m_flLastHomingSpeed = APC_HOMING_SPEED; + CreateDangerSounds( true ); + + + if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE ) + { + AddFlag( FL_AIMTARGET ); + } +} + + +//----------------------------------------------------------------------------- +// For hitting a specific target +//----------------------------------------------------------------------------- +void CAPCMissile::AimAtSpecificTarget( CBaseEntity *pTarget ) +{ + m_hSpecificTarget = pTarget; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOther - +//----------------------------------------------------------------------------- +void CAPCMissile::APCMissileTouch( CBaseEntity *pOther ) +{ + Assert( pOther ); + if ( !pOther->IsSolid() && !pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS) ) + return; + + Explode(); +} + + +//----------------------------------------------------------------------------- +// Specialized version of the missile +//----------------------------------------------------------------------------- +void CAPCMissile::IgniteDelay( void ) +{ + m_flIgnitionTime = gpGlobals->curtime + 0.3f; + + SetThink( &CAPCMissile::BeginSeekThink ); + SetNextThink( m_flIgnitionTime ); + Init(); + AddSolidFlags( FSOLID_NOT_SOLID ); +} + +void CAPCMissile::AugerDelay( float flDelay ) +{ + m_flIgnitionTime = gpGlobals->curtime; + SetThink( &CAPCMissile::AugerStartThink ); + SetNextThink( gpGlobals->curtime + flDelay ); + Init(); + DisableGuiding(); +} + +void CAPCMissile::AugerStartThink() +{ + if ( m_hRocketTrail != NULL ) + { + m_hRocketTrail->m_bDamaged = true; + } + m_flAugerTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f ); + SetThink( &CAPCMissile::AugerThink ); + SetNextThink( gpGlobals->curtime ); +} + +void CAPCMissile::ExplodeDelay( float flDelay ) +{ + m_flIgnitionTime = gpGlobals->curtime; + SetThink( &CAPCMissile::ExplodeThink ); + SetNextThink( gpGlobals->curtime + flDelay ); + Init(); + DisableGuiding(); +} + + +void CAPCMissile::BeginSeekThink( void ) +{ + RemoveSolidFlags( FSOLID_NOT_SOLID ); + SetThink( &CAPCMissile::APCSeekThink ); + SetNextThink( gpGlobals->curtime ); +} + +void CAPCMissile::APCSeekThink( void ) +{ + BaseClass::SeekThink(); + + bool bFoundDot = false; + + //If we can't find a dot to follow around then just send me wherever I'm facing so I can blow up in peace. + for( CLaserDot *pEnt = GetLaserDotList(); pEnt != NULL; pEnt = pEnt->m_pNext ) + { + if ( !pEnt->IsOn() ) + continue; + + if ( pEnt->GetOwnerEntity() != GetOwnerEntity() ) + continue; + + bFoundDot = true; + } + + if ( bFoundDot == false ) + { + Vector vDir = GetAbsVelocity(); + VectorNormalize ( vDir ); + + SetAbsVelocity( vDir * 800 ); + + SetThink( NULL ); + } +} + +void CAPCMissile::ExplodeThink() +{ + DoExplosion(); +} + +//----------------------------------------------------------------------------- +// Health lost at which augering starts +//----------------------------------------------------------------------------- +int CAPCMissile::AugerHealth() +{ + return m_iMaxHealth - 25; +} + + +//----------------------------------------------------------------------------- +// Health lost at which augering starts +//----------------------------------------------------------------------------- +void CAPCMissile::DisableGuiding() +{ + m_bGuidingDisabled = true; +} + + +//----------------------------------------------------------------------------- +// Guidance hints +//----------------------------------------------------------------------------- +void CAPCMissile::SetGuidanceHint( const char *pHintName ) +{ + m_strHint = MAKE_STRING( pHintName ); +} + + +//----------------------------------------------------------------------------- +// The actual explosion +//----------------------------------------------------------------------------- +void CAPCMissile::DoExplosion( void ) +{ + if ( GetWaterLevel() != 0 ) + { + CEffectData data; + data.m_vOrigin = WorldSpaceCenter(); + data.m_flMagnitude = 128; + data.m_flScale = 128; + data.m_fFlags = 0; + DispatchEffect( "WaterSurfaceExplosion", data ); + } + else + { +#ifdef HL2_EPISODIC + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), this, APC_MISSILE_DAMAGE, 100, true, 20000 ); +#else + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), APC_MISSILE_DAMAGE, 100, true, 20000 ); +#endif + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAPCMissile::ComputeLeadingPosition( const Vector &vecShootPosition, CBaseEntity *pTarget, Vector *pLeadPosition ) +{ + Vector vecTarget = pTarget->BodyTarget( vecShootPosition, false ); + float flShotSpeed = GetAbsVelocity().Length(); + if ( flShotSpeed == 0 ) + { + *pLeadPosition = vecTarget; + return; + } + + Vector vecVelocity = pTarget->GetSmoothedVelocity(); + vecVelocity.z = 0.0f; + float flTargetSpeed = VectorNormalize( vecVelocity ); + Vector vecDelta; + VectorSubtract( vecShootPosition, vecTarget, vecDelta ); + float flTargetToShooter = VectorNormalize( vecDelta ); + float flCosTheta = DotProduct( vecDelta, vecVelocity ); + + // Law of cosines... z^2 = x^2 + y^2 - 2xy cos Theta + // where z = flShooterToPredictedTargetPosition = flShotSpeed * predicted time + // x = flTargetSpeed * predicted time + // y = flTargetToShooter + // solve for predicted time using at^2 + bt + c = 0, t = (-b +/- sqrt( b^2 - 4ac )) / 2a + float a = flTargetSpeed * flTargetSpeed - flShotSpeed * flShotSpeed; + float b = -2.0f * flTargetToShooter * flCosTheta * flTargetSpeed; + float c = flTargetToShooter * flTargetToShooter; + + float flDiscrim = b*b - 4*a*c; + if (flDiscrim < 0) + { + *pLeadPosition = vecTarget; + return; + } + + flDiscrim = sqrt(flDiscrim); + float t = (-b + flDiscrim) / (2.0f * a); + float t2 = (-b - flDiscrim) / (2.0f * a); + if ( t < t2 ) + { + t = t2; + } + + if ( t <= 0.0f ) + { + *pLeadPosition = vecTarget; + return; + } + + VectorMA( vecTarget, flTargetSpeed * t, vecVelocity, *pLeadPosition ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAPCMissile::ComputeActualDotPosition( CLaserDot *pLaserDot, Vector *pActualDotPosition, float *pHomingSpeed ) +{ + if ( m_bGuidingDisabled ) + { + *pActualDotPosition = GetAbsOrigin(); + *pHomingSpeed = 0.0f; + m_flLastHomingSpeed = *pHomingSpeed; + return; + } + + if ( ( m_strHint != NULL_STRING ) && (!m_hSpecificTarget) ) + { + Vector vecOrigin, vecVelocity; + CBaseEntity *pTarget = pLaserDot->GetTargetEntity(); + if ( pTarget ) + { + vecOrigin = pTarget->BodyTarget( GetAbsOrigin(), false ); + vecVelocity = pTarget->GetSmoothedVelocity(); + } + else + { + vecOrigin = pLaserDot->GetChasePosition(); + vecVelocity = vec3_origin; + } + + m_hSpecificTarget = CInfoAPCMissileHint::FindAimTarget( this, STRING( m_strHint ), vecOrigin, vecVelocity ); + } + + CBaseEntity *pLaserTarget = m_hSpecificTarget ? m_hSpecificTarget.Get() : pLaserDot->GetTargetEntity(); + if ( !pLaserTarget ) + { + BaseClass::ComputeActualDotPosition( pLaserDot, pActualDotPosition, pHomingSpeed ); + m_flLastHomingSpeed = *pHomingSpeed; + return; + } + + if ( pLaserTarget->ClassMatches( "npc_bullseye" ) ) + { + if ( m_flLastHomingSpeed != RPG_HOMING_SPEED ) + { + if (m_flLastHomingSpeed > RPG_HOMING_SPEED) + { + m_flLastHomingSpeed -= HOMING_SPEED_ACCEL * UTIL_GetSimulationInterval(); + if ( m_flLastHomingSpeed < RPG_HOMING_SPEED ) + { + m_flLastHomingSpeed = RPG_HOMING_SPEED; + } + } + else + { + m_flLastHomingSpeed += HOMING_SPEED_ACCEL * UTIL_GetSimulationInterval(); + if ( m_flLastHomingSpeed > RPG_HOMING_SPEED ) + { + m_flLastHomingSpeed = RPG_HOMING_SPEED; + } + } + } + *pHomingSpeed = m_flLastHomingSpeed; + *pActualDotPosition = pLaserTarget->WorldSpaceCenter(); + return; + } + + Vector vLaserStart; + GetShootPosition( pLaserDot, &vLaserStart ); + *pHomingSpeed = APC_LAUNCH_HOMING_SPEED; + + //Get the laser's vector + Vector vecTargetPosition = pLaserTarget->BodyTarget( GetAbsOrigin(), false ); + + // Compute leading position + Vector vecLeadPosition; + ComputeLeadingPosition( GetAbsOrigin(), pLaserTarget, &vecLeadPosition ); + + Vector vecTargetToMissile, vecTargetToShooter; + VectorSubtract( GetAbsOrigin(), vecTargetPosition, vecTargetToMissile ); + VectorSubtract( vLaserStart, vecTargetPosition, vecTargetToShooter ); + + *pActualDotPosition = vecLeadPosition; + + float flMinHomingDistance = MIN_HOMING_DISTANCE; + float flMaxHomingDistance = MAX_HOMING_DISTANCE; + float flBlendTime = gpGlobals->curtime - m_flIgnitionTime; + if ( flBlendTime > DOWNWARD_BLEND_TIME_START ) + { + if ( m_flReachedTargetTime != 0.0f ) + { + *pHomingSpeed = APC_HOMING_SPEED; + float flDeltaTime = clamp( gpGlobals->curtime - m_flReachedTargetTime, 0.0f, CORRECTION_TIME ); + *pHomingSpeed = SimpleSplineRemapVal( flDeltaTime, 0.0f, CORRECTION_TIME, 0.2f, *pHomingSpeed ); + flMinHomingDistance = SimpleSplineRemapVal( flDeltaTime, 0.0f, CORRECTION_TIME, MIN_NEAR_HOMING_DISTANCE, flMinHomingDistance ); + flMaxHomingDistance = SimpleSplineRemapVal( flDeltaTime, 0.0f, CORRECTION_TIME, MAX_NEAR_HOMING_DISTANCE, flMaxHomingDistance ); + } + else + { + flMinHomingDistance = MIN_NEAR_HOMING_DISTANCE; + flMaxHomingDistance = MAX_NEAR_HOMING_DISTANCE; + Vector vecDelta; + VectorSubtract( GetAbsOrigin(), *pActualDotPosition, vecDelta ); + if ( vecDelta.z > MIN_HEIGHT_DIFFERENCE ) + { + float flClampedHeight = clamp( vecDelta.z, MIN_HEIGHT_DIFFERENCE, MAX_HEIGHT_DIFFERENCE ); + float flHeightAdjustFactor = SimpleSplineRemapVal( flClampedHeight, MIN_HEIGHT_DIFFERENCE, MAX_HEIGHT_DIFFERENCE, 0.0f, 1.0f ); + + vecDelta.z = 0.0f; + float flDist = VectorNormalize( vecDelta ); + + float flForwardOffset = 2000.0f; + if ( flDist > flForwardOffset ) + { + Vector vecNewPosition; + VectorMA( GetAbsOrigin(), -flForwardOffset, vecDelta, vecNewPosition ); + vecNewPosition.z = pActualDotPosition->z; + + VectorLerp( *pActualDotPosition, vecNewPosition, flHeightAdjustFactor, *pActualDotPosition ); + } + } + else + { + m_flReachedTargetTime = gpGlobals->curtime; + } + } + + // Allows for players right at the edge of rocket range to be threatened + if ( flBlendTime > 0.6f ) + { + float flTargetLength = GetAbsOrigin().DistTo( pLaserTarget->WorldSpaceCenter() ); + flTargetLength = clamp( flTargetLength, flMinHomingDistance, flMaxHomingDistance ); + *pHomingSpeed = SimpleSplineRemapVal( flTargetLength, flMaxHomingDistance, flMinHomingDistance, *pHomingSpeed, 0.01f ); + } + } + + float flDot = DotProduct2D( vecTargetToShooter.AsVector2D(), vecTargetToMissile.AsVector2D() ); + if ( ( flDot < 0 ) || m_bGuidingDisabled ) + { + *pHomingSpeed = 0.0f; + } + + m_flLastHomingSpeed = *pHomingSpeed; + +// NDebugOverlay::Line( vecLeadPosition, GetAbsOrigin(), 0, 255, 0, true, 0.05f ); +// NDebugOverlay::Line( GetAbsOrigin(), *pActualDotPosition, 255, 0, 0, true, 0.05f ); +// NDebugOverlay::Cross3D( *pActualDotPosition, -Vector(4,4,4), Vector(4,4,4), 255, 0, 0, true, 0.05f ); +} + +#define RPG_BEAM_SPRITE "effects/laser1_noz.vmt" +#define RPG_LASER_SPRITE "sprites/redglow1.vmt" + +//============================================================================= +// RPG +//============================================================================= + +BEGIN_DATADESC( CWeaponRPG ) + + DEFINE_FIELD( m_bInitialStateUpdate,FIELD_BOOLEAN ), + DEFINE_FIELD( m_bGuiding, FIELD_BOOLEAN ), + DEFINE_FIELD( m_vecNPCLaserDot, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_hLaserDot, FIELD_EHANDLE ), + DEFINE_FIELD( m_hMissile, FIELD_EHANDLE ), + DEFINE_FIELD( m_hLaserMuzzleSprite, FIELD_EHANDLE ), + DEFINE_FIELD( m_hLaserBeam, FIELD_EHANDLE ), + DEFINE_FIELD( m_bHideGuiding, FIELD_BOOLEAN ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST(CWeaponRPG, DT_WeaponRPG) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( weapon_rpg, CWeaponRPG ); +PRECACHE_WEAPON_REGISTER(weapon_rpg); + +acttable_t CWeaponRPG::m_acttable[] = +{ + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_RPG, true }, + + { ACT_IDLE_RELAXED, ACT_IDLE_RPG_RELAXED, true }, + { ACT_IDLE_STIMULATED, ACT_IDLE_ANGRY_RPG, true }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_RPG, true }, + + { ACT_IDLE, ACT_IDLE_RPG, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_RPG, true }, + { ACT_WALK, ACT_WALK_RPG, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RPG, true }, + { ACT_RUN, ACT_RUN_RPG, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RPG, true }, + { ACT_COVER_LOW, ACT_COVER_LOW_RPG, true }, +}; + +IMPLEMENT_ACTTABLE(CWeaponRPG); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CWeaponRPG::CWeaponRPG() +{ + m_bReloadsSingly = true; + m_bInitialStateUpdate= false; + m_bHideGuiding = false; + m_bGuiding = false; + + m_fMinRange1 = m_fMinRange2 = 40*12; + m_fMaxRange1 = m_fMaxRange2 = 500*12; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CWeaponRPG::~CWeaponRPG() +{ + if ( m_hLaserDot != NULL ) + { + UTIL_Remove( m_hLaserDot ); + m_hLaserDot = NULL; + } + + if ( m_hLaserMuzzleSprite ) + { + UTIL_Remove( m_hLaserMuzzleSprite ); + } + + if ( m_hLaserBeam ) + { + UTIL_Remove( m_hLaserBeam ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::Precache( void ) +{ + BaseClass::Precache(); + + PrecacheScriptSound( "Missile.Ignite" ); + PrecacheScriptSound( "Missile.Accelerate" ); + + // Laser dot... + PrecacheModel( "sprites/redglow1.vmt" ); + PrecacheModel( RPG_LASER_SPRITE ); + PrecacheModel( RPG_BEAM_SPRITE ); + + UTIL_PrecacheOther( "rpg_missile" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::Activate( void ) +{ + BaseClass::Activate(); + + // Restore the laser pointer after transition + if ( m_bGuiding ) + { + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + if ( pOwner->GetActiveWeapon() == this ) + { + StartGuiding(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEvent - +// *pOperator - +//----------------------------------------------------------------------------- +void CWeaponRPG::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + switch( pEvent->event ) + { + case EVENT_WEAPON_SMG1: + { + if ( m_hMissile != NULL ) + return; + + Vector muzzlePoint; + QAngle vecAngles; + + muzzlePoint = GetOwner()->Weapon_ShootPosition(); + + CAI_BaseNPC *npc = pOperator->MyNPCPointer(); + ASSERT( npc != NULL ); + + Vector vecShootDir = npc->GetActualShootTrajectory( muzzlePoint ); + + // look for a better launch location + Vector altLaunchPoint; + if (GetAttachment( "missile", altLaunchPoint )) + { + // check to see if it's relativly free + trace_t tr; + AI_TraceHull( altLaunchPoint, altLaunchPoint + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, NULL, &tr ); + + if( tr.fraction == 1.0) + { + muzzlePoint = altLaunchPoint; + } + } + + VectorAngles( vecShootDir, vecAngles ); + + m_hMissile = CMissile::Create( muzzlePoint, vecAngles, GetOwner()->edict() ); + m_hMissile->m_hOwner = this; + + // NPCs always get a grace period + m_hMissile->SetGracePeriod( 0.5 ); + + pOperator->DoMuzzleFlash(); + + WeaponSound( SINGLE_NPC ); + + // Make sure our laserdot is off + m_bGuiding = false; + + if ( m_hLaserDot ) + { + m_hLaserDot->TurnOff(); + } + } + break; + + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponRPG::HasAnyAmmo( void ) +{ + if ( m_hMissile != NULL ) + return true; + + return BaseClass::HasAnyAmmo(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponRPG::WeaponShouldBeLowered( void ) +{ + // Lower us if we're out of ammo + if ( !HasAnyAmmo() ) + return true; + + return BaseClass::WeaponShouldBeLowered(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::PrimaryAttack( void ) +{ + // Can't have an active missile out + if ( m_hMissile != NULL ) + return; + + // Can't be reloading + if ( GetActivity() == ACT_VM_RELOAD ) + return; + + Vector vecOrigin; + Vector vecForward; + + m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + Vector vForward, vRight, vUp; + + pOwner->EyeVectors( &vForward, &vRight, &vUp ); + + Vector muzzlePoint = pOwner->Weapon_ShootPosition() + vForward * 12.0f + vRight * 6.0f + vUp * -3.0f; + + QAngle vecAngles; + VectorAngles( vForward, vecAngles ); + m_hMissile = CMissile::Create( muzzlePoint, vecAngles, GetOwner()->edict() ); + + m_hMissile->m_hOwner = this; + + // If the shot is clear to the player, give the missile a grace period + trace_t tr; + Vector vecEye = pOwner->EyePosition(); + UTIL_TraceLine( vecEye, vecEye + vForward * 128, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction == 1.0 ) + { + m_hMissile->SetGracePeriod( 0.3 ); + } + + DecrementAmmo( GetOwner() ); + + // Register a muzzleflash for the AI + pOwner->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 ); + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + WeaponSound( SINGLE ); + + pOwner->RumbleEffect( RUMBLE_SHOTGUN_SINGLE, 0, RUMBLE_FLAG_RESTART ); + + m_iPrimaryAttacks++; + gamestats->Event_WeaponFired( pOwner, true, GetClassname() ); + + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1000, 0.2, GetOwner(), SOUNDENT_CHANNEL_WEAPON ); + + // Check to see if we should trigger any RPG firing triggers + int iCount = g_hWeaponFireTriggers.Count(); + for ( int i = 0; i < iCount; i++ ) + { + if ( g_hWeaponFireTriggers[i]->IsTouching( pOwner ) ) + { + if ( FClassnameIs( g_hWeaponFireTriggers[i], "trigger_rpgfire" ) ) + { + g_hWeaponFireTriggers[i]->ActivateMultiTrigger( pOwner ); + } + } + } + + if( hl2_episodic.GetBool() ) + { + CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); + int nAIs = g_AI_Manager.NumAIs(); + + string_t iszStriderClassname = AllocPooledString( "npc_strider" ); + + for ( int i = 0; i < nAIs; i++ ) + { + if( ppAIs[ i ]->m_iClassname == iszStriderClassname ) + { + ppAIs[ i ]->DispatchInteraction( g_interactionPlayerLaunchedRPG, NULL, m_hMissile ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOwner - +//----------------------------------------------------------------------------- +void CWeaponRPG::DecrementAmmo( CBaseCombatCharacter *pOwner ) +{ + // Take away our primary ammo type + pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void CWeaponRPG::SuppressGuiding( bool state ) +{ + m_bHideGuiding = state; + + if ( m_hLaserDot == NULL ) + { + StartGuiding(); + + //STILL!? + if ( m_hLaserDot == NULL ) + return; + } + + if ( state ) + { + m_hLaserDot->TurnOff(); + } + else + { + m_hLaserDot->TurnOn(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Override this if we're guiding a missile currently +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponRPG::Lower( void ) +{ + if ( m_hMissile != NULL ) + return false; + + return BaseClass::Lower(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::ItemPostFrame( void ) +{ + BaseClass::ItemPostFrame(); + + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if ( pPlayer == NULL ) + return; + + //If we're pulling the weapon out for the first time, wait to draw the laser + if ( ( m_bInitialStateUpdate ) && ( GetActivity() != ACT_VM_DRAW ) ) + { + StartGuiding(); + m_bInitialStateUpdate = false; + } + + // Supress our guiding effects if we're lowered + if ( GetIdealActivity() == ACT_VM_IDLE_LOWERED || GetIdealActivity() == ACT_VM_RELOAD ) + { + SuppressGuiding(); + } + else + { + SuppressGuiding( false ); + } + + //Player has toggled guidance state + //Adrian: Players are not allowed to remove the laser guide in single player anymore, bye! + if ( g_pGameRules->IsMultiplayer() == true ) + { + if ( pPlayer->m_afButtonPressed & IN_ATTACK2 ) + { + ToggleGuiding(); + } + } + + //Move the laser + UpdateLaserPosition(); + UpdateLaserEffects(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Vector +//----------------------------------------------------------------------------- +Vector CWeaponRPG::GetLaserPosition( void ) +{ + CreateLaserPointer(); + + if ( m_hLaserDot != NULL ) + return m_hLaserDot->GetAbsOrigin(); + + //FIXME: The laser dot sprite is not active, this code should not be allowed! + assert(0); + return vec3_origin; +} + +//----------------------------------------------------------------------------- +// Purpose: NPC RPG users cheat and directly set the laser pointer's origin +// Input : &vecTarget - +//----------------------------------------------------------------------------- +void CWeaponRPG::UpdateNPCLaserPosition( const Vector &vecTarget ) +{ + CreateLaserPointer(); + // Turn the laserdot on + m_bGuiding = true; + m_hLaserDot->TurnOn(); + + Vector muzzlePoint = GetOwner()->Weapon_ShootPosition(); + Vector vecDir = (vecTarget - muzzlePoint); + VectorNormalize( vecDir ); + vecDir = muzzlePoint + ( vecDir * MAX_TRACE_LENGTH ); + UpdateLaserPosition( muzzlePoint, vecDir ); + + SetNPCLaserPosition( vecTarget ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::SetNPCLaserPosition( const Vector &vecTarget ) +{ + m_vecNPCLaserDot = vecTarget; + //NDebugOverlay::Box( m_vecNPCLaserDot, -Vector(10,10,10), Vector(10,10,10), 255,0,0, 8, 3 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const Vector &CWeaponRPG::GetNPCLaserPosition( void ) +{ + return m_vecNPCLaserDot; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true if the rocket is being guided, false if it's dumb +//----------------------------------------------------------------------------- +bool CWeaponRPG::IsGuiding( void ) +{ + return m_bGuiding; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponRPG::Deploy( void ) +{ + m_bInitialStateUpdate = true; + + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponRPG::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + //Can't have an active missile out + if ( m_hMissile != NULL ) + return false; + + StopGuiding(); + return BaseClass::Holster( pSwitchingTo ); +} + +//----------------------------------------------------------------------------- +// Purpose: Turn on the guiding laser +//----------------------------------------------------------------------------- +void CWeaponRPG::StartGuiding( void ) +{ + // Don't start back up if we're overriding this + if ( m_bHideGuiding ) + return; + + m_bGuiding = true; + + WeaponSound(SPECIAL1); + + CreateLaserPointer(); + StartLaserEffects(); +} + +//----------------------------------------------------------------------------- +// Purpose: Turn off the guiding laser +//----------------------------------------------------------------------------- +void CWeaponRPG::StopGuiding( void ) +{ + m_bGuiding = false; + + WeaponSound( SPECIAL2 ); + + StopLaserEffects(); + + // Kill the dot completely + if ( m_hLaserDot != NULL ) + { + m_hLaserDot->TurnOff(); + UTIL_Remove( m_hLaserDot ); + m_hLaserDot = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle the guiding laser +//----------------------------------------------------------------------------- +void CWeaponRPG::ToggleGuiding( void ) +{ + if ( IsGuiding() ) + { + StopGuiding(); + } + else + { + StartGuiding(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::Drop( const Vector &vecVelocity ) +{ + StopGuiding(); + + BaseClass::Drop( vecVelocity ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::UpdateLaserPosition( Vector vecMuzzlePos, Vector vecEndPos ) +{ + if ( vecMuzzlePos == vec3_origin || vecEndPos == vec3_origin ) + { + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if ( !pPlayer ) + return; + + vecMuzzlePos = pPlayer->Weapon_ShootPosition(); + Vector forward; + + if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE ) + { + forward = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DEFAULT ); + } + else + { + pPlayer->EyeVectors( &forward ); + } + + vecEndPos = vecMuzzlePos + ( forward * MAX_TRACE_LENGTH ); + } + + //Move the laser dot, if active + trace_t tr; + + // Trace out for the endpoint +#ifdef PORTAL + g_bBulletPortalTrace = true; + Ray_t rayLaser; + rayLaser.Init( vecMuzzlePos, vecEndPos ); + UTIL_Portal_TraceRay( rayLaser, (MASK_SHOT & ~CONTENTS_WINDOW), this, COLLISION_GROUP_NONE, &tr ); + g_bBulletPortalTrace = false; +#else + UTIL_TraceLine( vecMuzzlePos, vecEndPos, (MASK_SHOT & ~CONTENTS_WINDOW), this, COLLISION_GROUP_NONE, &tr ); +#endif + + // Move the laser sprite + if ( m_hLaserDot != NULL ) + { + Vector laserPos = tr.endpos; + m_hLaserDot->SetLaserPosition( laserPos, tr.plane.normal ); + + if ( tr.DidHitNonWorldEntity() ) + { + CBaseEntity *pHit = tr.m_pEnt; + + if ( ( pHit != NULL ) && ( pHit->m_takedamage ) ) + { + m_hLaserDot->SetTargetEntity( pHit ); + } + else + { + m_hLaserDot->SetTargetEntity( NULL ); + } + } + else + { + m_hLaserDot->SetTargetEntity( NULL ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::CreateLaserPointer( void ) +{ + if ( m_hLaserDot != NULL ) + return; + + m_hLaserDot = CLaserDot::Create( GetAbsOrigin(), GetOwnerEntity() ); + m_hLaserDot->TurnOff(); + + UpdateLaserPosition(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::NotifyRocketDied( void ) +{ + m_hMissile = NULL; + + Reload(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponRPG::Reload( void ) +{ + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( pOwner == NULL ) + return false; + + if ( pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0 ) + return false; + + WeaponSound( RELOAD ); + + SendWeaponAnim( ACT_VM_RELOAD ); + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CWeaponRPG::WeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions ) +{ + bool bResult = BaseClass::WeaponLOSCondition( ownerPos, targetPos, bSetConditions ); + + if( bResult ) + { + CAI_BaseNPC* npcOwner = GetOwner()->MyNPCPointer(); + + if( npcOwner ) + { + trace_t tr; + + Vector vecRelativeShootPosition; + VectorSubtract( npcOwner->Weapon_ShootPosition(), npcOwner->GetAbsOrigin(), vecRelativeShootPosition ); + Vector vecMuzzle = ownerPos + vecRelativeShootPosition; + Vector vecShootDir = npcOwner->GetActualShootTrajectory( vecMuzzle ); + + // Make sure I have a good 10 feet of wide clearance in front, or I'll blow my teeth out. + AI_TraceHull( vecMuzzle, vecMuzzle + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, NULL, &tr ); + + if( tr.fraction != 1.0f ) + bResult = false; + } + } + + return bResult; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flDot - +// flDist - +// Output : int +//----------------------------------------------------------------------------- +int CWeaponRPG::WeaponRangeAttack1Condition( float flDot, float flDist ) +{ + if ( m_hMissile != NULL ) + return 0; + + // Ignore vertical distance when doing our RPG distance calculations + CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); + if ( pNPC ) + { + CBaseEntity *pEnemy = pNPC->GetEnemy(); + Vector vecToTarget = (pEnemy->GetAbsOrigin() - pNPC->GetAbsOrigin()); + vecToTarget.z = 0; + flDist = vecToTarget.Length(); + } + + if ( flDist < MIN( m_fMinRange1, m_fMinRange2 ) ) + return COND_TOO_CLOSE_TO_ATTACK; + + if ( m_flNextPrimaryAttack > gpGlobals->curtime ) + return 0; + + // See if there's anyone in the way! + CAI_BaseNPC *pOwner = GetOwner()->MyNPCPointer(); + ASSERT( pOwner != NULL ); + + if( pOwner ) + { + // Make sure I don't shoot the world! + trace_t tr; + + Vector vecMuzzle = pOwner->Weapon_ShootPosition(); + Vector vecShootDir = pOwner->GetActualShootTrajectory( vecMuzzle ); + + // Make sure I have a good 10 feet of wide clearance in front, or I'll blow my teeth out. + AI_TraceHull( vecMuzzle, vecMuzzle + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, NULL, &tr ); + + if( tr.fraction != 1.0 ) + { + return COND_WEAPON_SIGHT_OCCLUDED; + } + } + + return COND_CAN_RANGE_ATTACK1; +} + +//----------------------------------------------------------------------------- +// Purpose: Start the effects on the viewmodel of the RPG +//----------------------------------------------------------------------------- +void CWeaponRPG::StartLaserEffects( void ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner == NULL ) + return; + + CBaseViewModel *pBeamEnt = static_cast<CBaseViewModel *>(pOwner->GetViewModel()); + + if ( m_hLaserBeam == NULL ) + { + m_hLaserBeam = CBeam::BeamCreate( RPG_BEAM_SPRITE, 1.0f ); + + if ( m_hLaserBeam == NULL ) + { + // We were unable to create the beam + Assert(0); + return; + } + + m_hLaserBeam->EntsInit( pBeamEnt, pBeamEnt ); + + int startAttachment = LookupAttachment( "laser" ); + int endAttachment = LookupAttachment( "laser_end" ); + + m_hLaserBeam->FollowEntity( pBeamEnt ); + m_hLaserBeam->SetStartAttachment( startAttachment ); + m_hLaserBeam->SetEndAttachment( endAttachment ); + m_hLaserBeam->SetNoise( 0 ); + m_hLaserBeam->SetColor( 255, 0, 0 ); + m_hLaserBeam->SetScrollRate( 0 ); + m_hLaserBeam->SetWidth( 0.5f ); + m_hLaserBeam->SetEndWidth( 0.5f ); + m_hLaserBeam->SetBrightness( 128 ); + m_hLaserBeam->SetBeamFlags( SF_BEAM_SHADEIN ); +#ifdef PORTAL + m_hLaserBeam->m_bDrawInMainRender = true; + m_hLaserBeam->m_bDrawInPortalRender = false; +#endif + } + else + { + m_hLaserBeam->SetBrightness( 128 ); + } + + if ( m_hLaserMuzzleSprite == NULL ) + { + m_hLaserMuzzleSprite = CSprite::SpriteCreate( RPG_LASER_SPRITE, GetAbsOrigin(), false ); + + if ( m_hLaserMuzzleSprite == NULL ) + { + // We were unable to create the sprite + Assert(0); + return; + } + +#ifdef PORTAL + m_hLaserMuzzleSprite->m_bDrawInMainRender = true; + m_hLaserMuzzleSprite->m_bDrawInPortalRender = false; +#endif + + m_hLaserMuzzleSprite->SetAttachment( pOwner->GetViewModel(), LookupAttachment( "laser" ) ); + m_hLaserMuzzleSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_hLaserMuzzleSprite->SetBrightness( 255, 0.5f ); + m_hLaserMuzzleSprite->SetScale( 0.25f, 0.5f ); + m_hLaserMuzzleSprite->TurnOn(); + } + else + { + m_hLaserMuzzleSprite->TurnOn(); + m_hLaserMuzzleSprite->SetScale( 0.25f, 0.25f ); + m_hLaserMuzzleSprite->SetBrightness( 255 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Stop the effects on the viewmodel of the RPG +//----------------------------------------------------------------------------- +void CWeaponRPG::StopLaserEffects( void ) +{ + if ( m_hLaserBeam != NULL ) + { + m_hLaserBeam->SetBrightness( 0 ); + } + + if ( m_hLaserMuzzleSprite != NULL ) + { + m_hLaserMuzzleSprite->SetScale( 0.01f ); + m_hLaserMuzzleSprite->SetBrightness( 0, 0.5f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Pulse all the effects to make them more... well, laser-like +//----------------------------------------------------------------------------- +void CWeaponRPG::UpdateLaserEffects( void ) +{ + if ( !m_bGuiding ) + return; + + if ( m_hLaserBeam != NULL ) + { + m_hLaserBeam->SetBrightness( 128 + random->RandomInt( -8, 8 ) ); + } + + if ( m_hLaserMuzzleSprite != NULL ) + { + m_hLaserMuzzleSprite->SetScale( 0.1f + random->RandomFloat( -0.025f, 0.025f ) ); + } +} + +//============================================================================= +// Laser Dot +//============================================================================= + +LINK_ENTITY_TO_CLASS( env_laserdot, CLaserDot ); + +BEGIN_DATADESC( CLaserDot ) + DEFINE_FIELD( m_vecSurfaceNormal, FIELD_VECTOR ), + DEFINE_FIELD( m_hTargetEnt, FIELD_EHANDLE ), + DEFINE_FIELD( m_bVisibleLaserDot, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bIsOn, FIELD_BOOLEAN ), + + //DEFINE_FIELD( m_pNext, FIELD_CLASSPTR ), // don't save - regenerated by constructor + DEFINE_THINKFUNC( LaserThink ), +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Finds missiles in cone +//----------------------------------------------------------------------------- +CBaseEntity *CreateLaserDot( const Vector &origin, CBaseEntity *pOwner, bool bVisibleDot ) +{ + return CLaserDot::Create( origin, pOwner, bVisibleDot ); +} + +void SetLaserDotTarget( CBaseEntity *pLaserDot, CBaseEntity *pTarget ) +{ + CLaserDot *pDot = assert_cast< CLaserDot* >(pLaserDot ); + pDot->SetTargetEntity( pTarget ); +} + +void EnableLaserDot( CBaseEntity *pLaserDot, bool bEnable ) +{ + CLaserDot *pDot = assert_cast< CLaserDot* >(pLaserDot ); + if ( bEnable ) + { + pDot->TurnOn(); + } + else + { + pDot->TurnOff(); + } +} + +CLaserDot::CLaserDot( void ) +{ + m_hTargetEnt = NULL; + m_bIsOn = true; + g_LaserDotList.Insert( this ); +} + +CLaserDot::~CLaserDot( void ) +{ + g_LaserDotList.Remove( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// Output : CLaserDot +//----------------------------------------------------------------------------- +CLaserDot *CLaserDot::Create( const Vector &origin, CBaseEntity *pOwner, bool bVisibleDot ) +{ + CLaserDot *pLaserDot = (CLaserDot *) CBaseEntity::Create( "env_laserdot", origin, QAngle(0,0,0) ); + + if ( pLaserDot == NULL ) + return NULL; + + pLaserDot->m_bVisibleLaserDot = bVisibleDot; + pLaserDot->SetMoveType( MOVETYPE_NONE ); + pLaserDot->AddSolidFlags( FSOLID_NOT_SOLID ); + pLaserDot->AddEffects( EF_NOSHADOW ); + UTIL_SetSize( pLaserDot, vec3_origin, vec3_origin ); + + //Create the graphic + pLaserDot->SpriteInit( "sprites/redglow1.vmt", origin ); + + pLaserDot->SetName( AllocPooledString("TEST") ); + + pLaserDot->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + pLaserDot->SetScale( 0.5f ); + + pLaserDot->SetOwnerEntity( pOwner ); + + pLaserDot->SetContextThink( &CLaserDot::LaserThink, gpGlobals->curtime + 0.1f, g_pLaserDotThink ); + pLaserDot->SetSimulatedEveryTick( true ); + + if ( !bVisibleDot ) + { + pLaserDot->MakeInvisible(); + } + + return pLaserDot; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLaserDot::LaserThink( void ) +{ + SetNextThink( gpGlobals->curtime + 0.05f, g_pLaserDotThink ); + + if ( GetOwnerEntity() == NULL ) + return; + + Vector viewDir = GetAbsOrigin() - GetOwnerEntity()->GetAbsOrigin(); + float dist = VectorNormalize( viewDir ); + + float scale = RemapVal( dist, 32, 1024, 0.01f, 0.5f ); + float scaleOffs = random->RandomFloat( -scale * 0.25f, scale * 0.25f ); + + scale = clamp( scale + scaleOffs, 0.1f, 32.0f ); + + SetScale( scale ); +} + +void CLaserDot::SetLaserPosition( const Vector &origin, const Vector &normal ) +{ + SetAbsOrigin( origin ); + m_vecSurfaceNormal = normal; +} + +Vector CLaserDot::GetChasePosition() +{ + return GetAbsOrigin() - m_vecSurfaceNormal * 10; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLaserDot::TurnOn( void ) +{ + m_bIsOn = true; + if ( m_bVisibleLaserDot ) + { + BaseClass::TurnOn(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLaserDot::TurnOff( void ) +{ + m_bIsOn = false; + if ( m_bVisibleLaserDot ) + { + BaseClass::TurnOff(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLaserDot::MakeInvisible( void ) +{ + BaseClass::TurnOff(); +} |