Npc missiledefense Fix

From Valve Developer Community
Jump to navigation Jump to search
Broom icon.png
This article or section should be converted to third person to conform to wiki standards.

This is fix for the npc_missiledefense entity, so it can actually aim and take down rpg rockets launched by the player, unlike the default code which makes only works on npc launched ones. Also I added some extra features like a flag to make it vulnerable to bullets, and the options for the mappers to pick the models and sounds, as well as their health.

With these improvements, now everyone will be able to use this npc on their mods. Look at the GitHub page for the model and test map files. Of course, if you think you can improve it, go forward. This is for everyone.

Note.pngNote:The code part of igniting and turning black the npc on death, as well as working muzzle flashes doesn't work. I'm sorry.
Note.pngNote:I didn't found a way to tell the game to precache a default model and sounds. If you think you know how, tell me.

You can see a video demonstration of it here.

GitHub

https://github.com/MaestroFenix/MissileDefense

npc_missiledefense.cpp

	
//========= 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"
#include "explode.h" //For the explosion
#include "effect_dispatch_data.h" //Muzzleflash
#include "te_effect_dispatch.h" //Muzzleflash
//#include "ai_basenpc.h" //Ignite
//#include "decals.h" //Scorch effect

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define MD_FULLAMMO	50


#define MD_BC_YAW		1
#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	48
#define MD_PITCH_SPEED  48

//------------------------------------
// Spawnflags
//------------------------------------
#define SF_MISSILEDEFENSE_BULLETDMG			(1 << 16) //Can be damaged with bullets? N on def

//=========================================================
//=========================================================
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	Explode(const Vector &vecExplosionPos);
	void	Gib();
	void	GetGunAim( Vector *vecAim );
	void	TurretTurnOn(void);
	void	DoMuzzleFlash(void);
	~CNPC_MissileDefense();

	Vector		m_vGunAng;
	int			m_iAmmoLoaded;
	float		m_flReloadedTime;

	string_t			m_sTurretModel;
	string_t			m_sGibModel;
	string_t			m_sFireSound;
	string_t			m_sRotateSound;
	string_t			m_sReloadSound;

	int					m_nStartOn;
	int				m_nHealth;

	// ----------------
	//	Inputs
	// ----------------
	void InputTurnOn( inputdata_t &inputdata );
	void InputTurnOff( inputdata_t &inputdata );
};

LINK_ENTITY_TO_CLASS( npc_missiledefense, CNPC_MissileDefense );


ConVar	sk_missiledefense_health( "sk_missiledefense_health","100");

//=========================================================
//=========================================================
BEGIN_DATADESC( CNPC_MissileDefense )

	DEFINE_FIELD( m_iAmmoLoaded,		FIELD_INTEGER ),
	DEFINE_FIELD( m_flReloadedTime,	FIELD_TIME ),
	DEFINE_FIELD( m_vGunAng,			FIELD_VECTOR ),

	DEFINE_KEYFIELD( m_sTurretModel,			FIELD_STRING,	"TurretModel" ),
	DEFINE_KEYFIELD( m_sGibModel,			FIELD_STRING,	"GibModel" ),
	DEFINE_KEYFIELD( m_sFireSound,				FIELD_STRING,	"FireSound" ),
	DEFINE_KEYFIELD( m_sRotateSound,				FIELD_STRING,	"RotateSound" ),
	DEFINE_KEYFIELD( m_sReloadSound,				FIELD_STRING,	"ReloadSound" ),
	DEFINE_KEYFIELD( m_nHealth,					FIELD_INTEGER,	"Health" ),

	DEFINE_INPUT( m_nStartOn,			FIELD_INTEGER,	"StartOn" ),

	DEFINE_FIELD( m_nStartOn, FIELD_BOOLEAN ), //Starts on?

	DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
	DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),

END_DATADESC()

// ===================
//  Input Functions
// ===================

//--------------------------------------------------------------
// Purpose: Enables npc
//--------------------------------------------------------------
void CNPC_MissileDefense::InputTurnOn( inputdata_t &inputdata )
{
	m_nStartOn = 1;
	SetThink( &CNPC_MissileDefense::CallNPCThink );
	SetNextThink( gpGlobals->curtime );
}

//--------------------------------------------------------------
// Purpose: Disables npc
//--------------------------------------------------------------
void CNPC_MissileDefense::InputTurnOff( inputdata_t &inputdata )
{
	m_nStartOn = 0;
	SetThink(NULL);
}


