summaryrefslogtreecommitdiff
path: root/game/server/tf/bot/behavior/tf_bot_retreat_to_cover.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/bot/behavior/tf_bot_retreat_to_cover.cpp')
-rw-r--r--game/server/tf/bot/behavior/tf_bot_retreat_to_cover.cpp317
1 files changed, 317 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/tf_bot_retreat_to_cover.cpp b/game/server/tf/bot/behavior/tf_bot_retreat_to_cover.cpp
new file mode 100644
index 0000000..5dd970b
--- /dev/null
+++ b/game/server/tf/bot/behavior/tf_bot_retreat_to_cover.cpp
@@ -0,0 +1,317 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_move_to_cover.cpp
+// Retreat to local cover from known threats
+// Michael Booth, June 2009
+
+#include "cbase.h"
+#include "tf_player.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/tf_bot_retreat_to_cover.h"
+
+extern ConVar tf_bot_path_lookahead_range;
+ConVar tf_bot_retreat_to_cover_range( "tf_bot_retreat_to_cover_range", "1000", FCVAR_CHEAT );
+ConVar tf_bot_debug_retreat_to_cover( "tf_bot_debug_retreat_to_cover", "0", FCVAR_CHEAT );
+ConVar tf_bot_wait_in_cover_min_time( "tf_bot_wait_in_cover_min_time", "1", FCVAR_CHEAT );
+ConVar tf_bot_wait_in_cover_max_time( "tf_bot_wait_in_cover_max_time", "2", FCVAR_CHEAT );
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotRetreatToCover::CTFBotRetreatToCover( float hideDuration )
+{
+ m_hideDuration = hideDuration;
+ m_actionToChangeToOnceCoverReached = NULL;
+}
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotRetreatToCover::CTFBotRetreatToCover( Action< CTFBot > *actionToChangeToOnceCoverReached )
+{
+ m_hideDuration = -1.0f;
+ m_actionToChangeToOnceCoverReached = actionToChangeToOnceCoverReached;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// for testing a given area's exposure to known threats
+class CTestAreaAgainstThreats : public IVision::IForEachKnownEntity
+{
+public:
+ CTestAreaAgainstThreats( CTFBot *me, CTFNavArea *area )
+ {
+ m_me = me;
+ m_area = area;
+ m_exposedThreatCount = 0;
+ }
+
+ virtual bool Inspect( const CKnownEntity &known )
+ {
+ VPROF_BUDGET( "CTestAreaAgainstThreats::Inspect", "NextBot" );
+
+ if ( m_me->IsEnemy( known.GetEntity() ) )
+ {
+ const CNavArea *threatArea = known.GetLastKnownArea();
+
+ if ( threatArea )
+ {
+ // is area visible by known threat
+ if ( m_area->IsPotentiallyVisible( threatArea ) )
+ ++m_exposedThreatCount;
+ }
+ }
+
+ return true;
+ }
+
+ CTFBot *m_me;
+ CTFNavArea *m_area;
+ int m_exposedThreatCount;
+};
+
+
+// collect nearby areas that provide cover from our known threats
+class CSearchForCover : public ISearchSurroundingAreasFunctor
+{
+public:
+ CSearchForCover( CTFBot *me )
+ {
+ m_me = me;
+ m_minExposureCount = 9999;
+
+ if ( tf_bot_debug_retreat_to_cover.GetBool() )
+ TheNavMesh->ClearSelectedSet();
+ }
+
+ virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
+ {
+ VPROF_BUDGET( "CSearchForCover::operator()", "NextBot" );
+
+ CTFNavArea *area = (CTFNavArea *)baseArea;
+
+ CTestAreaAgainstThreats test( m_me, area );
+ m_me->GetVisionInterface()->ForEachKnownEntity( test );
+
+ if ( test.m_exposedThreatCount <= m_minExposureCount )
+ {
+ // this area is at least as good as already found cover
+ if ( test.m_exposedThreatCount < m_minExposureCount )
+ {
+ // this area is better than already found cover - throw out list and start over
+ m_coverAreaVector.RemoveAll();
+ m_minExposureCount = test.m_exposedThreatCount;
+ }
+
+ m_coverAreaVector.AddToTail( area );
+ }
+
+ return true;
+ }
+
+ // return true if 'adjArea' should be included in the ongoing search
+ virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
+ {
+ if ( travelDistanceSoFar > tf_bot_retreat_to_cover_range.GetFloat() )
+ return false;
+
+ // allow falling off ledges, but don't jump up - too slow
+ return ( currentArea->ComputeAdjacentConnectionHeightChange( adjArea ) < m_me->GetLocomotionInterface()->GetStepHeight() );
+ }
+
+ virtual void PostSearch( void )
+ {
+ if ( tf_bot_debug_retreat_to_cover.GetBool() )
+ {
+ for( int i=0; i<m_coverAreaVector.Count(); ++i )
+ TheNavMesh->AddToSelectedSet( m_coverAreaVector[i] );
+ }
+ }
+
+ CTFBot *m_me;
+ CUtlVector< CTFNavArea * > m_coverAreaVector;
+ int m_minExposureCount;
+};
+
+
+//---------------------------------------------------------------------------------------------
+CTFNavArea *CTFBotRetreatToCover::FindCoverArea( CTFBot *me )
+{
+ VPROF_BUDGET( "CTFBotRetreatToCover::FindCoverArea", "NextBot" );
+
+ CSearchForCover search( me );
+ SearchSurroundingAreas( me->GetLastKnownArea(), search );
+
+ if ( search.m_coverAreaVector.Count() == 0 )
+ {
+ return NULL;
+ }
+
+ // first in vector should be closest via travel distance
+ // pick from the closest 10 areas to avoid the whole team bunching up in one spot
+ int last = MIN( 10, search.m_coverAreaVector.Count() );
+ int which = RandomInt( 0, last-1 );
+ return search.m_coverAreaVector[ which ];
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotRetreatToCover::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
+
+ m_coverArea = FindCoverArea( me );
+
+ if ( m_coverArea == NULL )
+ return Done( "No cover available!" );
+
+ if ( m_hideDuration < 0.0f )
+ {
+ m_hideDuration = RandomFloat( tf_bot_wait_in_cover_min_time.GetFloat(), tf_bot_wait_in_cover_max_time.GetFloat() );
+ }
+
+ m_waitInCoverTimer.Start( m_hideDuration );
+
+ // if I'm a spy, cloak and disguise while I retreat
+ if ( me->IsPlayerClass( TF_CLASS_SPY ) )
+ {
+ if ( !me->m_Shared.IsStealthed() )
+ {
+ me->PressAltFireButton();
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotRetreatToCover::Update( CTFBot *me, float interval )
+{
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat( true );
+
+ if ( me->m_Shared.InCond( TF_COND_INVULNERABLE ) )
+ return Done( "I'm invulnerable - no need to retreat!" );
+
+ if ( ShouldRetreat( me ) == ANSWER_NO )
+ return Done( "No longer need to retreat" );
+
+ // attack while retreating
+ me->EquipBestWeaponForThreat( threat );
+
+ // reload while moving to cover
+ bool isDoingAFullReload = false;
+ CTFWeaponBase *myPrimary = (CTFWeaponBase *)me->Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
+ if ( myPrimary && me->GetAmmoCount( TF_AMMO_PRIMARY ) > 0 && me->IsBarrageAndReloadWeapon( myPrimary ) )
+ {
+ if ( myPrimary->Clip1() < myPrimary->GetMaxClip1() )
+ {
+ me->PressReloadButton();
+ isDoingAFullReload = true;
+ }
+ }
+
+
+ // move to cover, or stop if we've found opportunistic cover (no visible threats right now)
+ if ( me->GetLastKnownArea() == m_coverArea || !threat )
+ {
+ // we are now in cover
+
+ if ( threat )
+ {
+ // threats are still visible - find new cover
+ m_coverArea = FindCoverArea( me );
+
+ if ( m_coverArea == NULL )
+ {
+ return Done( "My cover is exposed, and there is no other cover available!" );
+ }
+ }
+
+ if ( me->IsPlayerClass( TF_CLASS_SPY ) && !me->m_Shared.InCond( TF_COND_DISGUISED ) )
+ {
+ // don't leave cover until my disguise kicks in
+ return Continue();
+ }
+
+ // uncloak so we can attack when we leave cover
+ if ( me->m_Shared.IsStealthed() )
+ {
+ me->PressAltFireButton();
+ }
+
+ if ( m_actionToChangeToOnceCoverReached )
+ {
+ return ChangeTo( m_actionToChangeToOnceCoverReached, "Doing given action now that I'm in cover" );
+ }
+
+ // if I'm being healed by a medic who nearly has his charge built up, wait in cover until his charge is ready
+ int numHealers = me->m_Shared.GetNumHealers();
+ for ( int i=0; i<numHealers; ++i )
+ {
+ CTFPlayer *medic = ToTFPlayer( me->m_Shared.GetHealerByIndex( i ) );
+
+ if ( medic && medic->MedicGetChargeLevel() > 0.9f )
+ {
+ // wait for uber to finish
+ return Continue();
+ }
+ }
+
+ // stay in cover while we fully reload
+ if ( isDoingAFullReload )
+ {
+ return Continue();
+ }
+
+ if ( m_waitInCoverTimer.IsElapsed() )
+ {
+ return Done( "Been in cover long enough" );
+ }
+ }
+ else
+ {
+ // not in cover yet
+ m_waitInCoverTimer.Reset();
+
+ if ( m_repathTimer.IsElapsed() )
+ {
+ m_repathTimer.Start( RandomFloat( 0.3f, 0.5f ) );
+
+ CTFBotPathCost cost( me, RETREAT_ROUTE );
+ m_path.Compute( me, m_coverArea->GetCenter(), cost );
+ }
+
+ m_path.Update( me );
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotRetreatToCover::OnStuck( CTFBot *me )
+{
+ return TryContinue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotRetreatToCover::OnMoveToSuccess( CTFBot *me, const Path *path )
+{
+ return TryContinue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotRetreatToCover::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
+{
+ return TryContinue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+// Hustle yer butt to safety!
+QueryResultType CTFBotRetreatToCover::ShouldHurry( const INextBot *me ) const
+{
+ return ANSWER_YES;
+}
+
+