232 lines
7.3 KiB
C++
232 lines
7.3 KiB
C++
#include "cbase.h"
|
|
#include "props.h"
|
|
#include "asw_sentry_base.h"
|
|
#include "asw_sentry_top_icer.h"
|
|
#include "asw_player.h"
|
|
#include "asw_marine.h"
|
|
#include "ammodef.h"
|
|
#include "asw_gamerules.h"
|
|
#include "beam_shared.h"
|
|
#include "asw_weapon_flamer_shared.h"
|
|
#include "asw_extinguisher_projectile.h"
|
|
#include "shot_manipulator.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#define SENTRY_TOP_MODEL "models/sentry_gun/freeze_top.mdl"
|
|
|
|
LINK_ENTITY_TO_CLASS( asw_sentry_top_icer, CASW_Sentry_Top_Icer );
|
|
PRECACHE_REGISTER( asw_sentry_top_icer );
|
|
|
|
|
|
IMPLEMENT_SERVERCLASS_ST( CASW_Sentry_Top_Icer, DT_ASW_Sentry_Top_Icer )
|
|
END_SEND_TABLE()
|
|
|
|
BEGIN_DATADESC( CASW_Sentry_Top_Icer )
|
|
END_DATADESC()
|
|
|
|
extern ConVar asw_weapon_max_shooting_distance;
|
|
extern ConVar asw_weapon_force_scale;
|
|
extern ConVar asw_difficulty_alien_health_step;
|
|
|
|
ConVar asw_sentry_debug_aim("asw_sentry_debug_aim", "0", FCVAR_CHEAT, "Draw debug lines for sentry gun aim");
|
|
|
|
|
|
#define ASW_SENTRY_FIRE_RATE 0.1f // time in seconds between each shot
|
|
#define ASW_SENTRY_FIRE_ANGLE_THRESHOLD 3
|
|
|
|
void CASW_Sentry_Top_Icer::SetTopModel()
|
|
{
|
|
SetModel(SENTRY_TOP_MODEL);
|
|
}
|
|
|
|
|
|
|
|
CASW_Sentry_Top_Icer::CASW_Sentry_Top_Icer() : CASW_Sentry_Top_Flamer(CASW_Weapon_Flamer::EXTINGUISHER_PROJECTILE_AIR_VELOCITY)
|
|
{
|
|
m_flShootRange = 300;
|
|
// increase turn rate until I get better leading code in (so it can actually hit something)
|
|
m_fTurnRate *= 3.0f;
|
|
}
|
|
|
|
/// @TODO attrib hooks
|
|
int CASW_Sentry_Top_Icer::GetSentryDamage()
|
|
{
|
|
return 1; // copied from flamer? i guess it doesn't come from script??
|
|
|
|
/*
|
|
float flDamage = 25.0f;
|
|
float flMultiplier = 1.0f;
|
|
if ( ASWGameRules() )
|
|
{
|
|
float fDiff = ASWGameRules()->GetMissionDifficulty() - 5;
|
|
flMultiplier = 1.0 + fDiff * asw_difficulty_alien_health_step.GetFloat();
|
|
}
|
|
|
|
//Msg ( "Damage = %f\n", flDamage );
|
|
return (flDamage * flMultiplier) * GetSentryBase()->m_fDamageScale;
|
|
*/
|
|
}
|
|
|
|
|
|
ITraceFilter *CASW_Sentry_Top_Icer::GetVisibilityTraceFilter()
|
|
{
|
|
return new CTraceFilterSkipClassname( GetSentryBase(), "asw_extinguisher_projectile", COLLISION_GROUP_NONE );
|
|
}
|
|
|
|
bool CASW_Sentry_Top_Icer::IsValidEnemy( CAI_BaseNPC *pNPC )
|
|
{
|
|
if ( !pNPC )
|
|
return false;
|
|
|
|
if ( !BaseClass::IsValidEnemy( pNPC ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// don't freeze current enemy past 60% unless there's no one better
|
|
// around (prevents one alien from being frozen solid while rest rush past)
|
|
// don't pick new enemies that are more than 60%
|
|
if ( pNPC == m_hEnemy.Get() )
|
|
return pNPC->GetFrozenAmount( ) < fsel( m_flEnemyOverfreezePermittedUntil - gpGlobals->curtime , 0.9f , 0.6f );
|
|
else
|
|
return pNPC->GetFrozenAmount( ) < 0.6f;
|
|
}
|
|
|
|
|
|
/// Helper function for Fire() -- actually emit the "bullets" of flame or ice
|
|
void CASW_Sentry_Top_Icer::FireProjectiles( int numShotsToFire, ///< number of pellets to fire this frame
|
|
const Vector &vecSrc, ///< origin for bullets
|
|
const Vector &vecAiming, ///< aim direction for bullets
|
|
const AngularImpulse &rotSpeed )
|
|
{
|
|
CShotManipulator Manipulator( vecAiming );
|
|
|
|
/*
|
|
CASW_Marine * RESTRICT const pMarineDeployer = GetSentryBase()->m_hDeployer.Get();
|
|
Assert( pMarineDeployer );
|
|
*/
|
|
|
|
for ( int i = 0 ; i < numShotsToFire ; i++ )
|
|
{
|
|
// create a pellet at some random spread direction
|
|
Vector projectileVel = Manipulator.GetShotDirection(); // Manipulator.ApplySpread(GetBulletSpread());
|
|
|
|
projectileVel *= GetProjectileVelocity();
|
|
projectileVel *= (1.0 + (0.1 * random->RandomFloat(-1,1)));
|
|
CASW_Extinguisher_Projectile *pProjectile = CASW_Extinguisher_Projectile::Extinguisher_Projectile_Create(
|
|
vecSrc + (projectileVel.Normalized() * BoundingRadius()), QAngle(0,0,0), projectileVel, rotSpeed,
|
|
this /*, pMarineDeployer*/ );
|
|
if ( pProjectile )
|
|
{
|
|
pProjectile->SetFreezeAmount( 0.4f );
|
|
}
|
|
}
|
|
}
|
|
|
|
extern ConVar asw_sentry_friendly_target;
|
|
CAI_BaseNPC * CASW_Sentry_Top_Icer::SelectOptimalEnemy()
|
|
{
|
|
// prioritize unfrozen aliens who are going to leave the cone soon.
|
|
// prioritize aliens less the more frozen they get.
|
|
CUtlVectorFixedGrowable< CAI_BaseNPC *,16 > candidates;
|
|
CUtlVectorFixedGrowable< float, 16 > candidatescores;
|
|
|
|
// search through all npcs, any that are in LOS and have health
|
|
CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
|
|
for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
|
|
{
|
|
if (ppAIs[i]->GetHealth() > 0 && CanSee(ppAIs[i]))
|
|
{
|
|
// don't shoot marines
|
|
if ( !asw_sentry_friendly_target.GetBool() && ppAIs[i]->Classify() == CLASS_ASW_MARINE )
|
|
continue;
|
|
|
|
if ( ppAIs[i]->Classify() == CLASS_SCANNER )
|
|
continue;
|
|
|
|
if ( !IsValidEnemy( ppAIs[i] ) )
|
|
continue;
|
|
|
|
candidates.AddToTail( ppAIs[i] );
|
|
}
|
|
}
|
|
|
|
// bail out if we don't have anyone
|
|
if ( candidates.Count() < 1 )
|
|
return NULL;
|
|
else if ( candidates.Count() == 1 ) // just one candidate is an obvious result
|
|
return candidates[0];
|
|
|
|
// collect some statistics on the available enemies as a whole
|
|
float flFreezeAmtMin, flFreezeAmtMax;
|
|
flFreezeAmtMin = flFreezeAmtMax = candidates[0]->GetFrozenAmount();
|
|
for ( int i = candidates.Count() - 1; i > 0 ; --i )
|
|
{
|
|
const float frz = candidates[i]->GetFrozenAmount();
|
|
flFreezeAmtMin = fpmin( flFreezeAmtMin, frz );
|
|
flFreezeAmtMax = fpmax( flFreezeAmtMax, frz );
|
|
}
|
|
if ( flFreezeAmtMin > 0.35f )
|
|
{
|
|
m_flEnemyOverfreezePermittedUntil = gpGlobals->curtime + 0.5f;
|
|
}
|
|
|
|
// score each of the candidates
|
|
candidatescores.EnsureCount( candidates.Count() );
|
|
for ( int i = candidates.Count() - 1; i >= 0 ; --i )
|
|
{
|
|
CAI_BaseNPC * RESTRICT pCandidate = candidates[i];
|
|
// is the candidate moving into or out of the cone?
|
|
Vector vCandVel = GetEnemyVelocity(pCandidate);
|
|
Vector vMeToTarget = pCandidate->GetAbsOrigin() - GetFiringPosition();
|
|
Vector vBaseForward = UTIL_YawToVector( m_fDeployYaw );
|
|
|
|
// crush everything to 2d for simplicity
|
|
vMeToTarget.z = 0;
|
|
vCandVel.z = 0;
|
|
vBaseForward.z = 0;
|
|
|
|
Vector velCross = vBaseForward.Cross(vCandVel); // this encodes also some info on perpendicularity
|
|
Vector vAimCross = vBaseForward.Cross(vMeToTarget);
|
|
bool bTargetHeadedOutOfCone = !vCandVel.IsZero() && velCross.z * vAimCross.z >= 0; // true if same sign
|
|
float flConeLeavingUrgency;
|
|
|
|
if ( bTargetHeadedOutOfCone )
|
|
{
|
|
flConeLeavingUrgency = fabs( velCross.z / vCandVel.Length2D() );
|
|
// just the sin; varies 0..1 where 1 means moving perpendicular to my aim
|
|
}
|
|
else
|
|
{
|
|
flConeLeavingUrgency = 0; // not at threat of leaving just yet
|
|
}
|
|
|
|
// the angle between my current yaw and what's needed to hit the target
|
|
float flSwivelNeeded = fabs( UTIL_AngleDiff( // i wish we weren't storing euler angles
|
|
UTIL_VecToYaw( vMeToTarget ) , m_fDeployYaw ) );
|
|
flSwivelNeeded /= ASW_SENTRY_ANGLE; // normalize to 0..2
|
|
|
|
float flFreezeNeeded = 1 - pCandidate->GetFrozenAmount();
|
|
|
|
candidatescores[i] = Vector( 3.0f, -1.5f, 2.0f ).Dot(
|
|
Vector(flConeLeavingUrgency, flSwivelNeeded, flFreezeNeeded) );
|
|
}
|
|
// find the highest scoring candidate
|
|
int best = 0;
|
|
for ( int i = 1 ; i < candidatescores.Count() ; ++i )
|
|
{
|
|
if ( candidatescores[i] > candidatescores[best] )
|
|
best = i;
|
|
}
|
|
|
|
// NDebugOverlay::EntityBounds(candidates[best], 255, 255, 0, 255, 0.2f );
|
|
|
|
return candidates[best];
|
|
}
|
|
|
|
|
|
|
|
|