sqwarmed/sdk_src/game/server/swarm/asw_sentry_top_flamer.cpp

377 lines
11 KiB
C++
Raw Normal View History

2024-08-29 19:18:30 -04:00
#include "cbase.h"
#include "props.h"
#include "asw_sentry_base.h"
#include "asw_sentry_top_flamer.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_flamer_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/flame_top.mdl"
LINK_ENTITY_TO_CLASS( asw_sentry_top_flamer, CASW_Sentry_Top_Flamer );
PRECACHE_REGISTER( asw_sentry_top_flamer );
IMPLEMENT_SERVERCLASS_ST(CASW_Sentry_Top_Flamer, DT_ASW_Sentry_Top_Flamer )
SendPropBool( SENDINFO( m_bFiring ) ),
SendPropFloat( SENDINFO( m_flPitchHack ) ),
END_SEND_TABLE()
BEGIN_DATADESC( CASW_Sentry_Top_Flamer )
END_DATADESC()
extern ConVar asw_weapon_max_shooting_distance;
extern ConVar asw_weapon_force_scale;
extern ConVar asw_difficulty_alien_health_step;
extern ConVar asw_sentry_debug_aim;
void CASW_Sentry_Top_Flamer::SetTopModel()
{
SetModel(SENTRY_TOP_MODEL);
}
#define ASW_SENTRY_FIRE_RATE 0.1f // time in seconds between each shot
#define ASW_SENTRY_FIRE_ANGLE_THRESHOLD 3
CASW_Sentry_Top_Flamer::CASW_Sentry_Top_Flamer( int projectileVelocity ) : m_bFiring(false), m_flPitchHack(false), m_nProjectileVelocity( projectileVelocity ? projectileVelocity : CASW_Weapon_Flamer::FLAMER_PROJECTILE_AIR_VELOCITY )
{
m_flShootRange = 375.0f;
// 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_Flamer::GetSentryDamage()
{
float flDamage = 4.0f;
if ( ASWGameRules() )
{
ASWGameRules()->ModifyAlienHealthBySkillLevel( flDamage );
}
return flDamage * ( GetSentryBase() ? GetSentryBase()->m_fDamageScale : 1.0f );
}
void CASW_Sentry_Top_Flamer::CheckFiring()
{
bool bShouldFire = false;
if ( HasAmmo() && m_hEnemy.IsValid() && m_hEnemy.Get())
{
float flDist = fabs(m_fGoalYaw - m_fCurrentYaw);
if (flDist > 180)
flDist = 360 - flDist;
// use some hysteresis
if (flDist < (IsFiring() ? ASW_SENTRY_FIRE_ANGLE_THRESHOLD * 1.1f : ASW_SENTRY_FIRE_ANGLE_THRESHOLD) )
{
bShouldFire = true;
}
}
if ( bShouldFire )
{
m_flFireHysteresisTime = gpGlobals->curtime + 0.5f;
}
else
{
bShouldFire = gpGlobals->curtime < m_flFireHysteresisTime ;
}
// turn firing on or off as appropriate
if ( IsFiring() != bShouldFire )
{
if ( bShouldFire )
StartFiring();
else
StopFiring();
}
Assert( IsFiring() == bShouldFire );
if ( bShouldFire )
{
Fire();
}
}
ITraceFilter *CASW_Sentry_Top_Flamer::GetVisibilityTraceFilter()
{
return new CTraceFilterSkipClassname( GetSentryBase(), "asw_flamer_projectile", COLLISION_GROUP_NONE );
}
float CASW_Sentry_Top_Flamer::GetYawTo(CBaseEntity* pEnt)
{
if (!pEnt)
return m_fDeployYaw;
Vector vEnemyVel = GetEnemyVelocity( pEnt );
// pEnt->GetVelocity( &vEnemyVel );
Vector vIdealAim = ProjectileIntercept( GetFiringPosition(),
GetProjectileVelocity(),
pEnt->WorldSpaceCenter(), vEnemyVel );
if ( vIdealAim.IsZero() )
return m_fDeployYaw;
else
return UTIL_VecToYaw(vIdealAim.Normalized());
}
void CASW_Sentry_Top_Flamer::Fire() RESTRICT
{
// determine the number of projectiles to be fired this frame.
// it's best to do this early by divsion and turn it into an int,
// rather than use eg
// for ( float shotTime = m_flLastShot; shotTime <= curTime - shotInterval ; shotTime+= shotInterval )
// because a branch on float comparison is really bad on 360.
// division and int conversion are slow too, but we're starting it
// early enough that hopefully the scheduler can hide latencies.
// hang on to the float original to avoid a LHS when doing the conversion in the other direction below
const float flNumShotsToFire = floor( (gpGlobals->curtime - m_flLastFireTime) / ASW_SENTRY_FIRE_RATE );
const int numShotsToFire = flNumShotsToFire ;
const Vector vecSrc = GetFiringPosition();
const QAngle &curAngles = GetAbsAngles();
Vector vecAiming;
AngleVectors( curAngles, &vecAiming );
// update our aim vector if we have an enemy
// (we might not, for the half-second period of hysteresis
// between losing an enemy and shutting off)
if ( !!m_hEnemy )
{
Vector videalToEnemy = m_hEnemy->WorldSpaceCenter() - vecSrc;
Vector vEnemyVelocity = GetEnemyVelocity();
//m_hEnemy->GetVelocity( &vEnemyVelocity );
if ( asw_sentry_debug_aim.GetBool() )
{
NDebugOverlay::HorzArrow( vecSrc, m_hEnemy->WorldSpaceCenter(), 2, 0, 255, 0, 255, true, 0.2f );
NDebugOverlay::HorzArrow( m_hEnemy->WorldSpaceCenter(), m_hEnemy->WorldSpaceCenter()+ vEnemyVelocity,
2, 0, 0, 255, 255, true, 0.2f );
}
Vector intercept = ProjectileIntercept( vecSrc, GetProjectileVelocity(),
m_hEnemy->WorldSpaceCenter(), vEnemyVelocity );
if ( intercept.IsZero( ) )
{
if ( asw_sentry_debug_aim.GetBool() )
NDebugOverlay::Cross( m_hEnemy->WorldSpaceCenter(), 8, 255, 0, 0 , true, 0.2f );
}
else
{
videalToEnemy = intercept;
if ( asw_sentry_debug_aim.GetBool() )
NDebugOverlay::HorzArrow( vecSrc, vecSrc + videalToEnemy, 2, 255, 255, 0, 255, true, 0.2f );
}
Vector vecAiming2DNormalized( vecAiming.x , vecAiming.y, 0 ) ;
vecAiming2DNormalized.NormalizeInPlace();
Vector vIdealToEnemyNormalized = videalToEnemy.Normalized();
float flIdeal2DLength = vIdealToEnemyNormalized.Length2D();
vecAiming.x = vecAiming2DNormalized.x * flIdeal2DLength;
vecAiming.y = vecAiming2DNormalized.y * flIdeal2DLength;
vecAiming.z = vIdealToEnemyNormalized.z ;
m_flPitchHack = -RAD2DEG(sin(vecAiming.z));
}
FireProjectiles( numShotsToFire, vecSrc, vecAiming );
// if we actually fired shots, roll the timers forward.
// leave the "cents" in place.
if ( numShotsToFire > 0 )
{
BaseClass::Fire();
float flMillisecondsFired = m_flLastFireTime;
m_flLastFireTime += flNumShotsToFire * ASW_SENTRY_FIRE_RATE;
if (ASWGameRules())
ASWGameRules()->m_fLastFireTime = m_flLastFireTime;
flMillisecondsFired = (m_flLastFireTime - flMillisecondsFired) * 25.0f;
Assert( flMillisecondsFired > 0 );
// subtract from ammo.
CASW_Sentry_Base* RESTRICT pSentryBase = GetSentryBase();
Assert( pSentryBase );
pSentryBase->OnFiredShots( (int)floor(flMillisecondsFired) );
//Msg( "%f == numShotsToFire - %d, ammo used %d\n", gpGlobals->curtime, numShotsToFire, (int)floor(flMillisecondsFired) );
}
}
void CASW_Sentry_Top_Flamer::StartFiring()
{
m_bFiring = true;
m_flLastFireTime = gpGlobals->curtime;
}
void CASW_Sentry_Top_Flamer::StopFiring()
{
m_bFiring = false;
}
/// Helper function for Fire() -- actually emit the "bullets" of flame or ice
void CASW_Sentry_Top_Flamer::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_Flamer_Projectile *pFlames = CASW_Flamer_Projectile::Flamer_Projectile_Create( GetSentryDamage(),
vecSrc + (projectileVel.Normalized() * BoundingRadius()), QAngle(0,0,0), projectileVel, rotSpeed,
this, pMarineDeployer, this );
pFlames->SetHurtIgnited( true );
}
}
/// Compute the necessary trajectory for a projectile to hit a moving object.
/// Given a static gun position, a moving target, the target's velocity vector,
/// and the SCALAR speed of the projectile, return the vector direction for
/// the projectile necessary to hit the target.
/// Ie, solves (A - S) + Vt = Pt where A is the enemy position,
/// S the gun position, V the enemy velocity, and P the projectile velocity,
/// where magnitude is known.
/// It's a quadratic, so if there are two solutions, returns the sooner one.
/// If there are no solutions, returns 0,0,0
/// If a Time output parameter is given, the (t) parameter from the quadratic
/// is written there. If there is no solution, time will be negative.
Vector ProjectileIntercept( const Vector &vProjectileOrigin, const float fProjectileVelocity,
const Vector &vTargetOrigin, const Vector &vTargetDirection,
float * RESTRICT pflTime )
{
#pragma message("TODO: make SIMD")
/* math:
let B = (A - S)
|P| = Q
B + Vt = Pt
(B + Vt)^2 = (P.P)t^2 = (Q^2)(t^2)
B^2 + 2t(B.V) + (V^2)(t^2) = (Q^2)(t^2)
so
(t^2)( V^2 - Q^2 ) + 2t(B.V) + B^2 = 0
thus by quadratic
t = ( -2(B.V) +- sqrt( 4(B.V)^2 - 4(V^2-Q^2)(B^2) ) ) /
2(V^2-Q^2)
= ( -(B.V) +- sqrt( (B.V)^2 - (V^2-Q^2)(B^2) ) ) /
(V^2-Q^2)
and therefore P = (B+V)/t
*/
const Vector vB = vTargetOrigin - vProjectileOrigin;
// quadratic term A
const float fQA = vTargetDirection.LengthSqr() - ( fProjectileVelocity*fProjectileVelocity );
// quadratic term B
const float fDotVB = vTargetDirection.Dot( vB );
// quadratic term C
const float fDotBB = vB.LengthSqr();
if ( fQA == 0 )
{
// linear system only
// so 0 = 2t(B.V) + B.B
// -(B.B)/2(B.V) = t
// which is the same as saying that if the projectile and target have the same
// velocity exactly, a solution is only possible if the gun is "ahead" of the
// target's path
float t = -fDotBB / ( 2 * fDotVB );
if ( pflTime )
*pflTime = t;
return ( t > 0 ?
(vB + vTargetDirection)*FastRecip(t) :
Vector(0,0,0) );
}
// else not linear
const float discrim = fDotVB*fDotVB - fQA*fDotBB;
if ( discrim < 0 )
{
// there is no solution
if ( pflTime )
*pflTime = -1;
return Vector(0,0,0);
}
else
{
const float discrimSqrt = FastSqrtEst(discrim);
#if 1
// select the + or - radicand to match B's sign
// to improve precision, then extract the other root
float tmp = -fsel( fDotVB, fDotVB + discrimSqrt, fDotVB - discrimSqrt );
const float t1 = tmp/fQA;
const float t2 = fDotBB/tmp;
#else
// this is the simpler quadratic implementation, but it can suffer
// from bad imprecision if the signs of the params mismatch and
// they differ only slightly
const float t1 = ( -fDotVB + discrimSqrt )/fQA;
const float t2 = ( -fDotVB - discrimSqrt )/fQA;
#endif
float t;
if ( t1 < 0 )
{
t = t2;
}
else if ( t2 < 0 )
{
t = t1;
}
else
{
t = fpmin( t1, t2 );
}
if ( pflTime )
*pflTime = t;
if ( t > 0 )
{
Vector retval = vB/t + vTargetDirection ;
#ifdef DBGFLAG_ASSERT
Vector interceptA = (vProjectileOrigin + ( t * retval ));
Vector interceptB = ( vTargetOrigin + vTargetDirection * t );
float err = (interceptA - interceptB).Length();
Assert( err < 0.001f );
#endif
return retval;
}
else
return Vector(0,0,0);
}
}