|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "npcevent.h"
#include "in_buttons.h"
#include "weapon_rpg.h"
#ifdef CLIENT_DLL
#include "c_hl2mp_player.h"
#include "model_types.h"
#include "beamdraw.h"
#include "fx_line.h"
#include "view.h"
#else
#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 "shake.h"
#include "te_effect_dispatch.h"
#include "triggers.h"
#include "smoke_trail.h"
#include "collisionutils.h"
#include "hl2_shareddefs.h"
#endif
#include "debugoverlay_shared.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define RPG_SPEED 1500
#ifndef CLIENT_DLL
const char *g_pLaserDotThink = "LaserThinkContext";
static ConVar sk_apc_missile_damage("sk_apc_missile_damage", "15"); #define APC_MISSILE_DAMAGE sk_apc_missile_damage.GetFloat()
#endif
#ifdef CLIENT_DLL
#define CLaserDot C_LaserDot
#endif
//-----------------------------------------------------------------------------
// Laser Dot
//-----------------------------------------------------------------------------
class CLaserDot : public CBaseEntity { DECLARE_CLASS( CLaserDot, CBaseEntity ); 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 );
int ObjectCaps() { return (BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; }
void MakeInvisible( void );
#ifdef CLIENT_DLL
virtual bool IsTransparent( void ) { return true; } virtual RenderGroup_t GetRenderGroup( void ) { return RENDER_GROUP_TRANSLUCENT_ENTITY; } virtual int DrawModel( int flags ); virtual void OnDataChanged( DataUpdateType_t updateType ); virtual bool ShouldDraw( void ) { return (IsEffectActive(EF_NODRAW)==false); }
CMaterialReference m_hSpriteMaterial; #endif
protected: Vector m_vecSurfaceNormal; EHANDLE m_hTargetEnt; bool m_bVisibleLaserDot; bool m_bIsOn;
DECLARE_NETWORKCLASS(); DECLARE_DATADESC(); public: CLaserDot *m_pNext; };
IMPLEMENT_NETWORKCLASS_ALIASED( LaserDot, DT_LaserDot )
BEGIN_NETWORK_TABLE( CLaserDot, DT_LaserDot ) END_NETWORK_TABLE()
#ifndef CLIENT_DLL
// 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 ), // 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; }
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 );
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(), GetDamage() * 2, 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 ) 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() );
color32 white = { 255,225,205,64 }; UTIL_ScreenFade( pPlayer, white, 0.1f, 0.0f, FFADE_IN ); }
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 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 );
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 ); }
//-----------------------------------------------------------------------------
// 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; }
//-----------------------------------------------------------------------------
// 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_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; }
//-----------------------------------------------------------------------------
// 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::SeekThink ); SetNextThink( gpGlobals->curtime ); }
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 { ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), APC_MISSILE_DAMAGE, 100, true, 20000 ); } }
//-----------------------------------------------------------------------------
// 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 );
}
#endif
#define RPG_BEAM_SPRITE "effects/laser1.vmt"
#define RPG_BEAM_SPRITE_NOZ "effects/laser1_noz.vmt"
#define RPG_LASER_SPRITE "sprites/redglow1"
//=============================================================================
// RPG
//=============================================================================
LINK_ENTITY_TO_CLASS( weapon_rpg, CWeaponRPG ); PRECACHE_WEAPON_REGISTER(weapon_rpg);
IMPLEMENT_NETWORKCLASS_ALIASED( WeaponRPG, DT_WeaponRPG )
#ifdef CLIENT_DLL
void RecvProxy_MissileDied( const CRecvProxyData *pData, void *pStruct, void *pOut ) { CWeaponRPG *pRPG = ((CWeaponRPG*)pStruct);
RecvProxy_IntToEHandle( pData, pStruct, pOut );
CBaseEntity *pNewMissile = pRPG->GetMissile();
if ( pNewMissile == NULL ) { if ( pRPG->GetOwner() && pRPG->GetOwner()->GetActiveWeapon() == pRPG ) { pRPG->NotifyRocketDied(); } } }
#endif
BEGIN_NETWORK_TABLE( CWeaponRPG, DT_WeaponRPG ) #ifdef CLIENT_DLL
RecvPropBool( RECVINFO( m_bInitialStateUpdate ) ), RecvPropBool( RECVINFO( m_bGuiding ) ), RecvPropBool( RECVINFO( m_bHideGuiding ) ), RecvPropEHandle( RECVINFO( m_hMissile ), RecvProxy_MissileDied ), RecvPropVector( RECVINFO( m_vecLaserDot ) ), #else
SendPropBool( SENDINFO( m_bInitialStateUpdate ) ), SendPropBool( SENDINFO( m_bGuiding ) ), SendPropBool( SENDINFO( m_bHideGuiding ) ), SendPropEHandle( SENDINFO( m_hMissile ) ), SendPropVector( SENDINFO( m_vecLaserDot ) ), #endif
END_NETWORK_TABLE()
#ifdef CLIENT_DLL
BEGIN_PREDICTION_DATA( CWeaponRPG ) DEFINE_PRED_FIELD( m_bInitialStateUpdate, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bGuiding, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bHideGuiding, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), END_PREDICTION_DATA()
#endif
#ifndef CLIENT_DLL
acttable_t CWeaponRPG::m_acttable[] = { { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_RPG, false }, { ACT_HL2MP_RUN, ACT_HL2MP_RUN_RPG, false }, { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_RPG, false }, { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_RPG, false }, { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_RPG, false }, { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_RPG, false }, { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_RPG, false }, { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_RPG, false }, };
IMPLEMENT_ACTTABLE(CWeaponRPG);
#endif
//-----------------------------------------------------------------------------
// 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() { #ifndef CLIENT_DLL
if ( m_hLaserDot != NULL ) { UTIL_Remove( m_hLaserDot ); m_hLaserDot = NULL; } #endif
}
//-----------------------------------------------------------------------------
// 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 ); PrecacheModel( RPG_BEAM_SPRITE_NOZ );
#ifndef CLIENT_DLL
UTIL_PrecacheOther( "rpg_missile" ); #endif
}
//-----------------------------------------------------------------------------
// 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:
//-----------------------------------------------------------------------------
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 ) { // Only the player fires this way so we can cast
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
if (!pPlayer) return;
// 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;
#ifndef CLIENT_DLL
QAngle vecAngles; VectorAngles( vForward, vecAngles );
CMissile *pMissile = CMissile::Create( muzzlePoint, vecAngles, GetOwner()->edict() ); pMissile->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 ) { pMissile->SetGracePeriod( 0.3 ); }
pMissile->SetDamage( GetHL2MPWpnData().m_iPlayerDamage );
m_hMissile = pMissile; #endif
DecrementAmmo( GetOwner() ); SendWeaponAnim( ACT_VM_PRIMARYATTACK ); WeaponSound( SINGLE );
// player "shoot" animation
pPlayer->SetAnimation( PLAYER_ATTACK1 ); }
//-----------------------------------------------------------------------------
// 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;
#ifndef CLIENT_DLL
if ( m_hLaserDot == NULL ) { StartGuiding();
//STILL!?
if ( m_hLaserDot == NULL ) return; }
if ( state ) { m_hLaserDot->TurnOff(); } else { m_hLaserDot->TurnOn(); } #endif
}
//-----------------------------------------------------------------------------
// 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 ) { SuppressGuiding(); } else { SuppressGuiding( false ); }
//Move the laser
UpdateLaserPosition();
if ( pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0 && m_hMissile == NULL ) { StopGuiding(); } }
//-----------------------------------------------------------------------------
// Purpose:
// Output : Vector
//-----------------------------------------------------------------------------
Vector CWeaponRPG::GetLaserPosition( void ) { #ifndef CLIENT_DLL
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); #endif
return vec3_origin; }
//-----------------------------------------------------------------------------
// Purpose: NPC RPG users cheat and directly set the laser pointer's origin
// Input : &vecTarget -
//-----------------------------------------------------------------------------
void CWeaponRPG::UpdateNPCLaserPosition( const Vector &vecTarget ) {
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponRPG::SetNPCLaserPosition( const Vector &vecTarget ) { }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const Vector &CWeaponRPG::GetNPCLaserPosition( void ) { return vec3_origin; }
//-----------------------------------------------------------------------------
// 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(); }
bool CWeaponRPG::CanHolster( void ) { //Can't have an active missile out
if ( m_hMissile != NULL ) return false;
return BaseClass::CanHolster(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CWeaponRPG::Holster( CBaseCombatWeapon *pSwitchingTo ) { 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;
#ifndef CLIENT_DLL
WeaponSound(SPECIAL1);
CreateLaserPointer(); #endif
}
//-----------------------------------------------------------------------------
// Purpose: Turn off the guiding laser
//-----------------------------------------------------------------------------
void CWeaponRPG::StopGuiding( void ) { m_bGuiding = false;
#ifndef CLIENT_DLL
WeaponSound( SPECIAL2 );
// Kill the dot completely
if ( m_hLaserDot != NULL ) { m_hLaserDot->TurnOff(); UTIL_Remove( m_hLaserDot ); m_hLaserDot = NULL; } #else
if ( m_pBeam ) { //Tell it to die right away and let the beam code free it.
m_pBeam->brightness = 0.0f; m_pBeam->flags &= ~FBEAM_FOREVER; m_pBeam->die = gpGlobals->curtime - 0.1; m_pBeam = NULL; } #endif
}
//-----------------------------------------------------------------------------
// 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 ) {
#ifndef CLIENT_DLL
if ( vecMuzzlePos == vec3_origin || vecEndPos == vec3_origin ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if ( !pPlayer ) return;
vecMuzzlePos = pPlayer->Weapon_ShootPosition(); Vector forward; pPlayer->EyeVectors( &forward ); vecEndPos = vecMuzzlePos + ( forward * MAX_TRACE_LENGTH ); }
//Move the laser dot, if active
trace_t tr; // Trace out for the endpoint
UTIL_TraceLine( vecMuzzlePos, vecEndPos, (MASK_SHOT & ~CONTENTS_WINDOW), GetOwner(), COLLISION_GROUP_NONE, &tr );
// 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 ); } } #endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponRPG::CreateLaserPointer( void ) { #ifndef CLIENT_DLL
if ( m_hLaserDot != NULL ) return;
CBaseCombatCharacter *pOwner = GetOwner(); if ( pOwner == NULL ) return;
if ( pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0 ) return;
m_hLaserDot = CLaserDot::Create( GetAbsOrigin(), GetOwner() ); m_hLaserDot->TurnOff();
UpdateLaserPosition(); #endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponRPG::NotifyRocketDied( void ) { m_hMissile = NULL;
if ( GetActivity() == ACT_VM_RELOAD ) return;
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; }
#ifdef CLIENT_DLL
#define RPG_MUZZLE_ATTACHMENT 1
#define RPG_GUIDE_ATTACHMENT 2
#define RPG_GUIDE_TARGET_ATTACHMENT 3
#define RPG_GUIDE_ATTACHMENT_3RD 4
#define RPG_GUIDE_TARGET_ATTACHMENT_3RD 5
#define RPG_LASER_BEAM_LENGTH 128
extern void FormatViewModelAttachment( Vector &vOrigin, bool bInverse );
//-----------------------------------------------------------------------------
// Purpose: Returns the attachment point on either the world or viewmodel
// This should really be worked into the CBaseCombatWeapon class!
//-----------------------------------------------------------------------------
void CWeaponRPG::GetWeaponAttachment( int attachmentId, Vector &outVector, Vector *dir /*= NULL*/ ) { QAngle angles;
if ( ShouldDrawUsingViewModel() ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner != NULL ) { pOwner->GetViewModel()->GetAttachment( attachmentId, outVector, angles ); ::FormatViewModelAttachment( outVector, true ); } } else { // We offset the IDs to make them correct for our world model
BaseClass::GetAttachment( attachmentId, outVector, angles ); }
// Supply the direction, if requested
if ( dir != NULL ) { AngleVectors( angles, dir, NULL, NULL ); } }
//-----------------------------------------------------------------------------
// Purpose: Setup our laser beam
//-----------------------------------------------------------------------------
void CWeaponRPG::InitBeam( void ) { if ( m_pBeam != NULL ) return;
CBaseCombatCharacter *pOwner = GetOwner(); if ( pOwner == NULL ) return;
if ( pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0 ) return;
BeamInfo_t beamInfo;
CBaseEntity *pEntity = NULL;
if ( ShouldDrawUsingViewModel() ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner != NULL ) { pEntity = pOwner->GetViewModel(); } } else { pEntity = this; }
beamInfo.m_pStartEnt = pEntity; beamInfo.m_pEndEnt = pEntity; beamInfo.m_nType = TE_BEAMPOINTS; beamInfo.m_vecStart = vec3_origin; beamInfo.m_vecEnd = vec3_origin; beamInfo.m_pszModelName = ( ShouldDrawUsingViewModel() ) ? RPG_BEAM_SPRITE_NOZ : RPG_BEAM_SPRITE; beamInfo.m_flHaloScale = 0.0f; beamInfo.m_flLife = 0.0f; if ( ShouldDrawUsingViewModel() ) { beamInfo.m_flWidth = 2.0f; beamInfo.m_flEndWidth = 2.0f; beamInfo.m_nStartAttachment = RPG_GUIDE_ATTACHMENT; beamInfo.m_nEndAttachment = RPG_GUIDE_TARGET_ATTACHMENT; } else { beamInfo.m_flWidth = 1.0f; beamInfo.m_flEndWidth = 1.0f; beamInfo.m_nStartAttachment = RPG_GUIDE_ATTACHMENT_3RD; beamInfo.m_nEndAttachment = RPG_GUIDE_TARGET_ATTACHMENT_3RD; }
beamInfo.m_flFadeLength = 0.0f; beamInfo.m_flAmplitude = 0; beamInfo.m_flBrightness = 255.0; beamInfo.m_flSpeed = 1.0f; beamInfo.m_nStartFrame = 0.0; beamInfo.m_flFrameRate = 30.0; beamInfo.m_flRed = 255.0; beamInfo.m_flGreen = 0.0; beamInfo.m_flBlue = 0.0; beamInfo.m_nSegments = 4; beamInfo.m_bRenderable = true; beamInfo.m_nFlags = (FBEAM_FOREVER|FBEAM_SHADEOUT);
m_pBeam = beams->CreateBeamEntPoint( beamInfo ); }
//-----------------------------------------------------------------------------
// Purpose: Draw effects for our weapon
//-----------------------------------------------------------------------------
void CWeaponRPG::DrawEffects( void ) { // Must be guiding and not hidden
if ( !m_bGuiding || m_bHideGuiding ) { if ( m_pBeam != NULL ) { m_pBeam->brightness = 0; }
return; }
// Setup our sprite
if ( m_hSpriteMaterial == NULL ) { m_hSpriteMaterial.Init( RPG_LASER_SPRITE, TEXTURE_GROUP_CLIENT_EFFECTS ); }
// Setup our beam
if ( m_hBeamMaterial == NULL ) { m_hBeamMaterial.Init( RPG_BEAM_SPRITE, TEXTURE_GROUP_CLIENT_EFFECTS ); }
color32 color={255,255,255,255}; Vector vecAttachment, vecDir; QAngle angles;
float scale = 8.0f + random->RandomFloat( -2.0f, 2.0f );
int attachmentID = ( ShouldDrawUsingViewModel() ) ? RPG_GUIDE_ATTACHMENT : RPG_GUIDE_ATTACHMENT_3RD;
GetWeaponAttachment( attachmentID, vecAttachment, &vecDir );
// Draw the sprite
CMatRenderContextPtr pRenderContext( materials ); pRenderContext->Bind( m_hSpriteMaterial, this ); DrawSprite( vecAttachment, scale, scale, color ); // Get the beam's run
trace_t tr; UTIL_TraceLine( vecAttachment, vecAttachment + ( vecDir * RPG_LASER_BEAM_LENGTH ), MASK_SHOT, GetOwner(), COLLISION_GROUP_NONE, &tr ); InitBeam();
if ( m_pBeam != NULL ) { m_pBeam->fadeLength = RPG_LASER_BEAM_LENGTH * tr.fraction; m_pBeam->brightness = random->RandomInt( 128, 200 ); } }
//-----------------------------------------------------------------------------
// Purpose: Called on third-person weapon drawing
//-----------------------------------------------------------------------------
int CWeaponRPG::DrawModel( int flags ) { // Only render these on the transparent pass
if ( flags & STUDIO_TRANSPARENCY ) { DrawEffects(); return 1; }
// Draw the model as normal
return BaseClass::DrawModel( flags ); }
//-----------------------------------------------------------------------------
// Purpose: Called after first-person viewmodel is drawn
//-----------------------------------------------------------------------------
void CWeaponRPG::ViewModelDrawn( C_BaseViewModel *pBaseViewModel ) { // Draw our laser effects
DrawEffects(); BaseClass::ViewModelDrawn( pBaseViewModel ); }
//-----------------------------------------------------------------------------
// Purpose: Used to determine sorting of model when drawn
//-----------------------------------------------------------------------------
bool CWeaponRPG::IsTranslucent( void ) { // Must be guiding and not hidden
if ( m_bGuiding && !m_bHideGuiding ) return true;
return false; }
//-----------------------------------------------------------------------------
// Purpose: Turns off effects when leaving the PVS
//-----------------------------------------------------------------------------
void CWeaponRPG::NotifyShouldTransmit( ShouldTransmitState_t state ) { BaseClass::NotifyShouldTransmit(state);
if ( state == SHOULDTRANSMIT_END ) { if ( m_pBeam != NULL ) { m_pBeam->brightness = 0.0f; } } }
#endif //CLIENT_DLL
//=============================================================================
// 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
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; #ifndef CLIENT_DLL
g_LaserDotList.Insert( this ); #endif
}
CLaserDot::~CLaserDot( void ) { #ifndef CLIENT_DLL
g_LaserDotList.Remove( this ); #endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &origin -
// Output : CLaserDot
//-----------------------------------------------------------------------------
CLaserDot *CLaserDot::Create( const Vector &origin, CBaseEntity *pOwner, bool bVisibleDot ) { #ifndef CLIENT_DLL
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, -Vector(4,4,4), Vector(4,4,4) );
pLaserDot->SetOwnerEntity( pOwner );
pLaserDot->AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
if ( !bVisibleDot ) { pLaserDot->MakeInvisible(); }
return pLaserDot; #else
return NULL; #endif
}
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 ) { }
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: Draw our sprite
//-----------------------------------------------------------------------------
int CLaserDot::DrawModel( int flags ) { color32 color={255,255,255,255}; Vector vecAttachment, vecDir; QAngle angles;
float scale; Vector endPos;
C_HL2MP_Player *pOwner = ToHL2MPPlayer( GetOwnerEntity() );
if ( pOwner != NULL && pOwner->IsDormant() == false ) { // Always draw the dot in front of our faces when in first-person
if ( pOwner->IsLocalPlayer() ) { // Take our view position and orientation
vecAttachment = CurrentViewOrigin(); vecDir = CurrentViewForward(); } else { // Take the eye position and direction
vecAttachment = pOwner->EyePosition(); QAngle angles = pOwner->GetAnimEyeAngles(); AngleVectors( angles, &vecDir ); } trace_t tr; UTIL_TraceLine( vecAttachment, vecAttachment + ( vecDir * MAX_TRACE_LENGTH ), MASK_SHOT, pOwner, COLLISION_GROUP_NONE, &tr ); // Backup off the hit plane
endPos = tr.endpos + ( tr.plane.normal * 4.0f ); } else { // Just use our position if we can't predict it otherwise
endPos = GetAbsOrigin(); }
// Randomly flutter
scale = 16.0f + random->RandomFloat( -4.0f, 4.0f );
// Draw our laser dot in space
CMatRenderContextPtr pRenderContext( materials ); pRenderContext->Bind( m_hSpriteMaterial, this ); DrawSprite( endPos, scale, scale, color );
return 1; }
//-----------------------------------------------------------------------------
// Purpose: Setup our sprite reference
//-----------------------------------------------------------------------------
void CLaserDot::OnDataChanged( DataUpdateType_t updateType ) { if ( updateType == DATA_UPDATE_CREATED ) { m_hSpriteMaterial.Init( RPG_LASER_SPRITE, TEXTURE_GROUP_CLIENT_EFFECTS ); } }
#endif //CLIENT_DLL
|