diff options
Diffstat (limited to 'game/server/tf/bot/behavior/demoman')
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 |