Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2377 lines
65 KiB

5 years ago
//========= 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();
}