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