Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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

//========= 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;
}