summaryrefslogtreecommitdiff
path: root/game/server/tf/bot/behavior/sniper/tf_bot_sniper_attack.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/bot/behavior/sniper/tf_bot_sniper_attack.cpp')
-rw-r--r--game/server/tf/bot/behavior/sniper/tf_bot_sniper_attack.cpp253
1 files changed, 253 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/sniper/tf_bot_sniper_attack.cpp b/game/server/tf/bot/behavior/sniper/tf_bot_sniper_attack.cpp
new file mode 100644
index 0000000..9cf7945
--- /dev/null
+++ b/game/server/tf/bot/behavior/sniper/tf_bot_sniper_attack.cpp
@@ -0,0 +1,253 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_sniper_attack.h
+// Attack a threat as a Sniper
+// Michael Booth, February 2009
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_obj_sentrygun.h"
+#include "tf_gamerules.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/sniper/tf_bot_sniper_attack.h"
+#include "bot/behavior/tf_bot_melee_attack.h"
+#include "bot/behavior/tf_bot_retreat_to_cover.h"
+
+#include "nav_mesh.h"
+
+extern ConVar tf_bot_path_lookahead_range;
+
+ConVar tf_bot_sniper_flee_range( "tf_bot_sniper_flee_range", "400", FCVAR_CHEAT, "If threat is closer than this, retreat" );
+ConVar tf_bot_sniper_melee_range( "tf_bot_sniper_melee_range", "200", FCVAR_CHEAT, "If threat is closer than this, attack with melee weapon" );
+ConVar tf_bot_sniper_linger_time( "tf_bot_sniper_linger_time", "5", FCVAR_CHEAT, "How long Sniper will wait around after losing his target before giving up" );
+
+
+//---------------------------------------------------------------------------------------------
+bool CTFBotSniperAttack::IsPossible( CTFBot *me )
+{
+ return me->IsPlayerClass( TF_CLASS_SNIPER ) && me->GetVisionInterface()->GetPrimaryKnownThreat() && me->GetVisionInterface()->GetPrimaryKnownThreat()->IsVisibleRecently();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSniperAttack::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSniperAttack::Update( CTFBot *me, float interval )
+{
+ // switch to our sniper rifle
+ CBaseCombatWeapon *myGun = me->Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
+ if ( myGun )
+ {
+ me->Weapon_Switch( myGun );
+ }
+
+ // shoot at bad guys
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+
+ if ( threat && !threat->GetEntity()->IsAlive() )
+ {
+ // he's dead
+ threat = NULL;
+ }
+
+ if ( threat == NULL || !threat->IsVisibleInFOVNow() )
+ {
+ if ( m_lingerTimer.IsElapsed() )
+ {
+ if ( me->m_Shared.InCond( TF_COND_ZOOMED ) )
+ {
+ return Continue();
+ }
+
+ return Done( "No threat for awhile" );
+ }
+
+ return Continue();
+ }
+
+ me->EquipBestWeaponForThreat( threat );
+
+ if ( me->IsDistanceBetweenLessThan( threat->GetLastKnownPosition(), tf_bot_sniper_flee_range.GetFloat() ) )
+ {
+ return SuspendFor( new CTFBotRetreatToCover, "Retreating from nearby enemy" );
+ }
+
+ if ( me->GetTimeSinceLastInjury() < 1.0f )
+ {
+ return SuspendFor( new CTFBotRetreatToCover, "Retreating due to injury" );
+ }
+
+ // we have a target
+ m_lingerTimer.Start( RandomFloat( 0.75f, 1.25f ) * tf_bot_sniper_linger_time.GetFloat() );
+
+ if ( !me->m_Shared.InCond( TF_COND_ZOOMED ) )
+ {
+ me->PressAltFireButton();
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CTFBotSniperAttack::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
+{
+ if ( me->m_Shared.InCond( TF_COND_ZOOMED ) )
+ {
+ // we're leaving to do something else - unzoom
+ me->PressAltFireButton();
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSniperAttack::OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction )
+{
+ if ( me->m_Shared.InCond( TF_COND_ZOOMED ) )
+ {
+ // we're leaving to do something else - unzoom
+ me->PressAltFireButton();
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotSniperAttack::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
+{
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+// given a subject, return the world space position we should aim at
+Vector CTFBotSniperAttack::SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const
+{
+ VPROF_BUDGET( "CTFBotSniperAttack::SelectTargetPoint", "NextBot" );
+
+ Vector visibleSpot;
+
+ trace_t result;
+ NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
+
+ // head, then chest, then feet for the Sniper
+
+ // headshot seems to be a bit higher that EyePosition()
+ Vector subjectHeadPos( subject->EyePosition() );
+ subjectHeadPos.z += 1.0f;
+
+ UTIL_TraceLine( me->GetBodyInterface()->GetEyePosition(), subjectHeadPos, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
+ if ( result.DidHit() )
+ {
+ UTIL_TraceLine( me->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
+
+ if ( result.DidHit() )
+ {
+ UTIL_TraceLine( me->GetBodyInterface()->GetEyePosition(), subject->GetAbsOrigin(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
+ }
+ }
+
+ // even if they aren't visible, we have no way to communicate that out, so pick a reasonable spot
+ return result.endpos;
+}
+
+
+//---------------------------------------------------------------------------------------------
+bool CTFBotSniperAttack::IsImmediateThreat( const CBaseCombatCharacter *subject, const CKnownEntity *threat ) const
+{
+ if ( subject->InSameTeam( threat->GetEntity() ) )
+ return false;
+
+ if ( !threat->GetEntity()->IsAlive() )
+ return false;
+
+ const float hiddenAwhile = 3.0f;
+ if ( !threat->WasEverVisible() || threat->GetTimeSinceLastSeen() > hiddenAwhile )
+ return false;
+
+ CTFPlayer *player = ToTFPlayer( threat->GetEntity() );
+
+ Vector to = subject->GetAbsOrigin() - threat->GetLastKnownPosition();
+ float threatRange = to.NormalizeInPlace();
+
+ if ( player == NULL )
+ {
+ CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( threat->GetEntity() );
+ if ( sentry )
+ {
+ // are we in range?
+ if ( threatRange < SENTRY_MAX_RANGE )
+ {
+ // is it pointing at us?
+ Vector sentryForward;
+ AngleVectors( sentry->GetTurretAngles(), &sentryForward );
+
+ if ( DotProduct( to, sentryForward ) > 0.8f )
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ if ( player->IsPlayerClass( TF_CLASS_SNIPER ) )
+ {
+ // is the sniper pointing at me?
+ Vector sniperForward;
+ player->EyeVectors( &sniperForward );
+
+ if ( DotProduct( to, sniperForward ) > 0.8f )
+ {
+ return true;
+ }
+ }
+
+#ifdef TF_RAID_MODE
+ if ( !TFGameRules()->IsRaidMode() )
+ {
+ }
+ else
+#endif // TF_RAID_MODE
+ {
+ if ( player->IsPlayerClass( TF_CLASS_MEDIC ) )
+ {
+ // always try to kill these guys first
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// return the more dangerous of the two threats to 'subject', or NULL if we have no opinion
+const CKnownEntity *CTFBotSniperAttack::SelectMoreDangerousThreat( const INextBot *me,
+ const CBaseCombatCharacter *subject,
+ const CKnownEntity *threat1,
+ const CKnownEntity *threat2 ) const
+{
+ if ( threat1 && threat2 )
+ {
+ bool isImmediateThreat1 = IsImmediateThreat( subject, threat1 );
+ bool isImmediateThreat2 = IsImmediateThreat( subject, threat2 );
+
+ if ( isImmediateThreat1 && !isImmediateThreat2 )
+ {
+ return threat1;
+ }
+ else if ( !isImmediateThreat1 && isImmediateThreat2 )
+ {
+ return threat2;
+ }
+ }
+
+ // both or neither are immediate threats - no preference
+ return NULL;
+}