summaryrefslogtreecommitdiff
path: root/game/server/tf/bot/behavior/demoman
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/bot/behavior/demoman')
-rw-r--r--game/server/tf/bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.cpp310
-rw-r--r--game/server/tf/bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h45
-rw-r--r--game/server/tf/bot/behavior/demoman/tf_bot_stickybomb_sentrygun.cpp342
-rw-r--r--game/server/tf/bot/behavior/demoman/tf_bot_stickybomb_sentrygun.h51
4 files changed, 748 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.cpp b/game/server/tf/bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.cpp
new file mode 100644
index 0000000..721bd35
--- /dev/null
+++ b/game/server/tf/bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.cpp
@@ -0,0 +1,310 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_prepare_stickybomb_trap.cpp
+// Place stickybombs to create a deadly trap
+// Michael Booth, July 2010
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h"
+#include "tf_weapon_pipebomblauncher.h"
+
+#define MAX_STICKYBOMB_COUNT 8
+
+ConVar tf_bot_stickybomb_density( "tf_bot_stickybomb_density", "0.0001", FCVAR_CHEAT, "Number of stickies to place per square inch" );
+
+
+//---------------------------------------------------------------------------------------------
+class PlaceStickyBombReply : public INextBotReply
+{
+public:
+ virtual void OnSuccess( INextBot *bot ) // invoked when process completed successfully
+ {
+ CTFBot *me = ToTFBot( bot->GetEntity() );
+
+ CTFWeaponBase *myCurrentWeapon = me->m_Shared.GetActiveTFWeapon();
+ if ( myCurrentWeapon && myCurrentWeapon->GetWeaponID() == TF_WEAPON_PIPEBOMBLAUNCHER )
+ {
+ // launch the sticky
+ me->PressFireButton( 0.1f );
+
+ // increase the bomb count for this target area
+ if ( m_bombTargetArea )
+ {
+ m_bombTargetArea->m_count++;
+ }
+
+ if( m_pLaunchWaitTimer )
+ {
+ // release the latch
+ m_pLaunchWaitTimer->Start( 0.15f );
+ }
+ }
+ }
+
+ virtual void OnFail( INextBot *bot, FailureReason reason )// invoked when process failed
+ {
+ // retry aim immediately
+ m_pLaunchWaitTimer->Invalidate();
+ }
+
+ void ClearData()
+ {
+ // Be sure to clear all members here, as we can potentially get an OnSuccess() call
+ // after the ~CTFBotPrepareStickybombTrap.
+ m_bombTargetArea = NULL;
+ m_pLaunchWaitTimer = NULL;
+ }
+
+ CTFBotPrepareStickybombTrap::BombTargetArea *m_bombTargetArea;
+ CountdownTimer *m_pLaunchWaitTimer;
+};
+
+
+static PlaceStickyBombReply bombReply;
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotPrepareStickybombTrap::CTFBotPrepareStickybombTrap( void )
+{
+ m_myArea = NULL;
+}
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotPrepareStickybombTrap::~CTFBotPrepareStickybombTrap( )
+{
+ bombReply.ClearData();
+}
+
+
+//---------------------------------------------------------------------------------------------
+// Return true if this Action has what it needs to perform right now
+bool CTFBotPrepareStickybombTrap::IsPossible( CTFBot *me )
+{
+ // don't lay a trap if we're in the midst of fighting
+ if ( /*me->IsInCombat() || */ me->GetTimeSinceLastInjury() < 1.0f )
+ {
+ return false;
+ }
+
+ if ( !me->IsPlayerClass( TF_CLASS_DEMOMAN ) )
+ {
+ return false;
+ }
+
+ CTFPipebombLauncher *stickyLauncher = dynamic_cast< CTFPipebombLauncher * >( me->Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
+ if ( stickyLauncher && !me->IsWeaponRestricted( stickyLauncher ) )
+ {
+ if ( stickyLauncher->GetPipeBombCount() >= MAX_STICKYBOMB_COUNT || me->GetAmmoCount( TF_AMMO_SECONDARY ) <= 0 )
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CTFBotPrepareStickybombTrap::InitBombTargetAreas( CTFBot *me )
+{
+ const CUtlVector< CTFNavArea * > &invasionAreaVector = m_myArea->GetEnemyInvasionAreaVector( me->GetTeamNumber() );
+
+ // randomly shuffle the target areas
+ CUtlVector< CTFNavArea * > shuffleVector;
+ shuffleVector = invasionAreaVector;
+ int n = shuffleVector.Count();
+ while( n > 1 )
+ {
+ int k = RandomInt( 0, n-1 );
+ n--;
+
+ CTFNavArea *tmp = shuffleVector[n];
+ shuffleVector[n] = shuffleVector[k];
+ shuffleVector[k] = tmp;
+ }
+
+ // initialize each target area to zero sticky bombs
+ m_bombTargetAreaVector.RemoveAll();
+
+ for( int i=0; i<shuffleVector.Count(); ++i )
+ {
+ BombTargetArea target;
+ target.m_area = shuffleVector[i];
+ target.m_count = 0;
+
+ m_bombTargetAreaVector.AddToTail( target );
+ }
+
+ m_launchWaitTimer.Invalidate();
+
+ // Clean up any in-flight AimHeadTowards() replies, since changing m_bombTargetAreaVector
+ // might move memory and invalidate the current reply pointer.
+ me->GetBodyInterface()->ClearPendingAimReply();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotPrepareStickybombTrap::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ // detonate old set of stickies
+ // me->PressAltFireButton();
+
+ // reload entire clip before laying sticky trap
+ CTFPipebombLauncher *stickyLauncher = dynamic_cast< CTFPipebombLauncher * >( me->Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
+ if ( stickyLauncher )
+ {
+ m_isFullReloadNeeded = ( me->GetAmmoCount( TF_AMMO_SECONDARY ) >= stickyLauncher->GetMaxClip1() && stickyLauncher->Clip1() < stickyLauncher->GetMaxClip1() );
+ }
+ else
+ {
+ m_isFullReloadNeeded = false;
+ }
+
+ m_myArea = me->GetLastKnownArea();
+ if ( !m_myArea )
+ {
+ return Done( "No nav mesh" );
+ }
+
+ InitBombTargetAreas( me );
+
+ // own our view updating so we can aim
+ me->StopLookingAroundForEnemies();
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotPrepareStickybombTrap::Update( CTFBot *me, float interval )
+{
+ if ( !TFGameRules()->InSetup() )
+ {
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+ if ( threat )
+ {
+ const float giveUpRange = 500.0f;
+ if ( me->IsDistanceBetweenLessThan( threat->GetLastKnownPosition(), giveUpRange ) )
+ {
+ return Done( "Enemy nearby - giving up" );
+ }
+ }
+ }
+
+ if ( me->GetLastKnownArea() && me->GetLastKnownArea() != m_myArea )
+ {
+ // we've moved
+ m_myArea = me->GetLastKnownArea();
+ InitBombTargetAreas( me );
+ }
+
+ CTFWeaponBase *myCurrentWeapon = me->m_Shared.GetActiveTFWeapon();
+ CTFPipebombLauncher *stickyLauncher = dynamic_cast< CTFPipebombLauncher * >( me->Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
+
+ if ( !myCurrentWeapon || !stickyLauncher )
+ {
+ return Done( "Missing weapon" );
+ }
+
+ if ( myCurrentWeapon->GetWeaponID() != TF_WEAPON_PIPEBOMBLAUNCHER )
+ {
+ me->Weapon_Switch( stickyLauncher );
+ }
+
+ // reload fully
+ if ( m_isFullReloadNeeded )
+ {
+ int maxClip = MIN( stickyLauncher->GetMaxClip1(), me->GetAmmoCount( TF_AMMO_SECONDARY ) );
+
+ if ( stickyLauncher->Clip1() >= maxClip )
+ {
+ // fully reloaded
+ m_isFullReloadNeeded = false;
+ }
+
+ me->PressReloadButton();
+
+ return Continue();
+ }
+
+
+ if ( stickyLauncher->GetPipeBombCount() >= MAX_STICKYBOMB_COUNT || me->GetAmmoCount( TF_AMMO_SECONDARY ) <= 0 )
+ {
+ return Done( "Max sticky bombs reached" );
+ }
+
+
+ // aim towards areas where enemy will come from
+ if ( m_launchWaitTimer.IsElapsed() )
+ {
+ // find next target that needs bombs
+ int i;
+ for( i=0; i<m_bombTargetAreaVector.Count(); ++i )
+ {
+ CTFNavArea *targetArea = m_bombTargetAreaVector[i].m_area;
+
+ int desiredCount = tf_bot_stickybomb_density.GetFloat() * targetArea->GetSizeX() * targetArea->GetSizeY();
+ if ( desiredCount < 1 )
+ {
+ desiredCount = 1;
+ }
+
+ if ( m_bombTargetAreaVector[i].m_count < desiredCount )
+ {
+ // place a sticky on this area
+ bombReply.m_bombTargetArea = &m_bombTargetAreaVector[i];
+
+ // this timer causes us to wait until the aim finishes and launched before we start another aim
+ m_launchWaitTimer.Start( 2.0f );
+ bombReply.m_pLaunchWaitTimer = &m_launchWaitTimer;
+
+ Vector bombSpot = targetArea->GetRandomPoint();
+
+ me->GetBodyInterface()->AimHeadTowards( bombSpot, IBody::IMPORTANT, 5.0f, &bombReply, "Aiming a sticky bomb" );
+
+ break;
+ }
+ }
+
+ if ( i == m_bombTargetAreaVector.Count() )
+ {
+ return Done( "Exhausted bomb target areas" );
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CTFBotPrepareStickybombTrap::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
+{
+ // clean up any in-flight AimHeadTowards() replies
+ me->GetBodyInterface()->ClearPendingAimReply();
+
+ me->StartLookingAroundForEnemies();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotPrepareStickybombTrap::OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction )
+{
+ // this behavior is transitory - if we need to do something else, just give up
+ return Done();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotPrepareStickybombTrap::OnInjured( CTFBot *me, const CTakeDamageInfo &info )
+{
+ return TryDone( RESULT_IMPORTANT, "Ouch!" );
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotPrepareStickybombTrap::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
+{
+ return ANSWER_NO;
+}
diff --git a/game/server/tf/bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h b/game/server/tf/bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h
new file mode 100644
index 0000000..0abded3
--- /dev/null
+++ b/game/server/tf/bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h
@@ -0,0 +1,45 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_prepare_stickybomb_trap.h
+// Place stickybombs to create a deadly trap
+// Michael Booth, July 2010
+
+#ifndef TF_BOT_PREPARE_STICKYBOMB_TRAP_H
+#define TF_BOT_PREPARE_STICKYBOMB_TRAP_H
+
+class CTFBotPrepareStickybombTrap : public Action< CTFBot >
+{
+public:
+ CTFBotPrepareStickybombTrap( void );
+ virtual ~CTFBotPrepareStickybombTrap( );
+
+ static bool IsPossible( CTFBot *me ); // Return true if this Action has what it needs to perform right now
+
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+ virtual void OnEnd( CTFBot *me, Action< CTFBot > *nextAction );
+
+ virtual ActionResult< CTFBot > OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction );
+
+ virtual EventDesiredResult< CTFBot > OnInjured( CTFBot *me, const CTakeDamageInfo &info );
+
+ virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
+
+ virtual const char *GetName( void ) const { return "PrepareStickybombTrap"; };
+
+ struct BombTargetArea
+ {
+ CTFNavArea *m_area;
+ int m_count;
+ };
+
+private:
+ bool m_isFullReloadNeeded;
+
+ CTFNavArea *m_myArea;
+
+ CUtlVector< BombTargetArea > m_bombTargetAreaVector;
+ void InitBombTargetAreas( CTFBot *me );
+ CountdownTimer m_launchWaitTimer;
+};
+
+#endif // TF_BOT_PREPARE_STICKYBOMB_TRAP_H
diff --git a/game/server/tf/bot/behavior/demoman/tf_bot_stickybomb_sentrygun.cpp b/game/server/tf/bot/behavior/demoman/tf_bot_stickybomb_sentrygun.cpp
new file mode 100644
index 0000000..d788892
--- /dev/null
+++ b/game/server/tf/bot/behavior/demoman/tf_bot_stickybomb_sentrygun.cpp
@@ -0,0 +1,342 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_stickybomb_sentrygun.cpp
+// Destroy the given sentrygun with stickybombs
+// Michael Booth, August 2010
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/demoman/tf_bot_stickybomb_sentrygun.h"
+#include "tf_weapon_pipebomblauncher.h"
+#include "tf_obj_sentrygun.h"
+#include "NextBotUtil.h"
+
+ConVar tf_bot_sticky_base_range( "tf_bot_sticky_base_range", "800", FCVAR_CHEAT );
+ConVar tf_bot_sticky_charge_rate( "tf_bot_sticky_charge_rate", "0.01", FCVAR_CHEAT, "Seconds of charge per unit range beyond base" );
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotStickybombSentrygun::CTFBotStickybombSentrygun( CObjectSentrygun *sentrygun )
+{
+ m_sentrygun = sentrygun;
+ m_hasGivenAim = false;
+}
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotStickybombSentrygun::CTFBotStickybombSentrygun( CObjectSentrygun *sentrygun, float aimYaw, float aimPitch, float aimCharge )
+{
+ m_sentrygun = sentrygun;
+ m_hasGivenAim = true;
+ m_givenYaw = aimYaw;
+ m_givenPitch = aimPitch;
+ m_givenCharge = aimCharge;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotStickybombSentrygun::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ // detonate old set of stickies
+ me->PressAltFireButton();
+
+ // own our view updating so we can aim
+ me->StopLookingAroundForEnemies();
+
+ m_isFullReloadNeeded = true;
+
+ // STOP
+ me->SetAbsVelocity( vec3_origin );
+
+ m_searchPitch = 0.0f;
+ m_hasTarget = false;
+ m_searchTimer.Start( 3.0f );
+
+ m_isChargingShot = false;
+
+ if ( m_hasGivenAim )
+ {
+ m_hasTarget = true;
+
+ // remember where we are standing - if we move for any reason, we'll need to re-search
+ m_launchSpot = me->GetAbsOrigin();
+
+ // start charging up the sticky launch
+ m_chargeToLaunch = m_givenCharge;
+ m_isChargingShot = true;
+
+ // aim along given pitch/yaw
+ QAngle angles;
+ angles.x = m_givenPitch;
+ angles.y = m_givenYaw;
+ angles.z = 0.0f;
+
+ Vector aimForward;
+ AngleVectors( angles, &aimForward );
+
+ m_eyeAimTarget = me->EyePosition() + 1500.0f * aimForward;
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+bool CTFBotStickybombSentrygun::IsAimOnTarget( CTFBot *me, float pitch, float yaw, float charge )
+{
+ // estimate impact spot
+ Vector impactSpot = me->EstimateStickybombProjectileImpactPosition( pitch, yaw, charge );
+
+ // check if impactSpot landed near sentry
+ const float explosionRadius = 75.0f;
+ if ( ( m_sentrygun->WorldSpaceCenter() - impactSpot ).IsLengthLessThan( explosionRadius ) )
+ {
+ trace_t trace;
+ NextBotTraceFilterIgnoreActors filter( NULL, COLLISION_GROUP_NONE );
+
+ UTIL_TraceLine( m_sentrygun->WorldSpaceCenter(), impactSpot, MASK_SOLID_BRUSHONLY, &filter, &trace );
+ if ( !trace.DidHit() )
+ {
+ // NDebugOverlay::Cross3D( impactSpot, 10.0f, 100, 255, 0, true, 60.0f );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotStickybombSentrygun::Update( CTFBot *me, float interval )
+{
+ CTFWeaponBase *myCurrentWeapon = me->m_Shared.GetActiveTFWeapon();
+ CTFPipebombLauncher *stickyLauncher = dynamic_cast< CTFPipebombLauncher * >( me->Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
+
+ if ( !myCurrentWeapon || !stickyLauncher )
+ {
+ return Done( "Missing weapon" );
+ }
+
+ if ( myCurrentWeapon->GetWeaponID() != TF_WEAPON_PIPEBOMBLAUNCHER )
+ {
+ me->Weapon_Switch( stickyLauncher );
+ }
+
+ if ( m_sentrygun == NULL || !m_sentrygun->IsAlive() )
+ {
+ return Done( "Sentry destroyed" );
+ }
+
+ if ( !m_hasTarget && m_searchTimer.IsElapsed() )
+ {
+ return Done( "Can't find aim" );
+ }
+
+ // reload fully
+ if ( m_isFullReloadNeeded )
+ {
+ int maxClip = MIN( stickyLauncher->GetMaxClip1(), me->GetAmmoCount( TF_AMMO_SECONDARY ) );
+
+ if ( stickyLauncher->Clip1() >= maxClip )
+ {
+ // fully reloaded
+ m_isFullReloadNeeded = false;
+ }
+
+ me->PressReloadButton();
+
+ return Continue();
+ }
+
+ int requiredStickyBombs = 3;
+
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ // launch more stickies to make sure we take out beefed-up sentries
+ requiredStickyBombs = 5;
+ }
+
+ if ( stickyLauncher->GetPipeBombCount() >= requiredStickyBombs || me->GetAmmoCount( TF_AMMO_SECONDARY ) <= 0 )
+ {
+ // stickies laid - detonate them once they are on the ground
+ const CUtlVector< CHandle< CTFGrenadePipebombProjectile > > &pipeVector = stickyLauncher->GetPipeBombVector();
+
+ int i;
+ for( i=0; i<pipeVector.Count(); ++i )
+ {
+ if ( pipeVector[i].Get() && !pipeVector[i]->m_bTouched )
+ {
+ break;
+ }
+ }
+
+ if ( i == pipeVector.Count() )
+ {
+ // stickies are on the ground
+ me->PressAltFireButton();
+
+ if ( me->GetAmmoCount( TF_AMMO_SECONDARY ) <= 0 )
+ {
+ return Done( "Out of ammo" );
+ }
+ }
+ }
+ else if ( m_isChargingShot )
+ {
+ // fudge charge time a bit longer - better to overshoot
+ float stickyChargeTime = 1.1f * m_chargeToLaunch * TF_PIPEBOMB_MAX_CHARGE_TIME;
+
+ me->GetBodyInterface()->AimHeadTowards( m_eyeAimTarget, IBody::CRITICAL, 0.3f, NULL, "Aiming a sticky bomb at a sentrygun" );
+
+ if ( gpGlobals->curtime - stickyLauncher->GetChargeBeginTime() >= stickyChargeTime )
+ {
+ // let go
+ me->ReleaseFireButton();
+ m_isChargingShot = false;
+ }
+ else
+ {
+ me->PressFireButton();
+ }
+ }
+ else if ( stickyLauncher->m_flNextPrimaryAttack < gpGlobals->curtime )
+ {
+ // if we've moved, we need to re-search
+ if ( m_hasTarget )
+ {
+ const float tolerance = 1.0f;
+ if ( me->IsRangeGreaterThan( m_launchSpot, tolerance ) )
+ {
+ m_hasTarget = false;
+ m_searchTimer.Reset();
+ }
+ }
+
+ if ( !m_hasTarget )
+ {
+ // search for angle to land sticky near sentry
+ Vector toSentry = m_sentrygun->WorldSpaceCenter() - me->EyePosition();
+
+ QAngle angles;
+ VectorAngles( toSentry, angles );
+
+ float bestYaw = 0.0f;
+ float bestPitch = 0.0f;
+ float bestCharge = 1.0f;
+
+ const int trials = 100;
+ for( int t=0; t<trials; ++t )
+ {
+ float yaw = angles.y + RandomFloat( -30.0f, 30.0f );
+ // float pitch = ( trials & 0x1 ) ? m_searchPitch : -m_searchPitch;
+ float pitch = RandomFloat( -85.0f, 85.0f );
+
+ float charge = 0.0f;
+ if ( toSentry.IsLengthGreaterThan( tf_bot_sticky_base_range.GetBool() ) )
+ {
+ charge = RandomFloat( 0.1f, 1.0f );
+
+ // skew towards zero - full charge shots are seldom required
+ charge *= charge;
+ }
+
+ if ( IsAimOnTarget( me, pitch, yaw, charge ) )
+ {
+ // found target aim - keep one we find with least required
+ // charge, because we need to be fast in combat
+ if ( charge < bestCharge )
+ {
+ m_hasTarget = true;
+
+ bestCharge = charge;
+ m_chargeToLaunch = bestCharge;
+
+ bestYaw = yaw;
+ bestPitch = pitch;
+
+ if ( bestCharge < 0.01 )
+ {
+ // as quick as possible - no need to search further
+ break;
+ }
+ }
+ }
+ }
+
+ // aim along yaw/pitch to reach impact spot
+ angles.x = bestPitch;
+ angles.y = bestYaw;
+ angles.z = 0.0f;
+
+ Vector aimForward;
+ AngleVectors( angles, &aimForward );
+
+ // always recompute eye aim target so we can update our view
+ m_eyeAimTarget = me->EyePosition() + 500.0f * aimForward;
+ me->GetBodyInterface()->AimHeadTowards( m_eyeAimTarget, IBody::CRITICAL, 0.3f, NULL, "Searching for aim..." );
+ }
+
+ if ( m_hasTarget )
+ {
+ // remember where we are standing - if we move for any reason, we'll need to re-search
+ m_launchSpot = me->GetAbsOrigin();
+
+ // start charging up the sticky launch
+ me->PressFireButton();
+ m_isChargingShot = true;
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CTFBotStickybombSentrygun::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
+{
+ // detonate any stickes left out there
+ me->PressAltFireButton();
+
+ me->StartLookingAroundForEnemies();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotStickybombSentrygun::OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction )
+{
+ // detonate any stickes left out there
+ me->PressAltFireButton();
+
+ // this behavior is transitory - if we need to do something else, just give up
+ return Done();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotStickybombSentrygun::OnInjured( CTFBot *me, const CTakeDamageInfo &info )
+{
+ return TryDone( RESULT_IMPORTANT, "Ouch!" );
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotStickybombSentrygun::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
+{
+ return ANSWER_NO;
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotStickybombSentrygun::ShouldHurry( const INextBot *me ) const
+{
+ // while killing a sentry we're "hurrying" so we don't dodge
+ return ANSWER_YES;
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotStickybombSentrygun::ShouldRetreat( const INextBot *me ) const
+{
+ // stay stuck in to try to kill that gun!
+ return ANSWER_NO;
+}
diff --git a/game/server/tf/bot/behavior/demoman/tf_bot_stickybomb_sentrygun.h b/game/server/tf/bot/behavior/demoman/tf_bot_stickybomb_sentrygun.h
new file mode 100644
index 0000000..901e541
--- /dev/null
+++ b/game/server/tf/bot/behavior/demoman/tf_bot_stickybomb_sentrygun.h
@@ -0,0 +1,51 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_stickybomb_sentrygun.h
+// Destroy the given sentrygun with stickybombs
+// Michael Booth, August 2010
+
+#ifndef TF_BOT_STICKYBOMB_SENTRY_H
+#define TF_BOT_STICKYBOMB_SENTRY_H
+
+class CObjectSentrygun;
+
+
+class CTFBotStickybombSentrygun : public Action< CTFBot >
+{
+public:
+ CTFBotStickybombSentrygun( CObjectSentrygun *sentrygun );
+ CTFBotStickybombSentrygun( CObjectSentrygun *sentrygun, float aimYaw, float aimPitch, float aimCharge );
+
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+ virtual void OnEnd( CTFBot *me, Action< CTFBot > *nextAction );
+
+ virtual ActionResult< CTFBot > OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction );
+
+ virtual EventDesiredResult< CTFBot > OnInjured( CTFBot *me, const CTakeDamageInfo &info );
+
+ virtual QueryResultType ShouldHurry( const INextBot *me ) const;
+ virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
+ virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
+
+ virtual const char *GetName( void ) const { return "StickybombSentrygun"; };
+
+private:
+ float m_givenYaw, m_givenPitch, m_givenCharge;
+ bool m_hasGivenAim;
+
+ bool m_isFullReloadNeeded;
+
+ CHandle< CObjectSentrygun > m_sentrygun;
+
+ bool m_isChargingShot;
+
+ CountdownTimer m_searchTimer;
+ bool m_hasTarget;
+ Vector m_eyeAimTarget;
+ Vector m_launchSpot;
+ float m_chargeToLaunch;
+ float m_searchPitch;
+ bool IsAimOnTarget( CTFBot *me, float pitch, float yaw, float charge );
+};
+
+#endif // TF_BOT_STICKYBOMB_SENTRY_H