You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
8.7 KiB
317 lines
8.7 KiB
//========= 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; |
|
} |
|
|
|
|
|
|