//========= 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 g_LaserDotList; template <> CLaserDot *CEntityClassList::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::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 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 g_APCMissileList; template <> CAPCMissile *CEntityClassList::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(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(); }