//--------------------------------------------------------------
// Purpose: Precache models and sounds that are going to be used
//--------------------------------------------------------------
void CNPC_MissileDefense::Precache( void )
{
	PrecacheModel(STRING(m_sTurretModel));
	PrecacheModel(STRING(m_sGibModel));
	
	PrecacheScriptSound( STRING(m_sFireSound));
	PrecacheScriptSound( STRING(m_sRotateSound));
	PrecacheScriptSound( STRING(m_sReloadSound));
}

//---------------------------------------------------------
// Purpose: Spawns the npc
//---------------------------------------------------------
void CNPC_MissileDefense::Spawn( void )
{
	Precache();

	//Gets gib model defined by the mapper
	char *szModel = (char *)STRING( m_sTurretModel );
	
	SetModel( szModel );	

	UTIL_SetSize( this, Vector( -36, -36 , 0 ), Vector( 36, 36, 64 ) );

	SetSolid( SOLID_BBOX );
	SetMoveType( MOVETYPE_NONE );
	m_takedamage		= DAMAGE_YES;
	SetBloodColor( DONT_BLEED ); 

	//Gets health defined by the mapper, if not set, get the cvar one
	if(!m_nHealth)
	{
		m_iHealth			= sk_missiledefense_health.GetFloat();
	}
	else
	{
		m_iHealth			= m_nHealth;
	}

	m_flFieldOfView		= VIEW_FIELD_FULL; //0.4f
	m_NPCState			= NPC_STATE_ALERT;
	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, 0 ); //10
	SetBoneController( MD_BC_PITCH, 0 );

	SetBodygroup( 1, 0 );
	SetBodygroup( 2, 0 );
	SetBodygroup( 3, 0 );
	SetBodygroup( 4, 0 );
}

