You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
497 lines
12 KiB
497 lines
12 KiB
5 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
|
||
|
#include "cbase.h"
|
||
|
#include "ai_basenpc.h"
|
||
|
#include "ai_hull.h"
|
||
|
#include "ai_senses.h"
|
||
|
#include "ai_memory.h"
|
||
|
#include "soundent.h"
|
||
|
#include "smoke_trail.h"
|
||
|
#include "weapon_rpg.h"
|
||
|
#include "gib.h"
|
||
|
#include "ndebugoverlay.h"
|
||
|
#include "IEffects.h"
|
||
|
#include "vstdlib/random.h"
|
||
|
#include "engine/IEngineSound.h"
|
||
|
#include "ammodef.h"
|
||
|
#include "hl2_shareddefs.h"
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include "tier0/memdbgon.h"
|
||
|
|
||
|
#define MD_FULLAMMO 50
|
||
|
|
||
|
|
||
|
#define MD_BC_YAW 0
|
||
|
#define MD_BC_PITCH 1
|
||
|
#define MD_AP_LGUN 2
|
||
|
#define MD_AP_RGUN 1
|
||
|
#define MD_GIB_COUNT 4
|
||
|
#define MD_GIB_MODEL "models/gibs/missile_defense_gibs.mdl"
|
||
|
#define MD_YAW_SPEED 24
|
||
|
#define MD_PITCH_SPEED 12
|
||
|
|
||
|
//=========================================================
|
||
|
//=========================================================
|
||
|
class CNPC_MissileDefense : public CAI_BaseNPC
|
||
|
{
|
||
|
DECLARE_CLASS( CNPC_MissileDefense, CAI_BaseNPC );
|
||
|
DECLARE_DATADESC();
|
||
|
|
||
|
public:
|
||
|
CNPC_MissileDefense( void ) { };
|
||
|
void Precache( void );
|
||
|
void Spawn( void );
|
||
|
Class_T Classify( void ) { return CLASS_NONE; }
|
||
|
int GetSoundInterests( void ) { return SOUND_NONE; }
|
||
|
float MaxYawSpeed( void ) { return 90.f; }
|
||
|
|
||
|
void RunAI(void);
|
||
|
void FireCannons( void );
|
||
|
void AimGun( void );
|
||
|
void EnemyShootPosition(CBaseEntity* pEnemy, Vector *vPosition);
|
||
|
|
||
|
void Event_Killed( const CTakeDamageInfo &info );
|
||
|
int OnTakeDamage_Alive( const CTakeDamageInfo &info );
|
||
|
void Gib();
|
||
|
void GetGunAim( Vector *vecAim );
|
||
|
~CNPC_MissileDefense();
|
||
|
|
||
|
Vector m_vGunAng;
|
||
|
int m_iAmmoLoaded;
|
||
|
float m_flReloadedTime;
|
||
|
};
|
||
|
|
||
|
LINK_ENTITY_TO_CLASS( npc_missiledefense, CNPC_MissileDefense );
|
||
|
|
||
|
//=========================================================
|
||
|
//=========================================================
|
||
|
BEGIN_DATADESC( CNPC_MissileDefense )
|
||
|
|
||
|
DEFINE_FIELD( m_iAmmoLoaded, FIELD_INTEGER ),
|
||
|
DEFINE_FIELD( m_flReloadedTime, FIELD_TIME ),
|
||
|
DEFINE_FIELD( m_vGunAng, FIELD_VECTOR ),
|
||
|
|
||
|
END_DATADESC()
|
||
|
|
||
|
//---------------------------------------------------------
|
||
|
//---------------------------------------------------------
|
||
|
void CNPC_MissileDefense::Precache( void )
|
||
|
{
|
||
|
PrecacheModel("models/missile_defense.mdl");
|
||
|
PrecacheModel(MD_GIB_MODEL);
|
||
|
|
||
|
PrecacheScriptSound( "NPC_MissileDefense.Attack" );
|
||
|
PrecacheScriptSound( "NPC_MissileDefense.Reload" );
|
||
|
PrecacheScriptSound( "NPC_MissileDefense.Turn" );
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------
|
||
|
//---------------------------------------------------------
|
||
|
void CNPC_MissileDefense::GetGunAim( Vector *vecAim )
|
||
|
{
|
||
|
Vector vecPos;
|
||
|
QAngle vecAng;
|
||
|
|
||
|
GetAttachment( MD_AP_LGUN, vecPos, vecAng );
|
||
|
|
||
|
vecAng.x = GetLocalAngles().x + GetBoneController( MD_BC_PITCH );
|
||
|
vecAng.z = 0;
|
||
|
vecAng.y = GetLocalAngles().y + GetBoneController( MD_BC_YAW );
|
||
|
|
||
|
Vector vecForward;
|
||
|
AngleVectors( vecAng, &vecForward );
|
||
|
|
||
|
*vecAim = vecForward;
|
||
|
}
|
||
|
|
||
|
|
||
|
#define NOISE 0.035f
|
||
|
#define MD_ATTN_CANNON 0.4
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose :
|
||
|
// Input :
|
||
|
// Output :
|
||
|
//------------------------------------------------------------------------------
|
||
|
void CNPC_MissileDefense::FireCannons( void )
|
||
|
{
|
||
|
// ----------------------------------------------
|
||
|
// Make sure I have an enemy
|
||
|
// ----------------------------------------------
|
||
|
if (GetEnemy() == NULL)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// ----------------------------------------------
|
||
|
// Make sure I have ammo
|
||
|
// ----------------------------------------------
|
||
|
if( m_iAmmoLoaded < 1 )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
// ----------------------------------------------
|
||
|
// Make sure gun it pointing in right direction
|
||
|
// ----------------------------------------------
|
||
|
Vector vGunDir;
|
||
|
GetGunAim( &vGunDir );
|
||
|
Vector vTargetPos;
|
||
|
EnemyShootPosition(GetEnemy(),&vTargetPos);
|
||
|
|
||
|
Vector vTargetDir = vTargetPos - GetAbsOrigin();
|
||
|
VectorNormalize( vTargetDir );
|
||
|
|
||
|
float fDotPr = DotProduct( vGunDir, vTargetDir );
|
||
|
if (fDotPr < 0.95)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// ----------------------------------------------
|
||
|
// Check line of sight
|
||
|
// ----------------------------------------------
|
||
|
trace_t tr;
|
||
|
AI_TraceLine( GetEnemy()->EyePosition(), GetAbsOrigin(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
|
||
|
if (tr.fraction < 1.0)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Vector vecRight;
|
||
|
Vector vecDir;
|
||
|
Vector vecCenter;
|
||
|
AngleVectors( GetLocalAngles(), NULL, &vecRight, NULL );
|
||
|
|
||
|
vecCenter = WorldSpaceCenter();
|
||
|
|
||
|
if( GetEnemy() == NULL )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
bool fSound = false;
|
||
|
if( random->RandomInt( 0, 3 ) == 0 )
|
||
|
{
|
||
|
fSound = true;
|
||
|
}
|
||
|
|
||
|
|
||
|
EmitSound( "NPC_MissileDefense.Attack" );
|
||
|
|
||
|
Vector vecGun;
|
||
|
QAngle vecAng;
|
||
|
|
||
|
GetAttachment( MD_AP_LGUN, vecGun, vecAng );
|
||
|
|
||
|
Vector vecTarget;
|
||
|
EnemyShootPosition(GetEnemy(),&vecTarget);
|
||
|
|
||
|
vecDir = vecTarget - vecCenter;
|
||
|
VectorNormalize(vecDir);
|
||
|
vecDir.x += random->RandomFloat( -NOISE, NOISE );
|
||
|
vecDir.y += random->RandomFloat( -NOISE, NOISE );
|
||
|
|
||
|
Vector vecStart = vecGun + vecDir * 110;
|
||
|
Vector vecEnd = vecGun + vecDir * 4096;
|
||
|
UTIL_Tracer( vecStart, vecEnd, 0, TRACER_DONT_USE_ATTACHMENT, 3000 + random->RandomFloat( 0, 2000 ), fSound );
|
||
|
|
||
|
vecDir = vecTarget - vecCenter;
|
||
|
VectorNormalize(vecDir);
|
||
|
vecDir.x += random->RandomFloat( -NOISE, NOISE );
|
||
|
vecDir.y += random->RandomFloat( -NOISE, NOISE );
|
||
|
vecDir.z += random->RandomFloat( -NOISE, NOISE );
|
||
|
|
||
|
GetAttachment( MD_AP_RGUN, vecGun, vecAng );
|
||
|
vecStart = vecGun + vecDir * 110;
|
||
|
vecEnd = vecGun + vecDir * 4096;
|
||
|
UTIL_Tracer( vecStart, vecEnd, 0, TRACER_DONT_USE_ATTACHMENT, 3000 + random->RandomFloat( 0, 2000 ) );
|
||
|
|
||
|
m_iAmmoLoaded -= 2;
|
||
|
|
||
|
if( m_iAmmoLoaded < 1 )
|
||
|
{
|
||
|
// Incite a reload.
|
||
|
EmitSound( "NPC_MissileDefense.Reload" );
|
||
|
m_flReloadedTime = gpGlobals->curtime + 0.3;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Do damage to the missile based on distance.
|
||
|
// if < 1, make damage 0.
|
||
|
|
||
|
float flDist = (GetEnemy()->GetLocalOrigin() - vecGun).Length();
|
||
|
float flDamage;
|
||
|
|
||
|
flDamage = 4000 - flDist;
|
||
|
|
||
|
flDamage /= 1000.0;
|
||
|
|
||
|
if( flDamage > 0 )
|
||
|
{
|
||
|
if( flDist <= 1500 )
|
||
|
{
|
||
|
flDamage *= 2;
|
||
|
}
|
||
|
|
||
|
CTakeDamageInfo info( this, this, flDamage, DMG_MISSILEDEFENSE );
|
||
|
CalculateBulletDamageForce( &info, GetAmmoDef()->Index("SMG1"), vecDir, GetEnemy()->GetAbsOrigin() );
|
||
|
GetEnemy()->TakeDamage( info );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------
|
||
|
//---------------------------------------------------------
|
||
|
void CNPC_MissileDefense::Spawn( void )
|
||
|
{
|
||
|
Precache();
|
||
|
|
||
|
SetModel( "models/missile_defense.mdl" );
|
||
|
UTIL_SetSize( this, Vector( -36, -36 , 0 ), Vector( 36, 36, 64 ) );
|
||
|
|
||
|
SetSolid( SOLID_BBOX );
|
||
|
SetMoveType( MOVETYPE_NONE );
|
||
|
m_takedamage = DAMAGE_YES;
|
||
|
SetBloodColor( DONT_BLEED );
|
||
|
m_iHealth = 10;
|
||
|
m_flFieldOfView = 0.1;
|
||
|
m_NPCState = NPC_STATE_NONE;
|
||
|
CapabilitiesClear();
|
||
|
CapabilitiesAdd ( bits_CAP_INNATE_RANGE_ATTACK1 );
|
||
|
|
||
|
// Hate missiles
|
||
|
AddClassRelationship( CLASS_MISSILE, D_HT, 5 );
|
||
|
|
||
|
m_spawnflags |= SF_NPC_LONG_RANGE;
|
||
|
|
||
|
m_flReloadedTime = gpGlobals->curtime;
|
||
|
|
||
|
InitBoneControllers();
|
||
|
|
||
|
NPCInit();
|
||
|
|
||
|
SetBoneController( MD_BC_YAW, 10 );
|
||
|
SetBoneController( MD_BC_PITCH, 0 );
|
||
|
|
||
|
SetBodygroup( 1, 1 );
|
||
|
SetBodygroup( 2, 1 );
|
||
|
SetBodygroup( 3, 1 );
|
||
|
SetBodygroup( 4, 1 );
|
||
|
|
||
|
m_NPCState = NPC_STATE_IDLE;
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose :
|
||
|
// Input :
|
||
|
// Output :
|
||
|
//------------------------------------------------------------------------------
|
||
|
int CNPC_MissileDefense::OnTakeDamage_Alive( const CTakeDamageInfo &info )
|
||
|
{
|
||
|
// Only take blast damage
|
||
|
if (info.GetDamageType() & DMG_BLAST )
|
||
|
{
|
||
|
return BaseClass::OnTakeDamage_Alive( info );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose :
|
||
|
// Input :
|
||
|
// Output :
|
||
|
//------------------------------------------------------------------------------
|
||
|
void CNPC_MissileDefense::Event_Killed( const CTakeDamageInfo &info )
|
||
|
{
|
||
|
StopSound( "NPC_MissileDefense.Turn" );
|
||
|
Gib();
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose :
|
||
|
// Input :
|
||
|
// Output :
|
||
|
//------------------------------------------------------------------------------
|
||
|
void CNPC_MissileDefense::Gib(void)
|
||
|
{
|
||
|
// Sparks
|
||
|
for (int i = 0; i < 4; i++)
|
||
|
{
|
||
|
Vector sparkPos = GetAbsOrigin();
|
||
|
sparkPos.x += random->RandomFloat(-12,12);
|
||
|
sparkPos.y += random->RandomFloat(-12,12);
|
||
|
sparkPos.z += random->RandomFloat(-12,12);
|
||
|
g_pEffects->Sparks(sparkPos);
|
||
|
}
|
||
|
// Smoke
|
||
|
UTIL_Smoke(GetAbsOrigin(), random->RandomInt(10, 15), 10);
|
||
|
|
||
|
// Light
|
||
|
CBroadcastRecipientFilter filter;
|
||
|
|
||
|
te->DynamicLight( filter, 0.0,
|
||
|
&GetAbsOrigin(), 255, 180, 100, 0, 100, 0.1, 0 );
|
||
|
|
||
|
// Remove top parts
|
||
|
SetBodygroup( 1, 0 );
|
||
|
SetBodygroup( 2, 0 );
|
||
|
SetBodygroup( 3, 0 );
|
||
|
SetBodygroup( 4, 0 );
|
||
|
m_takedamage = 0;
|
||
|
SetThink(NULL);
|
||
|
|
||
|
// Throw manhackgibs
|
||
|
CGib::SpawnSpecificGibs( this, MD_GIB_COUNT, 300, 500, MD_GIB_MODEL);
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose :
|
||
|
// Input :
|
||
|
// Output :
|
||
|
//------------------------------------------------------------------------------
|
||
|
void CNPC_MissileDefense::RunAI( void )
|
||
|
{
|
||
|
// If my enemy is dead clear the memory and reset m_hEnemy
|
||
|
if (GetEnemy() != NULL &&
|
||
|
!GetEnemy()->IsAlive())
|
||
|
{
|
||
|
ClearEnemyMemory();
|
||
|
SetEnemy( NULL );
|
||
|
}
|
||
|
|
||
|
if (GetEnemy() == NULL )
|
||
|
{
|
||
|
GetSenses()->Look( 4092 );
|
||
|
SetEnemy( BestEnemy( ) );
|
||
|
|
||
|
if (GetEnemy() != NULL)
|
||
|
{
|
||
|
m_iAmmoLoaded = MD_FULLAMMO;
|
||
|
m_flReloadedTime = gpGlobals->curtime;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( m_iAmmoLoaded < 1 && gpGlobals->curtime > m_flReloadedTime )
|
||
|
{
|
||
|
m_iAmmoLoaded = MD_FULLAMMO;
|
||
|
}
|
||
|
|
||
|
AimGun();
|
||
|
FireCannons();
|
||
|
SetNextThink( gpGlobals->curtime + 0.05 );
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose : Add a little prediction into my enemy aim position
|
||
|
// Input :
|
||
|
// Output :
|
||
|
//------------------------------------------------------------------------------
|
||
|
void CNPC_MissileDefense::EnemyShootPosition(CBaseEntity* pEnemy, Vector *vPosition)
|
||
|
{
|
||
|
// This should never happen, but just in case
|
||
|
if (!pEnemy)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
*vPosition = pEnemy->GetAbsOrigin();
|
||
|
|
||
|
// Add prediction but prevents us from flipping around as enemy approaches us
|
||
|
float flDist = (pEnemy->GetAbsOrigin() - GetAbsOrigin()).Length();
|
||
|
Vector vPredVel = pEnemy->GetSmoothedVelocity() * 0.5;
|
||
|
if ( flDist > vPredVel.Length())
|
||
|
{
|
||
|
*vPosition += vPredVel;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose :
|
||
|
// Input :
|
||
|
// Output :
|
||
|
//------------------------------------------------------------------------------
|
||
|
void CNPC_MissileDefense::AimGun( void )
|
||
|
{
|
||
|
if (GetEnemy() == NULL)
|
||
|
{
|
||
|
StopSound( "NPC_MissileDefense.Turn" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Vector forward, right, up;
|
||
|
AngleVectors( GetLocalAngles(), &forward, &right, &up );
|
||
|
|
||
|
// Get gun attachment points
|
||
|
Vector vBasePos;
|
||
|
QAngle vBaseAng;
|
||
|
GetAttachment( MD_AP_LGUN, vBasePos, vBaseAng );
|
||
|
|
||
|
Vector vTargetPos;
|
||
|
EnemyShootPosition(GetEnemy(),&vTargetPos);
|
||
|
|
||
|
Vector vTargetDir = vTargetPos - vBasePos;
|
||
|
VectorNormalize( vTargetDir );
|
||
|
|
||
|
Vector vecOut;
|
||
|
vecOut.x = DotProduct( forward, vTargetDir );
|
||
|
vecOut.y = -DotProduct( right, vTargetDir );
|
||
|
vecOut.z = DotProduct( up, vTargetDir );
|
||
|
|
||
|
QAngle angles;
|
||
|
VectorAngles(vecOut, angles);
|
||
|
|
||
|
if (angles.y > 180)
|
||
|
angles.y = angles.y - 360;
|
||
|
if (angles.y < -180)
|
||
|
angles.y = angles.y + 360;
|
||
|
if (angles.x > 180)
|
||
|
angles.x = angles.x - 360;
|
||
|
if (angles.x < -180)
|
||
|
angles.x = angles.x + 360;
|
||
|
|
||
|
float flOldX = m_vGunAng.x;
|
||
|
float flOldY = m_vGunAng.y;
|
||
|
|
||
|
if (angles.x > m_vGunAng.x)
|
||
|
m_vGunAng.x = MIN( angles.x, m_vGunAng.x + MD_PITCH_SPEED );
|
||
|
if (angles.x < m_vGunAng.x)
|
||
|
m_vGunAng.x = MAX( angles.x, m_vGunAng.x - MD_PITCH_SPEED );
|
||
|
if (angles.y > m_vGunAng.y)
|
||
|
m_vGunAng.y = MIN( angles.y, m_vGunAng.y + MD_YAW_SPEED );
|
||
|
if (angles.y < m_vGunAng.y)
|
||
|
m_vGunAng.y = MAX( angles.y, m_vGunAng.y - MD_YAW_SPEED );
|
||
|
|
||
|
m_vGunAng.y = SetBoneController( MD_BC_YAW, m_vGunAng.y );
|
||
|
m_vGunAng.x = SetBoneController( MD_BC_PITCH, m_vGunAng.x );
|
||
|
|
||
|
if (flOldX != m_vGunAng.x || flOldY != m_vGunAng.y)
|
||
|
{
|
||
|
EmitSound( "NPC_MissileDefense.Turn" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
StopSound( "NPC_MissileDefense.Turn" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose :
|
||
|
// Input :
|
||
|
// Output :
|
||
|
//------------------------------------------------------------------------------
|
||
|
CNPC_MissileDefense::~CNPC_MissileDefense(void)
|
||
|
{
|
||
|
StopSound( "NPC_MissileDefense.Turn" );
|
||
|
}
|