source-engine/game/server/hl2/npc_launcher.cpp

414 lines
12 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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;
}