//------------------------------------------------------------------------------
// Purpose : Main AI work
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_MissileDefense::RunAI( void )
{

	//If Starts On option isn't activated, disable AI 
	if (m_nStartOn == 0)
	{
		SetThink(NULL);
	}

	// If my enemy is dead clear the memory and reset m_hEnemy
	if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) ) 
	{
		ClearEnemyMemory();
		SetEnemy( NULL );
	}

	if (GetEnemy() == NULL )
	{
		// We have to refresh our memories before finding enemies, so
		// dead enemies are cleared out before new ones are added.
		GetEnemies()->RefreshMemories();
		
		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: Starts aiming the gun
//---------------------------------------------------------
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 : Open fire on the rocket
// 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	
	//
	// Disabled, so it makes work fine the turret
	// ----------------------------------------------
	/*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
	//
	// For some unknown reason, with two or more turrets it makes them to do not fire. Disabled.
	// -----------------------------------------------------------------------------------------
	/*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;
	}

	//Plays the fire sound
	char *sFireSound = (char *)STRING( m_sFireSound );
	EmitSound(sFireSound);

	DoMuzzleFlash();

	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.
		char *sReloadSound = (char *)STRING( m_sReloadSound );
		EmitSound(sReloadSound);

		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 );
	}
}

//------------------------------------------------------------------------------
// Purpose : Takes damage only from blasts and bullets
// Input   :
// Output  :
//------------------------------------------------------------------------------
int CNPC_MissileDefense::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{

	// Only take blast damage
	if (info.GetDamageType() & DMG_BLAST )
	{
		return BaseClass::OnTakeDamage_Alive( info );
	}

	//
	// Bullets can damage it if their flag is checked
	//
	else if (info.GetDamageType() & DMG_BULLET)
	{
		if(HasSpawnFlags(SF_MISSILEDEFENSE_BULLETDMG))
		{
			return BaseClass::OnTakeDamage_Alive( info );
		}
		else
		{
			return 0;
		}
	}
	else
	{
		return 0;
	}
}

//------------------------------------------------------------------------------
// Purpose : Stuff to do when gets killed
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_MissileDefense::Event_Killed( const CTakeDamageInfo &info )
{
	//Set on fire
	//Ignite( 60, false );
	
	//Paint me black
	//Scorch( 8, 50 );

	char *sRotateSound = (char *)STRING( m_sRotateSound );
	StopSound( sRotateSound );

	CTakeDamageInfo dmgInfo = info;

	Explode( dmgInfo.GetDamagePosition() );

	Gib();
}

//------------------------------------------------------------------------------
// Purpose : Makes an explosion
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_MissileDefense::Explode(const Vector &vecExplosionPos)
{
	//Explodes!
	ExplosionCreate( vecExplosionPos, vec3_angle, this, 1000, 500.0f, 
		SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS	|
		SF_ENVEXPLOSION_NOSMOKE  | SF_ENVEXPLOSION_NOFIREBALLSMOKE, 0 );
	UTIL_ScreenShake( vecExplosionPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
}

//------------------------------------------------------------------------------
// Purpose : Gibs and some more effects when death
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_MissileDefense::Gib(void)
{
	//Gets gib model defined by the mapper
	char *szModelGib = (char *)STRING( m_sGibModel ); 

	// 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, 1 );
	SetBodygroup( 2, 1 );
	SetBodygroup( 3, 1 );
	SetBodygroup( 4, 1 );
	m_takedamage = 0;
	SetThink(NULL);

	// Throw manhackgibs
	CGib::SpawnSpecificGibs( this, MD_GIB_COUNT, 300, 500, szModelGib);
}

//------------------------------------------------------------------------------
// 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 : Moves the turret to aim the rocket
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CNPC_MissileDefense::AimGun( void )
{
	if (GetEnemy() == NULL)
	{
		char *sRotateSound = (char *)STRING( m_sRotateSound );
		StopSound( sRotateSound );
		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);

	//Pitch for the cannons
	if (angles.x != m_vGunAng.x){
		float flDir = m_vGunAng.x > angles.x ? 1 : -1;
		angles.x += 0.1 * MD_PITCH_SPEED * flDir;

		SetBoneController(1, angles.x - m_vGunAng.x);
	}
	
	//Yaw for the cannons
	if (angles.y != m_vGunAng.y){
		float flDir = m_vGunAng.y > angles.y ? 1 : -1;
		float flDist = fabs(m_vGunAng.y - angles.y);

		if (flDist > 180){
			flDist = 360 - flDist;
			flDir =- flDir;
		}

		angles.y += 0.1 * MD_YAW_SPEED * flDir;

		if (angles.y < 0)
			angles.y += 360;
		else if (angles.y >= 360)
			angles.y -= 360;

		if(flDist < (0.05 * MD_YAW_SPEED))
			angles.y = m_vGunAng.y;

		SetBoneController(0, angles.y - m_vGunAng.y);
	}

	//Plays movement sound
	if (angles.x != m_vGunAng.x || angles.y != m_vGunAng.y)
	{
		//EmitSound( "NPC_FloorTurret.Alarm" );
		char *sRotateSound = (char *)STRING( m_sRotateSound );
		EmitSound( sRotateSound );
	}
	else
	{
		//StopSound( "NPC_FloorTurret.Alarm" );
		char *sRotateSound = (char *)STRING( m_sRotateSound );
		StopSound( sRotateSound );
	}
}


//------------------------------------------------------------------------------
// Purpose :
// Input   :
// Output  :
//------------------------------------------------------------------------------
CNPC_MissileDefense::~CNPC_MissileDefense(void)
{
	char *sRotateSound = (char *)STRING( m_sRotateSound );
	StopSound( sRotateSound );
}

//-----------------------------------------------------------------------------
// Purpose: Puts a muzzleflash on the cannons
//-----------------------------------------------------------------------------
void CNPC_MissileDefense::DoMuzzleFlash( void )
{
	CEffectData data, data2;
	data.m_nEntIndex = entindex();
	data2.m_nEntIndex = entindex();
	data.m_nAttachmentIndex = LookupAttachment( "R_Gun_AtchPnt" );
	data2.m_nAttachmentIndex = LookupAttachment( "L_Gun_AtchPnt" );
	data.m_flScale = 1.0f;
	data2.m_flScale = 1.0f;
	DispatchEffect( "ChopperMuzzleFlash", data );
	DispatchEffect( "ChopperMuzzleFlash", data2 );
}

FGD

//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======
//
// Purpose: Missile Defense npc fixed and improved by Maestro Fénix (.fgd) 
//
// Copy and paste the code on the fgd of your mod.
//=============================================================================

@NPCClass base(BaseNPC) studio("models/missile_defense.mdl") = npc_missiledefense : "A turret who takes down rockets from the player"
[
	spawnflags(flags) = 
	[
		65536  :  "Vulnerable to bullets" : 0
	]

	Health(Integer) : "Health" : 10
	TurretModel(studio) : "Turret Model" : "models/missile_defense.mdl"
	GibModel(studio) : "Gib Model" : "models/gibs/manhack_gib01.mdl"
	FireSound(sound) : "Fire Sound" : "npc/turret_floor/shoot1.wav"
	RotateSound(sound) :	"Rotate Sound": "npc/turret_floor/ping.wav"
	ReloadSound(sound) :	"Reload Sound": "vehicles/tank_readyfire1.wav"
	
	StartOn(choices) : "Start On" : 1 =
	[
		0 : "No"
		1 : "Yes"
	]
	
	// Inputs
	input TurnOn(void) : "Turn on: Look for enemies"
	input TurnOff(void) : "Turn off: Stop looking for enemies"
]