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.
253 lines
7.2 KiB
253 lines
7.2 KiB
//========= 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; |
|
}
|
|
|