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.
414 lines
12 KiB
414 lines
12 KiB
5 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
|
||
|
#include "cbase.h"
|
||
|
#include "ai_basenpc.h"
|
||
|
#include "ai_senses.h"
|
||
|
#include "ai_squad.h"
|
||
|
#include "grenade_homer.h"
|
||
|
#include "grenade_pathfollower.h"
|
||
|
#include "explode.h"
|
||
|
#include "ndebugoverlay.h"
|
||
|
#include "engine/IEngineSound.h"
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include "tier0/memdbgon.h"
|
||
|
|
||
|
#define LAUNCHER_REST_TIME 3
|
||
|
|
||
|
|
||
|
//------------------------------------
|
||
|
// Spawnflags
|
||
|
//------------------------------------
|
||
|
#define SF_LAUNCHER_CHECK_LOS (1 << 16)
|
||
|
|
||
|
|
||
|
//=========================================================
|
||
|
// >> CNPC_Launcher
|
||
|
//=========================================================
|
||
|
class CNPC_Launcher : public CAI_BaseNPC
|
||
|
{
|
||
|
DECLARE_CLASS( CNPC_Launcher, CAI_BaseNPC );
|
||
|
|
||
|
public:
|
||
|
int m_nStartOn;
|
||
|
string_t m_sMissileModel;
|
||
|
string_t m_sLaunchSound;
|
||
|
string_t m_sFlySound;
|
||
|
int m_nSmokeTrail;
|
||
|
bool m_bSmokeLaunch;
|
||
|
int m_nLaunchDelay;
|
||
|
float m_flLaunchSpeed;
|
||
|
string_t m_sPathCornerName; // If following a path
|
||
|
float m_flHomingSpeed;
|
||
|
int m_nHomingStrength;
|
||
|
float m_flHomingDelay; // How long before homing starts
|
||
|
float m_flHomingRampUp; // How much time to ramp up to full homing
|
||
|
float m_flHomingDuration; // How long does homing last
|
||
|
float m_flHomingRampDown; // How long to ramp down to no homing
|
||
|
float m_flMissileGravity;
|
||
|
float m_flMinAttackDist;
|
||
|
float m_flMaxAttackDist;
|
||
|
float m_flSpinMagnitude;
|
||
|
float m_flSpinSpeed;
|
||
|
float m_flDamage;
|
||
|
float m_flDamageRadius;
|
||
|
|
||
|
// ----------------
|
||
|
// Outputs
|
||
|
// ----------------
|
||
|
COutputEvent m_OnLaunch; // Triggered when missile is launched.
|
||
|
|
||
|
// ----------------
|
||
|
// Inputs
|
||
|
// ----------------
|
||
|
void InputTurnOn( inputdata_t &inputdata );
|
||
|
void InputTurnOff( inputdata_t &inputdata );
|
||
|
void InputLOSCheckOn( inputdata_t &inputdata );
|
||
|
void InputLOSCheckOff( inputdata_t &inputdata );
|
||
|
void InputSetEnemy( inputdata_t &inputdata );
|
||
|
void InputClearEnemy( inputdata_t &inputdata );
|
||
|
void InputFireOnce( inputdata_t &inputdata );
|
||
|
|
||
|
void LauncherTurnOn(void);
|
||
|
|
||
|
void Precache( void );
|
||
|
void Spawn( void );
|
||
|
Class_T Classify( void );
|
||
|
bool IsValidEnemy(CBaseEntity *pTarget );
|
||
|
void LaunchGrenade(CBaseEntity* pLauncher );
|
||
|
void LauncherThink(void );
|
||
|
bool FInViewCone( CBaseEntity *pEntity );
|
||
|
|
||
|
int DrawDebugTextOverlays(void);
|
||
|
|
||
|
DECLARE_DATADESC();
|
||
|
};
|
||
|
|
||
|
|
||
|
BEGIN_DATADESC( CNPC_Launcher )
|
||
|
|
||
|
// Inputs
|
||
|
DEFINE_KEYFIELD( m_nStartOn, FIELD_INTEGER, "StartOn" ),
|
||
|
DEFINE_KEYFIELD( m_sMissileModel, FIELD_STRING, "MissileModel" ),
|
||
|
DEFINE_KEYFIELD( m_sLaunchSound, FIELD_STRING, "LaunchSound" ),
|
||
|
DEFINE_KEYFIELD( m_sFlySound, FIELD_STRING, "FlySound" ),
|
||
|
DEFINE_KEYFIELD( m_nSmokeTrail, FIELD_INTEGER, "SmokeTrail" ),
|
||
|
DEFINE_KEYFIELD( m_bSmokeLaunch, FIELD_BOOLEAN, "LaunchSmoke" ),
|
||
|
DEFINE_KEYFIELD( m_nLaunchDelay, FIELD_INTEGER, "LaunchDelay" ),
|
||
|
DEFINE_KEYFIELD( m_flLaunchSpeed, FIELD_FLOAT, "LaunchSpeed" ),
|
||
|
DEFINE_KEYFIELD( m_sPathCornerName, FIELD_STRING, "PathCornerName" ),
|
||
|
DEFINE_KEYFIELD( m_flHomingSpeed, FIELD_FLOAT, "HomingSpeed" ),
|
||
|
DEFINE_KEYFIELD( m_nHomingStrength, FIELD_INTEGER, "HomingStrength" ),
|
||
|
DEFINE_KEYFIELD( m_flHomingDelay, FIELD_FLOAT, "HomingDelay" ),
|
||
|
DEFINE_KEYFIELD( m_flHomingRampUp, FIELD_FLOAT, "HomingRampUp" ),
|
||
|
DEFINE_KEYFIELD( m_flHomingDuration, FIELD_FLOAT, "HomingDuration" ),
|
||
|
DEFINE_KEYFIELD( m_flHomingRampDown, FIELD_FLOAT, "HomingRampDown" ),
|
||
|
DEFINE_KEYFIELD( m_flGravity, FIELD_FLOAT, "Gravity" ),
|
||
|
DEFINE_KEYFIELD( m_flMinAttackDist, FIELD_FLOAT, "MinRange" ),
|
||
|
DEFINE_KEYFIELD( m_flMaxAttackDist, FIELD_FLOAT, "MaxRange" ),
|
||
|
DEFINE_KEYFIELD( m_flSpinMagnitude, FIELD_FLOAT, "SpinMagnitude" ),
|
||
|
DEFINE_KEYFIELD( m_flSpinSpeed, FIELD_FLOAT, "SpinSpeed" ),
|
||
|
DEFINE_KEYFIELD( m_flDamage, FIELD_FLOAT, "Damage" ),
|
||
|
DEFINE_KEYFIELD( m_flDamageRadius, FIELD_FLOAT, "DamageRadius" ),
|
||
|
DEFINE_FIELD( m_flMissileGravity, FIELD_FLOAT ),
|
||
|
|
||
|
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
|
||
|
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
|
||
|
DEFINE_INPUTFUNC( FIELD_VOID, "LOSCheckOn", InputLOSCheckOn ),
|
||
|
DEFINE_INPUTFUNC( FIELD_VOID, "LOSCheckOn", InputLOSCheckOn ),
|
||
|
DEFINE_INPUTFUNC( FIELD_VOID, "FireOnce", InputFireOnce ),
|
||
|
DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetEnemyEntity", InputSetEnemy ),
|
||
|
DEFINE_INPUTFUNC( FIELD_VOID, "ClearEnemyEntity", InputClearEnemy ),
|
||
|
|
||
|
DEFINE_OUTPUT( m_OnLaunch, "OnLaunch" ),
|
||
|
|
||
|
// Function Pointers
|
||
|
DEFINE_THINKFUNC( LauncherThink ),
|
||
|
|
||
|
END_DATADESC()
|
||
|
|
||
|
LINK_ENTITY_TO_CLASS( npc_launcher, CNPC_Launcher );
|
||
|
|
||
|
|
||
|
// ===================
|
||
|
// Input Functions
|
||
|
// ===================
|
||
|
void CNPC_Launcher::InputTurnOn( inputdata_t &inputdata )
|
||
|
{
|
||
|
LauncherTurnOn();
|
||
|
}
|
||
|
|
||
|
void CNPC_Launcher::InputTurnOff( inputdata_t &inputdata )
|
||
|
{
|
||
|
SetThink(NULL);
|
||
|
}
|
||
|
|
||
|
void CNPC_Launcher::InputLOSCheckOn( inputdata_t &inputdata )
|
||
|
{
|
||
|
m_spawnflags |= SF_LAUNCHER_CHECK_LOS;
|
||
|
}
|
||
|
|
||
|
void CNPC_Launcher::InputLOSCheckOff( inputdata_t &inputdata )
|
||
|
{
|
||
|
m_spawnflags &= ~SF_LAUNCHER_CHECK_LOS;
|
||
|
}
|
||
|
|
||
|
void CNPC_Launcher::InputSetEnemy( inputdata_t &inputdata )
|
||
|
{
|
||
|
SetEnemy( inputdata.value.Entity().Get() );
|
||
|
}
|
||
|
|
||
|
void CNPC_Launcher::InputClearEnemy( inputdata_t &inputdata )
|
||
|
{
|
||
|
SetEnemy( NULL );
|
||
|
}
|
||
|
|
||
|
void CNPC_Launcher::InputFireOnce( inputdata_t &inputdata )
|
||
|
{
|
||
|
m_flNextAttack = 0;
|
||
|
|
||
|
// If I using path following missiles just launch
|
||
|
if (m_sPathCornerName != NULL_STRING)
|
||
|
{
|
||
|
LaunchGrenade(NULL);
|
||
|
}
|
||
|
// Otherwise only launch if I have an enemy
|
||
|
else
|
||
|
{
|
||
|
LauncherThink();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Launcher::Precache( void )
|
||
|
{
|
||
|
// This is a dummy model that is never used!
|
||
|
PrecacheModel("models/player.mdl");
|
||
|
PrecacheModel(STRING(m_sMissileModel));
|
||
|
PrecacheScriptSound( STRING(m_sLaunchSound));
|
||
|
PrecacheScriptSound( STRING(m_sFlySound));
|
||
|
|
||
|
UTIL_PrecacheOther( "grenade_homer");
|
||
|
BaseClass::Precache();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Launcher::LauncherTurnOn(void)
|
||
|
{
|
||
|
SetThink(&CNPC_Launcher::LauncherThink);
|
||
|
SetNextThink( gpGlobals->curtime );
|
||
|
m_flNextAttack = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Launcher::Spawn( void )
|
||
|
{
|
||
|
Precache();
|
||
|
|
||
|
// This is a dummy model that is never used!
|
||
|
SetModel( "models/player.mdl" );
|
||
|
|
||
|
UTIL_SetSize(this, vec3_origin, vec3_origin);
|
||
|
|
||
|
m_takedamage = DAMAGE_NO;
|
||
|
|
||
|
if (m_nHomingStrength > 100)
|
||
|
{
|
||
|
m_nHomingStrength = 100;
|
||
|
Warning("WARNING: NPC_Launcher Homing Strength must be between 0 and 100\n");
|
||
|
}
|
||
|
|
||
|
SetSolid( SOLID_NONE );
|
||
|
SetMoveType( MOVETYPE_NONE );
|
||
|
SetBloodColor( DONT_BLEED );
|
||
|
AddEffects( EF_NODRAW );
|
||
|
|
||
|
AddFlag( FL_NPC );
|
||
|
|
||
|
CapabilitiesAdd( bits_CAP_SQUAD );
|
||
|
|
||
|
InitRelationshipTable();
|
||
|
|
||
|
if (m_nStartOn)
|
||
|
{
|
||
|
LauncherTurnOn();
|
||
|
}
|
||
|
|
||
|
// -------------------------------------------------------
|
||
|
// If I form squads add me to a squad
|
||
|
// -------------------------------------------------------
|
||
|
// @TODO (toml 12-05-02): RECONCILE WITH SAVE/RESTORE
|
||
|
if (!m_pSquad)
|
||
|
{
|
||
|
m_pSquad = g_AI_SquadManager.FindCreateSquad(this, m_SquadName);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
Class_T CNPC_Launcher::Classify( void )
|
||
|
{
|
||
|
return CLASS_NONE;
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//------------------------------------------------------------------------------
|
||
|
void CNPC_Launcher::LaunchGrenade( CBaseEntity* pEnemy )
|
||
|
{
|
||
|
// If a path following missile, create a path following missile
|
||
|
if (m_sPathCornerName != NULL_STRING)
|
||
|
{
|
||
|
CGrenadePathfollower *pGrenade = CGrenadePathfollower::CreateGrenadePathfollower( m_sMissileModel, m_sFlySound, GetAbsOrigin(), vec3_angle, edict() );
|
||
|
pGrenade->SetDamage(m_flDamage);
|
||
|
pGrenade->SetDamageRadius(m_flDamageRadius);
|
||
|
pGrenade->Launch(m_flLaunchSpeed,m_sPathCornerName);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Vector vUp;
|
||
|
AngleVectors( GetAbsAngles(), NULL, NULL, &vUp );
|
||
|
Vector vLaunchVelocity = (vUp * m_flLaunchSpeed);
|
||
|
|
||
|
CGrenadeHomer *pGrenade = CGrenadeHomer::CreateGrenadeHomer( m_sMissileModel, m_sFlySound, GetAbsOrigin(), vec3_angle, edict() );
|
||
|
pGrenade->Spawn( );
|
||
|
pGrenade->SetSpin(m_flSpinMagnitude,m_flSpinSpeed);
|
||
|
pGrenade->SetHoming((0.01*m_nHomingStrength),m_flHomingDelay,m_flHomingRampUp,m_flHomingDuration,m_flHomingRampDown);
|
||
|
pGrenade->SetDamage(m_flDamage);
|
||
|
pGrenade->SetDamageRadius(m_flDamageRadius);
|
||
|
pGrenade->Launch(this,pEnemy,vLaunchVelocity,m_flHomingSpeed,GetGravity(),m_nSmokeTrail);
|
||
|
}
|
||
|
|
||
|
CPASAttenuationFilter filter( this, 0.3 );
|
||
|
|
||
|
EmitSound_t ep;
|
||
|
ep.m_nChannel = CHAN_WEAPON;
|
||
|
ep.m_pSoundName = STRING(m_sLaunchSound);
|
||
|
ep.m_SoundLevel = SNDLVL_NORM;
|
||
|
|
||
|
EmitSound( filter, entindex(), ep );
|
||
|
|
||
|
if (m_bSmokeLaunch)
|
||
|
{
|
||
|
UTIL_Smoke(GetAbsOrigin(), random->RandomInt(20,30), random->RandomInt(10,15));
|
||
|
}
|
||
|
m_flNextAttack = gpGlobals->curtime + LAUNCHER_REST_TIME;
|
||
|
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose : Launcher sees 360 degrees
|
||
|
//------------------------------------------------------------------------------
|
||
|
bool CNPC_Launcher::FInViewCone( CBaseEntity *pEntity )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose : Override base class to check range and visibility
|
||
|
//------------------------------------------------------------------------------
|
||
|
bool CNPC_Launcher::IsValidEnemy( CBaseEntity *pTarget )
|
||
|
{
|
||
|
// ---------------------------------
|
||
|
// Check range
|
||
|
// ---------------------------------
|
||
|
float flTargetDist = (GetAbsOrigin() - pTarget->GetAbsOrigin()).Length();
|
||
|
if (flTargetDist < m_flMinAttackDist)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (flTargetDist > m_flMaxAttackDist)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!FBitSet (m_spawnflags, SF_LAUNCHER_CHECK_LOS))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
// ------------------------------------------------------
|
||
|
// Make sure I can see the target from above my position
|
||
|
// ------------------------------------------------------
|
||
|
trace_t tr;
|
||
|
|
||
|
// Trace from launch position to target position.
|
||
|
// Use position above actual barral based on vertical launch speed
|
||
|
Vector vStartPos = GetAbsOrigin() + Vector(0,0,0.2*m_flLaunchSpeed);
|
||
|
Vector vEndPos = pTarget->GetAbsOrigin();
|
||
|
AI_TraceLine( vStartPos, vEndPos, MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );
|
||
|
|
||
|
if (tr.fraction == 1.0)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose :
|
||
|
//------------------------------------------------------------------------------
|
||
|
void CNPC_Launcher::LauncherThink( void )
|
||
|
{
|
||
|
if (gpGlobals->curtime > m_flNextAttack)
|
||
|
{
|
||
|
// If enemy was set, fire at enemy
|
||
|
if (GetEnemy())
|
||
|
{
|
||
|
LaunchGrenade(GetEnemy());
|
||
|
m_OnLaunch.FireOutput(GetEnemy(), this);
|
||
|
m_flNextAttack = gpGlobals->curtime + m_nLaunchDelay;
|
||
|
}
|
||
|
// Otherwise look for enemy to fire at
|
||
|
else
|
||
|
{
|
||
|
GetSenses()->Look(m_flMaxAttackDist);
|
||
|
CBaseEntity* pBestEnemy = BestEnemy();
|
||
|
|
||
|
if (pBestEnemy)
|
||
|
{
|
||
|
LaunchGrenade(pBestEnemy);
|
||
|
m_OnLaunch.FireOutput(pBestEnemy, this);
|
||
|
m_flNextAttack = gpGlobals->curtime + m_nLaunchDelay;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Draw any debug text overlays
|
||
|
// Output : Current text offset from the top
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int CNPC_Launcher::DrawDebugTextOverlays(void)
|
||
|
{
|
||
|
int text_offset = BaseClass::DrawDebugTextOverlays();
|
||
|
|
||
|
if (m_debugOverlays & OVERLAY_TEXT_BIT)
|
||
|
{
|
||
|
char tempstr[512];
|
||
|
Q_snprintf(tempstr,sizeof(tempstr),"State: %s", (m_pfnThink) ? "On" : "Off" );
|
||
|
EntityText(text_offset,tempstr,0);
|
||
|
text_offset++;
|
||
|
|
||
|
Q_snprintf(tempstr,sizeof(tempstr),"LOS: %s", (FBitSet (m_spawnflags, SF_LAUNCHER_CHECK_LOS)) ? "On" : "Off" );
|
||
|
EntityText(text_offset,tempstr,0);
|
||
|
text_offset++;
|
||
|
}
|
||
|
return text_offset;
|
||
|
}
|