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.
496 lines
12 KiB
496 lines
12 KiB
//========= 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" ); |
|
}
|
|
|