diff options
Diffstat (limited to 'game/server/tf2/tf_obj_sentrygun.cpp')
| -rw-r--r-- | game/server/tf2/tf_obj_sentrygun.cpp | 1178 |
1 files changed, 1178 insertions, 0 deletions
diff --git a/game/server/tf2/tf_obj_sentrygun.cpp b/game/server/tf2/tf_obj_sentrygun.cpp new file mode 100644 index 0000000..2ea32dc --- /dev/null +++ b/game/server/tf2/tf_obj_sentrygun.cpp @@ -0,0 +1,1178 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Defender's sentrygun objects +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_obj.h" +#include "tf_obj_sentrygun.h" +#include "tf_obj_dragonsteeth.h" +#include "tf_obj_tower.h" +#include "tf_obj_sandbag_bunker.h" +#include "tf_obj_bunker.h" +#include "tf_obj_mapdefined.h" +#include "tf_gamerules.h" +#include "gamerules.h" +#include "ammodef.h" +#include "plasmaprojectile.h" +#include "tf_class_recon.h" +#include "sendproxy.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "grenade_rocket.h" +#include "VGuiScreen.h" + +extern short g_sModelIndexFireball; + +#define MAX_SUPPRESSION_TIME 5.0 // Max amount of time to supress for + +// Sentrygun size +#define SENTRYGUN_MINS Vector(-16, -16, 0) +#define SENTRYGUN_MAXS Vector( 16, 16, 65) + +//============================================================================= +// Link and precache all the sentrygun types +LINK_ENTITY_TO_CLASS(obj_sentrygun_plasma, CObjectSentrygunPlasma); +LINK_ENTITY_TO_CLASS(obj_sentrygun_rocketlauncher, CObjectSentrygunRocketlauncher); +PRECACHE_REGISTER(obj_sentrygun_plasma); +PRECACHE_REGISTER(obj_sentrygun_rocketlauncher); + +//============================================================================= +// Data description +BEGIN_DATADESC( CObjectSentrygun ) + + DEFINE_THINKFUNC( SentryRotate ), + DEFINE_THINKFUNC( Attack ), + +END_DATADESC() + +// Sentrygun team-only vars. +BEGIN_SEND_TABLE_NOBASE( CObjectSentrygun, DT_SentrygunTeamOnlyVars ) + SendPropInt( SENDINFO(m_iAmmo), 9 ), +END_SEND_TABLE() + +#define SENTRY_ANIMATION_PARITY_BITS 2 + +IMPLEMENT_SERVERCLASS_ST(CObjectSentrygun, DT_ObjectSentrygun) + SendPropInt( SENDINFO( m_iBaseTurnRate ), 3, SPROP_UNSIGNED ), + SendPropEHandle( SENDINFO( m_hEnemy ) ), + SendPropDataTable( "teamonly", 0, &REFERENCE_SEND_TABLE( DT_SentrygunTeamOnlyVars ), SendProxy_OnlyToTeam ), + SendPropInt( SENDINFO(m_bTurtled), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_nAnimationParity ), (1<<SENTRY_ANIMATION_PARITY_BITS), SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_nOrientationParity ), 1, SPROP_UNSIGNED ), +END_SEND_TABLE(); + +ConVar obj_sentrygun_plasma_health( "obj_sentrygun_plasma_health","200", FCVAR_NONE, "Plasma sentrygun health" ); +ConVar obj_sentrygun_plasma_range( "obj_sentrygun_plasma_range","1500", FCVAR_NONE, "Plasma sentrygun's shot range" ); +ConVar obj_sentrygun_rocketlauncher_health( "obj_sentrygun_rocketlauncher_health","250", FCVAR_NONE, "Rocket Launcher sentrygun health" ); +ConVar obj_sentrygun_range_mid( "obj_sentrygun_range_mid","768", FCVAR_NONE, "Sentrygun's mid targeting range. Targets beyond this need to be in the viewcone to be seen." ); +ConVar obj_sentrygun_range_max( "obj_sentrygun_range_max","1600", FCVAR_NONE, "Sentrygun's max targeting range." ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectSentrygun::CObjectSentrygun( void ) +{ + UseClientSideAnimation(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::Spawn( void ) +{ + m_bSmarter = false; + m_bSensors = false; + m_bSuppressing = false; + m_bTurtled = false; + m_bTurtling = false; + m_flTurtlingFinishedAt = 0; + + SetViewOffset( Vector(0,0,22) ); + + // Setup + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + UTIL_SetSize(this, SENTRYGUN_MINS, SENTRYGUN_MAXS); + + BaseClass::Spawn(); + + // Start searching for enemies + m_hEnemy = NULL; + m_hDesignatedEnemy = NULL; + SetThink( SentryRotate ); + SetNextThink( gpGlobals->curtime + 0.5f ); + m_flNextLook = gpGlobals->curtime; + + SetTechnology( false, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::Precache() +{ + PrecacheModel( SG_PLASMA_MODEL ); + PrecacheModel( SG_ROCKETLAUNCHER_MODEL ); + PrecacheVGuiScreen( "screen_obj_sentrygun" ); + + PrecacheScriptSound( "ObjectSentrygun.ResupplyAmmo" ); + PrecacheScriptSound( "ObjectSentrygun.Idle" ); + PrecacheScriptSound( "ObjectSentrygun.FoundTarget" ); + PrecacheScriptSound( "ObjectSentrygun.Turtle" ); + PrecacheScriptSound( "ObjectSentrygun.UnTurtle" ); + PrecacheScriptSound( "ObjectSentrygun.Fire" ); + PrecacheScriptSound( "ObjectSentrygunRocketlauncher.Fire" ); + +} + +//----------------------------------------------------------------------------- +// Purpose: Gets info about the control panels +//----------------------------------------------------------------------------- +void CObjectSentrygun::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "screen_obj_sentrygun"; +} + +//----------------------------------------------------------------------------- +// Purpose: Hide the base of the gun if it's on an attachment +//----------------------------------------------------------------------------- +void CObjectSentrygun::SetupAttachedVersion( void ) +{ + BaseClass::SetupAttachedVersion(); + + SetBodygroup( 1, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::SetupUnattachedVersion( void ) +{ + BaseClass::SetupUnattachedVersion(); + + SetBodygroup( 1, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::FinishedBuilding( void ) +{ + BaseClass::FinishedBuilding(); + + // Orient it + m_vecCurAngles.y = UTIL_AngleMod( GetLocalAngles().y ); + RecomputeOrientation(); +} + +//----------------------------------------------------------------------------- +// Called when a rotation happens +//----------------------------------------------------------------------------- +void CObjectSentrygun::RecomputeOrientation( ) +{ + ResetOrientation(); + + m_iRightBound = UTIL_AngleMod( m_vecCurAngles.y - 50); + m_iLeftBound = UTIL_AngleMod( m_vecCurAngles.y + 50); + if ( m_iRightBound > m_iLeftBound ) + { + m_iRightBound = m_iLeftBound; + m_iLeftBound = UTIL_AngleMod( m_vecCurAngles.y - 50); + } + + // Start it rotating + m_vecGoalAngles.y = m_iRightBound; + m_vecGoalAngles.x = m_vecCurAngles.x = 0; + m_bTurningRight = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Handle commands sent from vgui panels on the client +//----------------------------------------------------------------------------- +bool CObjectSentrygun::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg ) +{ + if ( FStrEq( pCmd, "addammo" ) ) + { + if ( TakeAmmoFrom( pPlayer ) ) + { + // We got some ammo, so make a sound + CPASAttenuationFilter filter( pPlayer, "ObjectSentrygun.ResupplyAmmo" ); + EmitSound( filter, pPlayer->entindex(), "ObjectSentrygun.ResupplyAmmo" ); + } + return true; + } + + return BaseClass::ClientCommand( pPlayer, pCmd, pArg ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the player gave the sentrygun some ammo +//----------------------------------------------------------------------------- +bool CObjectSentrygun::TakeAmmoFrom( CBaseTFPlayer *pPlayer ) +{ + // Do I need ammo? + if ( m_iAmmo >= m_iMaxAmmo ) + return false; + + // Try to fill the sentry up a bit at a time + int iRoundsToGive = 10; + iRoundsToGive = MIN( iRoundsToGive, (m_iMaxAmmo - m_iAmmo) ); + iRoundsToGive = MIN( iRoundsToGive, pPlayer->GetAmmoCount( m_iAmmoType ) ); + if ( !iRoundsToGive ) + return false; + + // Give me the ammo + pPlayer->RemoveAmmo( iRoundsToGive, m_iAmmoType ); + m_iAmmo += iRoundsToGive; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Resupply has taken damage +//----------------------------------------------------------------------------- +int CObjectSentrygun::OnTakeDamage( const CTakeDamageInfo &info ) +{ + int iDamage = BaseClass::OnTakeDamage( info ); + + return iDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: Object has been blown up +//----------------------------------------------------------------------------- +void CObjectSentrygun::Killed( void ) +{ + // Tell the player he's lost this resupply beacon + if ( GetOwner() ) + { + GetOwner()->OwnedObjectDestroyed( this ); + } + + BaseClass::Killed(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::RestartAnimation( void ) +{ + // Increment and mask parity counter + m_nAnimationParity += 1; + m_nAnimationParity &= ( (1<<SENTRY_ANIMATION_PARITY_BITS) - 1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::ResetOrientation() +{ + m_nOrientationParity = !m_nOrientationParity; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::SetSentryAnim( TFTURRET_ANIM anim ) +{ + if ( GetSequence() != anim ) + { + switch(anim) + { + case TFTURRET_ANIM_FIRE: + case TFTURRET_ANIM_SPIN: + if ( GetSequence() != TFTURRET_ANIM_FIRE && GetSequence() != TFTURRET_ANIM_SPIN ) + { + RestartAnimation(); + } + break; + default: + RestartAnimation(); + break; + } + + ResetSequence( anim ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle movement of the turret +//----------------------------------------------------------------------------- +bool CObjectSentrygun::MoveTurret(void) +{ + bool bMoved = 0; + + // any x movement? + if ( m_vecCurAngles.x != m_vecGoalAngles.x ) + { + float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ; + + m_vecCurAngles.x += 0.1 * (m_iBaseTurnRate * 5) * flDir; + + // if we started below the goal, and now we're past, peg to goal + if (flDir == 1) + { + if (m_vecCurAngles.x > m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + else + { + if (m_vecCurAngles.x < m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + + m_fBoneYRotator = m_vecCurAngles.x; + + bMoved = 1; + } + + if ( m_vecCurAngles.y != m_vecGoalAngles.y ) + { + float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ; + float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y); + bool bReversed = false; + + if (flDist > 180) + { + flDist = 360 - flDist; + flDir = -flDir; + bReversed = true; + } + + if (m_hEnemy == NULL && !m_bSuppressing) + { + if (flDist > 30) + { + if (m_fTurnRate < m_iBaseTurnRate * 20) + { + m_fTurnRate += m_iBaseTurnRate; + } + } + else + { + // Slow down + if ( m_fTurnRate > (m_iBaseTurnRate * 5) ) + m_fTurnRate -= m_iBaseTurnRate; + } + } + else + { + // When tracking enemies, move faster and don't slow + if (flDist > 30) + { + if (m_fTurnRate < m_iBaseTurnRate * 30) + { + m_fTurnRate += m_iBaseTurnRate * 3; + } + } + } + + m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir; + + // if we passed over the goal, peg right to it now + if (flDir == -1) + { + if ( (bReversed == false && m_vecGoalAngles.y > m_vecCurAngles.y) || (bReversed == true && m_vecGoalAngles.y < m_vecCurAngles.y) ) + m_vecCurAngles.y = m_vecGoalAngles.y; + } + else + { + if ( (bReversed == false && m_vecGoalAngles.y < m_vecCurAngles.y) || (bReversed == true && m_vecGoalAngles.y > m_vecCurAngles.y) ) + m_vecCurAngles.y = m_vecGoalAngles.y; + } + + if (m_vecCurAngles.y < 0) + m_vecCurAngles.y += 360; + else if (m_vecCurAngles.y >= 360) + m_vecCurAngles.y -= 360; + + if (flDist < (0.05 * m_iBaseTurnRate)) + m_vecCurAngles.y = m_vecGoalAngles.y; + + m_fBoneXRotator = m_vecCurAngles.y - GetLocalAngles().y; + + bMoved = 1; + } + + if ( !bMoved || !m_fTurnRate ) + { + m_fTurnRate = m_iBaseTurnRate; + } + + return bMoved; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true is the passed ent is in the caller's forward view cone. +// The dot product is performed in 2d, making the view cone infinitely tall. +//----------------------------------------------------------------------------- +bool CObjectSentrygun::FInViewCone( CBaseEntity *pEntity ) +{ + float flDot; + + Vector vecFacingDir; + AngleVectors( m_vecCurAngles, &vecFacingDir ); + Vector vecLOS = ( pEntity->GetAbsOrigin() - GetAbsOrigin() ); + flDot = DotProduct( vecLOS , vecFacingDir ); + + if ( flDot > VIEW_FIELD_NARROW ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Check the shield's values +//----------------------------------------------------------------------------- +void CObjectSentrygun::CheckShield( void ) +{ + if ( m_nRenderFX == kRenderFxNone ) + return; +} + +//----------------------------------------------------------------------------- +// Purpose: Rotate and scan for targets +//----------------------------------------------------------------------------- +void CObjectSentrygun::SentryRotate( void ) +{ + SetNextThink( gpGlobals->curtime + 0.1f ); + + CheckShield(); + + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return; + + // If we're turtling, see if we're finished yet + if ( IsTurtling() ) + { + if ( m_flTurtlingFinishedAt <= gpGlobals->curtime ) + { + m_bTurtling = false; + if ( m_bTurtled ) + { + AddSolidFlags( FSOLID_NOT_SOLID ); + m_takedamage = DAMAGE_NO; + } + } + return; + } + + // Turtling sentryguns don't think + if ( IsTurtled() ) + return; + + // animate + SetSentryAnim( TFTURRET_ANIM_SPIN ); + + // Abort if it's not time to search for enemies + if ( m_flNextLook < gpGlobals->curtime ) + { + m_flNextLook = gpGlobals->curtime + 1.0; + + // Look for a target + m_hEnemy = FindTarget(); + + if ( m_hEnemy != NULL ) + { + FoundTarget(); + return; + } + } + + // Rotate + if ( !MoveTurret() ) + { + // Play a sound occasionally + if ( random->RandomFloat(0, 1) < 0.02 ) + { + EmitSound( "ObjectSentrygun.Idle" ); + } + + // Switch rotation direction + if (m_bTurningRight) + { + m_bTurningRight = false; + m_vecGoalAngles.y = m_iLeftBound; + } + else + { + m_bTurningRight = true; + m_vecGoalAngles.y = m_iRightBound; + } + + // Randomly look up and down a bit + if ( random->RandomFloat(0, 1) < 0.3 ) + { + m_vecGoalAngles.x = (int)random->RandomFloat(-10,10); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if there's a valid target in sight +//----------------------------------------------------------------------------- +CBaseEntity *CObjectSentrygun::FindTarget( void ) +{ + CBaseEntity *pHighestPriorityTarget = NULL; + float fHighestPriority = 0; + + // If I have a designated enemy, and it's valid, assume it will be the target, unless something higher priority shows up. + if ( m_hDesignatedEnemy.Get() && ValidTarget( m_hDesignatedEnemy) ) + { + fHighestPriority = GetPriority( m_hDesignatedEnemy ); + pHighestPriorityTarget = m_hDesignatedEnemy; + } + + // Find a target. + CBaseEntity *pList[1024]; + Vector delta( 2048, 2048, 2048 ); + int count = UTIL_EntitiesInBox( pList, 1024, GetAbsOrigin() - delta, GetAbsOrigin() + delta, FL_CLIENT|FL_NPC|FL_OBJECT ); + + for ( int i = 0; i < count; i++ ) + { + if( !pList[i] ) + continue; + + if ( pList[i] == this ) + continue; + + float fPriority = GetPriority( pList[i] ); + + if( !pHighestPriorityTarget || (fPriority > fHighestPriority) ) + { + + if ( ValidTarget( pList[i] ) ) + { + pHighestPriorityTarget = pList[i]; + fHighestPriority = fPriority; + } + } + } + + return pHighestPriorityTarget; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the priority of the target +//----------------------------------------------------------------------------- +float CObjectSentrygun::GetPriority( CBaseEntity *pTarget ) +{ + // Players + if ( pTarget->IsPlayer() ) + { + return 20; + } + + // NPCs + if ( pTarget->GetFlags() & FL_NPC ) + return 10; + + // Objects + if ( pTarget->Classify() == CLASS_MILITARY ) + { + // Sentryguns are highest priority + CBaseObject *pObject = (CBaseObject *)pTarget; + if ( pObject->IsSentrygun() ) + return 5; + return 2; + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the sentry targeting range the target is in +//----------------------------------------------------------------------------- +int CObjectSentrygun::Range( CBaseEntity *pTarget ) +{ + Vector vecOrg = EyePosition(); + Vector vecTargetOrg = pTarget->EyePosition(); + + int iDist = ( vecTargetOrg - vecOrg ).Length(); + + // Sensors increase targeting range + if ( m_bSensors ) + { + iDist *= 0.75; + } + + if (iDist < obj_sentrygun_range_mid.GetFloat() ) + return RANGE_NEAR; + if (iDist < obj_sentrygun_range_max.GetFloat() ) + return RANGE_MID; + return RANGE_FAR; +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if a target's valid +//----------------------------------------------------------------------------- +bool CObjectSentrygun::ValidTarget( CBaseEntity *pTarget ) +{ + // Make sure we aren't borked: + if ( !pTarget ) + return false; + + // Don't attack things that have already died + if ( !pTarget->IsAlive() ) + return false; + + // Don't attack things that cant be hurt + if ( pTarget->m_takedamage != DAMAGE_YES ) + return false; + + // Don't shoot at objects on the neutral team. + if( !pTarget->IsInAnyTeam() ) + return false; + + + + // Ignore camoed players + if ( pTarget->IsPlayer() ) + { + CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)pTarget; + + if ( InSameTeam( pPlayer ) ) + return false; + if ( pPlayer->IsClass( TFCLASS_UNDECIDED ) ) + return false; + if ( pPlayer->GetCamouflageAmount() >= 30.0f ) + return false; + + } + else + { + // Only attack enemies. + if ( InSameTeam( pTarget) ) + return false; + } + + if ( pTarget->GetFlags() & FL_NOTARGET ) + return false; + + if ( !FVisible(pTarget) ) + return false; + + // Ignore certain enemy infrastructure type objects: + CBaseObject *pObject = dynamic_cast< CBaseObject* >(pTarget); + if ( pObject ) + { + // Make sure it's not placing + if ( pObject->IsPlacing() ) + return false; + + // Ignore upgrades + if ( pObject->IsAnUpgrade() ) + return false; + + // Ignore defensive structures + if ( IsObjectADefensiveBuilding( pObject->GetType() ) ) + return false; + + // Ignore mapdefined objects + if ( pObject->GetType() == OBJ_MAPDEFINED ) + return false; + } + + // Make sure there's nothing inbetween us + Vector vecSrc = EyePosition(); + + // Now make sure there isn't something other than team players in the way. + trace_t tr; + CTraceFilterSimpleList sentryFilter( COLLISION_GROUP_NONE ); + sentryFilter.AddEntityToIgnore( GetOwner() ); + sentryFilter.AddEntityToIgnore( this ); + sentryFilter.AddEntityToIgnore( GetMoveParent() ); + UTIL_TraceLine( vecSrc, pTarget->WorldSpaceCenter(), MASK_SHOT, &sentryFilter, &tr ); + CBaseEntity *pEntity = tr.m_pEnt; + if ( (tr.fraction < 1.0) && ( pEntity != pTarget ) ) + return false; + + int iRange = Range(pTarget); + if ( iRange == RANGE_FAR ) + return false; + + // Better sensors allow them to track irrespective of facing + if ( iRange == RANGE_MID && (!FInViewCone(pTarget) && !m_bSensors) ) + return false; + + // Don't shoot at turtled sentry guns. + CObjectSentrygun *pSentry = dynamic_cast< CObjectSentrygun* >( pTarget ); + if ( pSentry && pSentry->IsTurtled() ) + return false; + + // Don't shoot at targets blocked by enemy shields + bool bBlocked = TFGameRules()->IsBlockedByEnemyShields( GetAbsOrigin(), pTarget->GetAbsOrigin(), GetTeamNumber() ); + if( bBlocked ) + return false; + + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the sentry has some ammo to fire +//----------------------------------------------------------------------------- +bool CObjectSentrygun::HasAmmo( void ) +{ + return (m_iAmmo > 0); +} + +//----------------------------------------------------------------------------- +// Purpose: We've found a valid target +//----------------------------------------------------------------------------- +void CObjectSentrygun::FoundTarget() +{ + if ( HasAmmo() ) + { + EmitSound( "ObjectSentrygun.FoundTarget" ); + } + + SetThink( Attack ); + SetNextThink( gpGlobals->curtime + 0.1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Make sure our target is still valid, and if so, fire at it +//----------------------------------------------------------------------------- +void CObjectSentrygun::Attack( void ) +{ + SetNextThink( gpGlobals->curtime + 0.1f ); + + CheckShield(); + + // Turtling sentryguns don't attack + if ( IsTurtled() ) + { + SetThink( SentryRotate ); + return; + } + + // I can't do anything if I'm not active + if ( !ShouldBeActive() ) + return; + + // Make sure our target is still valid and that we've still got ammo + if ( m_bSuppressing && HasAmmo() ) + { + if ( gpGlobals->curtime > (m_flStartedSuppressing + MAX_SUPPRESSION_TIME) ) + { + m_bSuppressing = false; + SetThink( SentryRotate ); + return; + } + + // Check to see if we can find a valid target to switch to + m_hEnemy = FindTarget(); + if ( m_hEnemy ) + { + // Stop supressing and target this new enemy + m_bSuppressing = false; + m_flStartedSuppressing = 0; + FoundTarget(); + } + } + else if ( !ValidTarget(m_hEnemy) || HasAmmo() == false ) + { + m_hEnemy = NULL; + + // Smarter sentryguns will suppression fire for a few seconds + if ( m_bSmarter && WillSuppress() && HasAmmo() ) + { + m_bSuppressing = true; + m_flStartedSuppressing = gpGlobals->curtime; + } + else + { + RecomputeOrientation(); + SetThink( SentryRotate ); + return; + } + } + + // If I have a designated enemy, and it's valid, target it instead of my current enemy + if ( m_hDesignatedEnemy.Get() && (m_hEnemy.Get() != m_hDesignatedEnemy.Get()) ) + { + if ( ValidTarget( m_hDesignatedEnemy ) ) + { + m_hEnemy = m_hDesignatedEnemy; + FoundTarget(); + } + } + + // Figure out where we're firing at + Vector vecMid = EyePosition(); + if ( m_bSuppressing ) + { + // Suppression fire should just shoot at it's last known position + m_vecFireTarget = m_vecLastKnownPosition; + } + else + { + m_vecFireTarget = m_hEnemy->BodyTarget( vecMid ); + m_vecLastKnownPosition = m_vecFireTarget; + } + Vector vecDirToEnemy = m_vecFireTarget - vecMid; + QAngle angToTarget; + VectorAngles(vecDirToEnemy, angToTarget); + + angToTarget.y = UTIL_AngleMod( angToTarget.y ); + if (angToTarget.x < -180) + angToTarget.x += 360; + if (angToTarget.x > 180) + angToTarget.x -= 360; + + // now all numbers should be in [1...360] + // pin to turret limitations to [-50...50] + if (angToTarget.x > 50) + angToTarget.x = 50; + else if (angToTarget.x < -50) + angToTarget.x = -50; + m_vecGoalAngles.y = angToTarget.y; + m_vecGoalAngles.x = angToTarget.x; + + MoveTurret(); + + // Fire on the target if it's within 10 units of being aimed right at it + if ( m_flNextAttack <= gpGlobals->curtime && (m_vecGoalAngles - m_vecCurAngles).Length() <= 15 ) + { + // Suppressing turrets fire randomly + if ( m_bSuppressing ) + { + if ( random->RandomInt( 0,1 ) != 0 ) + { + m_flNextAttack = gpGlobals->curtime + 0.5; + return; + } + } + + // See if the object or its owner is taking emp damage, if so, don't fire + if ( ShouldBeActive() ) + { + Fire(); + } + } + else + { + SetSentryAnim( TFTURRET_ANIM_SPIN ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when a rotation happens +//----------------------------------------------------------------------------- +void CObjectSentrygun::ObjectMoved( void ) +{ + m_vecCurAngles.y = UTIL_AngleMod( GetLocalAngles().y ); + RecomputeOrientation(); + m_fBoneXRotator = 0; + BaseClass::ObjectMoved(); +} + +//----------------------------------------------------------------------------- +// Purpose: Fire at our target +//----------------------------------------------------------------------------- +bool CObjectSentrygun::Fire( void ) +{ + // Base sentry doesn't know how to fire + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Tell this sentrygun to attack the following target, if it can +//----------------------------------------------------------------------------- +void CObjectSentrygun::DesignateTarget( CBaseEntity *pTarget ) +{ + m_hDesignatedEnemy = pTarget; + + if ( m_hEnemy.Get() != m_hDesignatedEnemy.Get() ) + { + m_hEnemy = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CObjectSentrygun::IsTurtled( void ) +{ + return m_bTurtled; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CObjectSentrygun::IsTurtling( void ) +{ + return m_bTurtling; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::ToggleTurtle( void ) +{ + // Don't turtle while building + if ( IsPlacing() || IsBuilding() || IsTurtling() ) + return; + + // Don't turtle if I'm built on anything + if ( GetMoveParent() ) + return; + + // Swap turtle state + if ( IsTurtled() ) + { + UnTurtle(); + } + else + { + Turtle(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::Turtle( void ) +{ + m_bTurtled = true; + m_bTurtling = true; + + m_flTurtlingFinishedAt = gpGlobals->curtime + SENTRY_TURTLE_TIME; + EmitSound( "ObjectSentrygun.Turtle" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectSentrygun::UnTurtle( void ) +{ + // Make sure there's enough room to unturtle + // NJS: this seems a bit hacky and returns false positives sometimes, for now we're just assuming that if it can turtle, it can also unturtle. + //trace_t tr; + //UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin(), WorldAlignMins() - Vector( 4,4,4 ), WorldAlignMaxs() + Vector( 4,4,4 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + //if ( tr.startsolid || tr.allsolid ) + // return; + + m_bTurtled = false; + m_bTurtling = true; + m_flTurtlingFinishedAt = gpGlobals->curtime + SENTRY_TURTLE_TIME; + EmitSound( "ObjectSentrygun.UnTurtle" ); + + RemoveSolidFlags( FSOLID_NOT_SOLID ); + m_takedamage = DAMAGE_YES; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the technology levels of the sentrygun +//----------------------------------------------------------------------------- +void CObjectSentrygun::SetTechnology( bool bSmarter, bool bSensors ) +{ + m_bSmarter = bSmarter; + m_bSensors = bSensors; + + // Smarter sentryguns turn faster + if ( m_bSmarter ) + { + m_iBaseTurnRate = 6; + } + else + { + m_iBaseTurnRate = 4; + } +} + +//======================================================================================================== +// SENTRYGUN TYPES +//======================================================================================================== +IMPLEMENT_SERVERCLASS_ST(CObjectSentrygunPlasma, DT_ObjectSentrygunPlasma) +END_SEND_TABLE(); + +IMPLEMENT_SERVERCLASS_ST(CObjectSentrygunRocketlauncher, DT_ObjectSentrygunRocketlauncher) +END_SEND_TABLE(); + +void CObjectSentrygunPlasma::Spawn( void ) +{ + m_iHealth = obj_sentrygun_plasma_health.GetInt(); + + SetModel( SG_PLASMA_MODEL ); + BaseClass::Spawn(); + + SetType( OBJ_SENTRYGUN_PLASMA ); + + m_flNextAmmoRecharge = gpGlobals->curtime + PLASMA_SENTRYGUN_RECHARGE_TIME; + m_nBurstCount = PLASMA_SENTRY_BURST_COUNT; + + m_iAmmo = m_iMaxAmmo = 50; + m_iAmmoType = GetAmmoDef()->Index( "ShotgunEnergy" ); +} + +void CObjectSentrygunRocketlauncher::Spawn( void ) +{ + m_iHealth = obj_sentrygun_rocketlauncher_health.GetInt(); + + SetModel( SG_ROCKETLAUNCHER_MODEL ); + BaseClass::Spawn(); + + SetType( OBJ_SENTRYGUN_ROCKET_LAUNCHER ); + + m_iAmmo = m_iMaxAmmo = 50; + m_iAmmoType = GetAmmoDef()->Index( "Rockets" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Plasma sentrygun's fire +//----------------------------------------------------------------------------- +bool CObjectSentrygunPlasma::Fire( void ) +{ + Vector vecSrc = EyePosition(); + Vector vecTarget = m_vecFireTarget; + Vector vecAim; + QAngle vecAng; + + // Because the plasma sentrygun always thinks it has ammo (see below) + // we might not have ammo here, in which case we should just abort. + if ( !m_iAmmo ) + return true; + + GetAttachment( "muzzle", vecSrc, vecAng ); + + // Get the distance to the target + float targetDist = (vecTarget - vecSrc).Length(); + float targetTime = targetDist / PLASMA_VELOCITY; + + // If we're not suppressing, calculate where the target's going to be in that time + if ( !m_bSuppressing ) + { + Vector vecVelocity = m_hEnemy->GetSmoothedVelocity(); + // Dampen Z velocity to prevent jumping people screwing the aim + vecVelocity.z *= 0.25; + // Get the target point to aim for + Vector vecEnd = vecTarget + ( vecVelocity * targetTime ); + vecAim = (vecEnd - vecSrc); + } + else + { + vecAim = (vecTarget - vecSrc); + } + VectorNormalize( vecAim ); + + int damageType = GetAmmoDef()->DamageType( m_iAmmoType ); + CBasePlasmaProjectile *pPlasma = CBasePlasmaProjectile::Create( vecSrc + (vecAim * 32), vecAim, damageType, this ); + pPlasma->SetDamage( 15 ); + pPlasma->SetMaxRange( obj_sentrygun_plasma_range.GetFloat() ); + pPlasma->m_hOwner = GetBuilder(); + + EmitSound( "ObjectSentrygun.Fire" ); + SetSentryAnim( TFTURRET_ANIM_FIRE ); + DoMuzzleFlash(); + + m_iAmmo -= 1; + + float flAttackTime; + if (--m_nBurstCount > 0) + { + flAttackTime = 0.2f; + } + else + { + flAttackTime = random->RandomFloat( 1.0f, 2.0f ); + m_nBurstCount = PLASMA_SENTRY_BURST_COUNT + random->RandomInt( 0, PLASMA_SENTRY_BURST_COUNT ); + } + + // If I'm EMPed, slow the firing rate down + if ( HasPowerup(POWERUP_EMP) ) + { + flAttackTime *= 3; + } + + m_flNextAttack = gpGlobals->curtime + flAttackTime; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Plasma sentry regenerates ammo, so always assume it has ammo left. +// This is to prevent it from continually unlocking & relocking when it's +// ammo is flickering between 0 and 1. +//----------------------------------------------------------------------------- +bool CObjectSentrygunPlasma::HasAmmo( void ) +{ + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Plasma sentrygun recharges it's ammo +//----------------------------------------------------------------------------- +void CObjectSentrygunPlasma::CheckShield( void ) +{ + // ROBIN: Disabled recharging for now + /* + if ( m_flNextAmmoRecharge < gpGlobals->curtime ) + { + if ( m_iAmmo < m_iMaxAmmo ) + { + m_iAmmo++; + } + + m_flNextAmmoRecharge = gpGlobals->curtime + PLASMA_SENTRYGUN_RECHARGE_TIME; + } + */ + + BaseClass::CheckShield(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Rocket launcher sentrygun's fire +//----------------------------------------------------------------------------- +bool CObjectSentrygunRocketlauncher::Fire() +{ + Vector vecSrc = EyePosition(); + Vector vecTarget = m_vecFireTarget; + Vector vecAim; + + // Get the distance to the target + float targetDist = (vecTarget - vecSrc).Length(); + float targetTime = targetDist / ROCKET_VELOCITY; + + // If we're not suppressing, calculate where the target's going to be in that time + if ( !m_bSuppressing ) + { + Vector vecVelocity = m_hEnemy->GetSmoothedVelocity(); + // Dampen velocity to prevent people rapidly switching strafe + vecVelocity *= 0.5; + // Dampen Z velocity to prevent jumping people screwing the aim + vecVelocity.z *= 0.5; + // Get the target point to aim for + Vector vecEnd = vecTarget + ( vecVelocity * targetTime ); + vecAim = (vecEnd - vecSrc); + } + else + { + vecAim = (vecTarget - vecSrc); + } + + CGrenadeRocket::Create( vecSrc, vecAim, edict(), GetOwner() ); + + EmitSound( "ObjectSentrygunRocketlauncher.Fire" ); + + m_iAmmo -= 1; + + m_flNextAttack = gpGlobals->curtime + 1.5f; + return true; +} + + +void CObjectSentrygunRocketlauncher::SetTechnology( bool bSmarter, bool bSensors ) +{ + BaseClass::SetTechnology( bSmarter, bSensors ); + m_iBaseTurnRate = 2; +} |