This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions
@@ -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;
}
@@ -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
@@ -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;
}
@@ -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
@@ -0,0 +1,123 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h"
#include "bot/map_entities/tf_bot_hint_sentrygun.h"
#include "bot/map_entities/tf_bot_hint_teleporter_exit.h"
#include "string_t.h"
#include "tf_fx.h"
extern ConVar tf_bot_engineer_mvm_building_health_multiplier;
//---------------------------------------------------------------------------------------------
CTFBotMvMEngineerBuildSentryGun::CTFBotMvMEngineerBuildSentryGun( CTFBotHintSentrygun* pSentryHint )
{
m_sentryBuildHint = pSentryHint;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerBuildSentryGun::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
me->StartBuildingObjectOfType( OBJ_SENTRYGUN );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerBuildSentryGun::Update( CTFBot *me, float interval )
{
if ( m_sentryBuildHint == NULL )
return Done( "No hint entity" );
float rangeToBuildSpot = me->GetRangeTo( m_sentryBuildHint->GetAbsOrigin() );
if ( rangeToBuildSpot < 200.0f )
{
// crouch as we get close so we don't overshoot
me->PressCrouchButton();
me->GetBodyInterface()->AimHeadTowards( m_sentryBuildHint->GetAbsOrigin(), IBody::MANDATORY, 0.1f, NULL, "Placing sentry" );
}
// various interruptions could mean we're away from our build location - move to it
if ( rangeToBuildSpot > 25.0f )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, m_sentryBuildHint->GetAbsOrigin(), cost );
}
m_path.Update( me );
if ( !m_path.IsValid() )
{
return Done( "Path failed" );
}
return Continue();
}
if ( !m_delayBuildTime.HasStarted() )
{
m_delayBuildTime.Start( 0.1f );
TFGameRules()->PushAllPlayersAway( m_sentryBuildHint->GetAbsOrigin(), 400, 500, TF_TEAM_RED );
}
else if ( m_delayBuildTime.HasStarted() && m_delayBuildTime.IsElapsed() )
{
// destroy previous object
me->DetonateObjectOfType( OBJ_SENTRYGUN, MODE_SENTRYGUN_NORMAL, true );
// directly create a sentry gun at the precise position and orientation desired
m_sentry = (CObjectSentrygun *)CreateEntityByName( "obj_sentrygun" );
if ( m_sentry )
{
m_sentry->SetName( m_sentryBuildHint->GetEntityName() );
m_sentryBuildHint->IncrementUseCount();
m_sentry->m_nDefaultUpgradeLevel = 2;
m_sentry->SetAbsOrigin( m_sentryBuildHint->GetAbsOrigin() );
m_sentry->SetAbsAngles( QAngle( 0, m_sentryBuildHint->GetAbsAngles().y, 0 ) );
m_sentry->Spawn();
m_sentry->StartPlacement( me );
m_sentry->StartBuilding( me );
// the sentry owns this hint now
m_sentryBuildHint->SetOwnerEntity( m_sentry );
m_sentry = NULL;
}
return Done( "Built a sentry" );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotMvMEngineerBuildSentryGun::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
if ( m_sentry.Get() )
{
m_sentry->DropCarriedObject( me );
UTIL_Remove( m_sentry );
m_sentry = NULL;
}
}
@@ -0,0 +1,29 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#ifndef TF_BOT_MVM_ENGINEER_BUILD_SENTRYGUN_H
#define TF_BOT_MVM_ENGINEER_BUILD_SENTRYGUN_H
class CTFBotHintSentrygun;
class CTFBotMvMEngineerBuildSentryGun : public Action< CTFBot >
{
public:
CTFBotMvMEngineerBuildSentryGun( CTFBotHintSentrygun* pSentryHint );
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 const char *GetName( void ) const { return "MvMEngineerBuildSentryGun"; };
private:
CHandle< CTFBotHintSentrygun > m_sentryBuildHint;
CHandle< CObjectSentrygun > m_sentry;
CountdownTimer m_delayBuildTime;
CountdownTimer m_repathTimer;
PathFollower m_path;
};
#endif // TF_BOT_MVM_ENGINEER_BUILD_SENTRYGUN_H
@@ -0,0 +1,105 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#include "cbase.h"
#include "string_t.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h"
#include "bot/map_entities/tf_bot_hint_teleporter_exit.h"
ConVar tf_bot_engineer_mvm_building_health_multiplier( "tf_bot_engineer_building_health_multiplier", "2", FCVAR_CHEAT );
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
CTFBotMvMEngineerBuildTeleportExit::CTFBotMvMEngineerBuildTeleportExit( CTFBotHintTeleporterExit *hint )
{
m_teleporterBuildHint = hint;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerBuildTeleportExit::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerBuildTeleportExit::Update( CTFBot *me, float interval )
{
if ( m_teleporterBuildHint == NULL )
return Done( "No hint entity" );
// various interruptions could mean we're away from our build location - move to it
if ( me->IsRangeGreaterThan( m_teleporterBuildHint->GetAbsOrigin(), 25.0f ) )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, m_teleporterBuildHint->GetAbsOrigin(), cost );
}
m_path.Update( me );
if ( !m_path.IsValid() )
{
return Done( "Path failed" );
}
return Continue();
}
if ( !m_delayBuildTime.HasStarted() )
{
m_delayBuildTime.Start( 0.1f );
TFGameRules()->PushAllPlayersAway( m_teleporterBuildHint->GetAbsOrigin(), 400, 500, TF_TEAM_RED );
}
else if ( m_delayBuildTime.IsElapsed() )
{
// destroy previous object
me->DetonateObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT, true );
// directly create at the precise position and orientation desired
CObjectTeleporter* myTeleporter = (CObjectTeleporter *)CreateEntityByName( "obj_teleporter" );
if ( myTeleporter )
{
myTeleporter->SetAbsOrigin( m_teleporterBuildHint->GetAbsOrigin() );
myTeleporter->SetAbsAngles( QAngle( 0, m_teleporterBuildHint->GetAbsAngles().y, 0 ) );
myTeleporter->SetObjectMode( MODE_TELEPORTER_EXIT );
myTeleporter->Spawn();
myTeleporter->SetTeleportWhere( me->GetTeleportWhere() );
if ( me->ShouldQuickBuild() )
{
myTeleporter->ForceQuickBuild();
}
myTeleporter->StartPlacement( me );
myTeleporter->StartBuilding( me );
int iHealth = myTeleporter->GetMaxHealthForCurrentLevel() * tf_bot_engineer_mvm_building_health_multiplier.GetFloat();
myTeleporter->SetMaxHealth( iHealth );
myTeleporter->SetHealth( iHealth );
m_teleporterBuildHint->SetOwnerEntity( myTeleporter );
me->EmitSound( "Engineer.MVM_AutoBuildingTeleporter02" );
return Done( "Teleport exit built" );
}
}
return Continue();
}
@@ -0,0 +1,27 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#ifndef TF_BOT_MVM_ENGINEER_BUILD_TELEPORTER_H
#define TF_BOT_MVM_ENGINEER_BUILD_TELEPORTER_H
class CTFBotHintTeleporterExit;
class CTFBotMvMEngineerBuildTeleportExit : public Action< CTFBot >
{
public:
CTFBotMvMEngineerBuildTeleportExit( CTFBotHintTeleporterExit *hint );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual const char *GetName( void ) const { return "MvMEngineerBuildTeleportExit"; };
private:
CHandle< CTFBotHintTeleporterExit > m_teleporterBuildHint;
CountdownTimer m_delayBuildTime;
CountdownTimer m_repathTimer;
PathFollower m_path;
};
#endif // TF_BOT_MVM_ENGINEER_BUILD_TELEPORTER_H
@@ -0,0 +1,662 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#include "cbase.h"
#include "nav_mesh/tf_nav_mesh.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_teleporter.h"
#include "bot/tf_bot.h"
#include "bot/map_entities/tf_bot_hint_engineer_nest.h"
#include "bot/map_entities/tf_bot_hint_sentrygun.h"
#include "bot/map_entities/tf_bot_hint_teleporter_exit.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h"
#include "bot/behavior/tf_bot_retreat_to_cover.h"
ConVar tf_bot_engineer_mvm_sentry_hint_bomb_forward_range( "tf_bot_engineer_mvm_sentry_hint_bomb_forward_range", "0", FCVAR_CHEAT );
ConVar tf_bot_engineer_mvm_sentry_hint_bomb_backward_range( "tf_bot_engineer_mvm_sentry_hint_bomb_backward_range", "3000", FCVAR_CHEAT );
ConVar tf_bot_engineer_mvm_hint_min_distance_from_bomb( "tf_bot_engineer_mvm_hint_min_distance_from_bomb", "1300", FCVAR_CHEAT );
struct BombInfo_t
{
Vector m_vPosition;
float m_flMinBattleFront;
float m_flMaxBattleFront;
};
bool GetBombInfo( BombInfo_t* pBombInfo = NULL )
{
// find the incursion distance of the current "front" (the location of the bomb)
// first find farthest bomb delivery distance of invading team since maps
// have different spawn room sizes and geometries
float battlefront = 0.0f;
for( int n=0; n<TheNavAreas.Count(); ++n )
{
CTFNavArea *area = (CTFNavArea *)TheNavAreas[n];
if ( area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
{
continue;
}
float areaDistanceToTarget = area->GetTravelDistanceToBombTarget();
if ( areaDistanceToTarget > battlefront && areaDistanceToTarget > 0.0f )
{
battlefront = areaDistanceToTarget;
}
}
// find the travel distance from the bomb to the delivery target and use it as the front
CCaptureFlag *flag = NULL;
Vector vBombSpot(0, 0, 0);
for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
{
CCaptureFlag *pTempFlag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[i] );
Vector vTempBombSpot;
CTFPlayer *carrier = ToTFPlayer( pTempFlag->GetOwnerEntity() );
if ( carrier )
{
vTempBombSpot = carrier->GetAbsOrigin();
}
else
{
vTempBombSpot = pTempFlag->WorldSpaceCenter();
}
CTFNavArea *flagArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( vTempBombSpot, false, 1000.0f );
if ( flagArea )
{
float flagDistanceToTarget = flagArea->GetTravelDistanceToBombTarget();
if ( flagDistanceToTarget < battlefront && flagDistanceToTarget >= 0.0f )
{
battlefront = flagDistanceToTarget;
flag = pTempFlag;
vBombSpot = vTempBombSpot;
}
}
}
float flMaxBattlefront = battlefront + tf_bot_engineer_mvm_sentry_hint_bomb_backward_range.GetFloat();
float flMinBattlefront = battlefront - tf_bot_engineer_mvm_sentry_hint_bomb_forward_range.GetFloat();
if ( pBombInfo )
{
pBombInfo->m_vPosition = vBombSpot;
pBombInfo->m_flMinBattleFront = flMinBattlefront;
pBombInfo->m_flMaxBattleFront = flMaxBattlefront;
}
return flag ? true : false;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerIdle::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
me->StopLookingAroundForEnemies();
m_sentryHint = NULL;
m_teleporterHint = NULL;
m_nestHint = NULL;
m_nTeleportedCount = 0;
m_bTeleportedToHint = false;
m_bTriedToDetonateStaleNest = false;
return Continue();
}
void CTFBotMvMEngineerIdle::TakeOverStaleNest( CBaseTFBotHintEntity* pHint, CTFBot *me )
{
if ( pHint != NULL && pHint->OwnerObjectHasNoOwner() )
{
CBaseObject* pObj = static_cast< CBaseObject* >( pHint->GetOwnerEntity() );
pObj->SetOwnerEntity( me );
pObj->SetBuilder( me );
me->AddObject( pObj );
}
}
bool CTFBotMvMEngineerIdle::ShouldAdvanceNestSpot( CTFBot *me )
{
if ( !m_nestHint )
{
return false;
}
if ( !m_reevaluateNestTimer.HasStarted() )
{
m_reevaluateNestTimer.Start( 5.f );
return false;
}
for ( int i=0; i<me->GetObjectCount(); ++i )
{
CBaseObject *pObj = me->GetObject( i );
if ( pObj && pObj->GetHealth() < pObj->GetMaxHealth() )
{
// if the nest is under attack, don't advance the nest
m_reevaluateNestTimer.Start( 5.f );
return false;
}
}
if ( m_reevaluateNestTimer.IsElapsed() )
{
m_reevaluateNestTimer.Invalidate();
}
BombInfo_t bombInfo;
if ( GetBombInfo( &bombInfo ) )
{
if ( m_nestHint )
{
CTFNavArea *hintArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( m_nestHint->GetAbsOrigin(), false, 1000.0f );
if ( hintArea )
{
float hintDistanceToTarget = hintArea->GetTravelDistanceToBombTarget();
bool bShouldAdvance = ( hintDistanceToTarget > bombInfo.m_flMaxBattleFront );
return bShouldAdvance;
}
}
}
return false;
}
void CTFBotMvMEngineerIdle::TryToDetonateStaleNest()
{
if ( m_bTriedToDetonateStaleNest )
return;
// wait until the engy finish building his nest
if ( ( m_sentryHint && !m_sentryHint->OwnerObjectFinishBuilding() ) ||
( m_teleporterHint && !m_teleporterHint->OwnerObjectFinishBuilding() ) )
return;
// collect all existing and active teleporter hints
CUtlVector< CTFBotHintEngineerNest* > activeEngineerNest;
for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i )
{
CBaseTFBotHintEntity *pHint = static_cast<CBaseTFBotHintEntity*>( ITFBotHintEntityAutoList::AutoList()[i] );
if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_ENGINEER_NEST ) && pHint->IsEnabled() && pHint->GetOwnerEntity() == NULL )
{
activeEngineerNest.AddToTail( static_cast< CTFBotHintEngineerNest* >( pHint ) );
}
}
// try to detonate stale nest that's out of range, when engineer finished building his nest
for ( int i=0; i<activeEngineerNest.Count(); ++i )
{
CTFBotHintEngineerNest *pNest = activeEngineerNest[i];
if ( pNest->IsStaleNest() )
{
pNest->DetonateStaleNest();
}
}
m_bTriedToDetonateStaleNest = true;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerIdle::Update( CTFBot *me, float interval )
{
if ( !me->IsAlive() )
{
// don't do anything when I'm dead
return Done();
}
// Always equip my wrench
CBaseCombatWeapon *wrench = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
if ( wrench )
{
me->Weapon_Switch( wrench );
}
if ( m_nestHint == NULL || ShouldAdvanceNestSpot( me ) )
{
if ( m_findHintTimer.HasStarted() && !m_findHintTimer.IsElapsed() )
{
// too soon
return Continue();
}
m_findHintTimer.Start( RandomFloat( 1.0f, 2.0f ) );
// figure out where to teleport into the map
bool bShouldTeleportToHint = me->HasAttribute( CTFBot::TELEPORT_TO_HINT );
bool bShouldCheckForBlockingObject = !m_bTeleportedToHint && bShouldTeleportToHint;
CHandle< CTFBotHintEngineerNest > newNest = NULL;
if ( !CTFBotMvMEngineerHintFinder::FindHint( bShouldCheckForBlockingObject, !bShouldTeleportToHint, &newNest ) )
{
// try again next time
return Continue();
}
// unown the old nest
if ( m_nestHint )
{
m_nestHint->SetOwnerEntity( NULL );
}
m_nestHint = newNest;
m_nestHint->SetOwnerEntity( me );
m_sentryHint = m_nestHint->GetSentryHint();
TakeOverStaleNest( m_sentryHint, me );
if ( me->GetTeleportWhere().Count() > 0 )
{
m_teleporterHint = m_nestHint->GetTeleporterHint();
TakeOverStaleNest( m_teleporterHint, me );
}
}
if ( !m_bTeleportedToHint && me->HasAttribute( CTFBot::TELEPORT_TO_HINT ) )
{
m_nTeleportedCount++;
bool bFirstTeleportSpawn = m_nTeleportedCount == 1;
m_bTeleportedToHint = true;
return SuspendFor( new CTFBotMvMEngineerTeleportSpawn( m_nestHint, bFirstTeleportSpawn ), "In spawn area - teleport to the teleporter hint" );
}
const float rebuildInterval = 3.0f;
CObjectSentrygun *mySentry = NULL;
if ( m_sentryHint )
{
if ( m_sentryHint->GetOwnerEntity() && m_sentryHint->GetOwnerEntity()->IsBaseObject() )
{
mySentry = assert_cast< CObjectSentrygun* >( m_sentryHint->GetOwnerEntity() );
}
if ( mySentry )
{
// force an interval between sentry being destroyed and me trying to rebuild it
m_sentryRebuildTimer.Start( rebuildInterval );
}
else
{
// check if there's a stale object on the hint
if ( m_sentryHint->GetOwnerEntity() && m_sentryHint->GetOwnerEntity()->IsBaseObject() )
{
mySentry = assert_cast< CObjectSentrygun* >( m_sentryHint->GetOwnerEntity() );
me->AddObject( mySentry );
mySentry->SetOwnerEntity( me );
}
else
{
if ( m_sentryRebuildTimer.IsElapsed() )
{
return SuspendFor( new CTFBotMvMEngineerBuildSentryGun( m_sentryHint ), "No sentry - building a new one" );
}
else
{
// run away!
return SuspendFor( new CTFBotRetreatToCover( 1.0f ), "Lost my sentry - retreat!" );
}
}
}
}
if ( mySentry && mySentry->GetHealth() < mySentry->GetMaxHealth() && !mySentry->IsBuilding() )
{
// track when sentry was last hurt
m_sentryInjuredTimer.Start( 3.0f );
}
CObjectTeleporter *myTeleporter = NULL;
if ( m_teleporterHint && m_sentryInjuredTimer.IsElapsed() )
{
if ( m_teleporterHint->GetOwnerEntity() && m_teleporterHint->GetOwnerEntity()->IsBaseObject() )
{
// force an interval between teleporter being destroyed and me trying to rebuild it
myTeleporter = assert_cast< CObjectTeleporter* >( m_teleporterHint->GetOwnerEntity() );
m_teleporterRebuildTimer.Start( rebuildInterval );
}
else if ( m_teleporterRebuildTimer.IsElapsed() )
{
return SuspendFor( new CTFBotMvMEngineerBuildTeleportExit( m_teleporterHint ), "Sentry is safe - building a teleport exit" );
}
}
// fix teleporter if sentry is not hurt
if ( myTeleporter && m_sentryInjuredTimer.IsElapsed() && myTeleporter->GetHealth() < myTeleporter->GetMaxHealth() && !myTeleporter->IsBuilding() )
{
float rangeToTeleporter = me->GetDistanceBetween( myTeleporter );
const float nearTeleporterRange = 75.0f;
if ( rangeToTeleporter < 1.2f * nearTeleporterRange )
{
// crouch as I get close
me->PressCrouchButton();
}
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
Vector toTeleporter = myTeleporter->GetAbsOrigin() - me->GetAbsOrigin();
Vector hittingTeleporterSpot = myTeleporter->GetAbsOrigin() - 50.0f * toTeleporter.Normalized();
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, hittingTeleporterSpot, cost );
}
m_path.Update( me );
if ( rangeToTeleporter < nearTeleporterRange )
{
// we are in position - hit sentry with wrench
me->GetBodyInterface()->AimHeadTowards( myTeleporter->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my Teleporter" );
me->PressFireButton();
}
}
else if ( mySentry )
{
float rangeToSentry = me->GetDistanceBetween( mySentry );
const float nearSentryRange = 75.0f;
if ( rangeToSentry < 1.2f * nearSentryRange )
{
// crouch as I get close
me->PressCrouchButton();
}
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
Vector mySentryForward;
AngleVectors( mySentry->GetTurretAngles(), &mySentryForward );
Vector behindSentrySpot = mySentry->GetAbsOrigin() - 50.0f * mySentryForward;
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, behindSentrySpot, cost );
}
m_path.Update( me );
if ( rangeToSentry < nearSentryRange )
{
// we are in position - hit sentry with wrench
me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my Sentry" );
me->PressFireButton();
}
}
TryToDetonateStaleNest();
return Continue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotMvMEngineerIdle::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
return ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotMvMEngineerIdle::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotMvMEngineerIdle::ShouldHurry( const INextBot *me ) const
{
return ANSWER_YES;
}
CTFBotHintEngineerNest* SelectOutOfRangeNest( const CUtlVector< CTFBotHintEngineerNest* >& nestVector )
{
if ( nestVector.Count() )
{
for ( int i=0; i<nestVector.Count(); ++i )
{
if ( nestVector[i]->IsStaleNest() )
{
return nestVector[i];
}
}
int which = RandomInt( 0, nestVector.Count() - 1 );
return nestVector[which];
}
return NULL;
}
//---------------------------------------------------------------------------------------------
bool CTFBotMvMEngineerHintFinder::FindHint( bool bShouldCheckForBlockingObjects, bool bAllowOutOfRangeNest, CHandle< CTFBotHintEngineerNest >* pFoundNest /*= NULL*/ )
{
// collect all existing and active teleporter hints
CUtlVector< CTFBotHintEngineerNest* > activeEngineerNest;
for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i )
{
CBaseTFBotHintEntity *pHint = static_cast<CBaseTFBotHintEntity*>( ITFBotHintEntityAutoList::AutoList()[i] );
if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_ENGINEER_NEST ) && pHint->IsEnabled() && pHint->GetOwnerEntity() == NULL )
{
activeEngineerNest.AddToTail( static_cast< CTFBotHintEngineerNest* >( pHint ) );
}
}
if ( activeEngineerNest.Count() == 0 )
{
if ( pFoundNest )
{
*pFoundNest = NULL;
}
return false;
}
BombInfo_t bombInfo;
GetBombInfo( &bombInfo );
CUtlVector< CTFBotHintEngineerNest* > forwardOutOfRangeHintVector;
CUtlVector< CTFBotHintEngineerNest* > backwardOutOfRangeHintVector;
CUtlVector< CTFBotHintEngineerNest* > freeAtFrontHintVector;
CUtlVector< CTFBotHintEngineerNest* > staleAtFrontHintVector;
for( int i=0; i<activeEngineerNest.Count(); ++i )
{
CTFBotHintEngineerNest* pCurrentNest = activeEngineerNest[i];
const Vector& vNestPosition = pCurrentNest->GetAbsOrigin();
CTFNavArea *hintArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( vNestPosition, false, 1000.0f );
if ( !hintArea )
{
Warning( "Sentry hint has NULL nav area!\n" );
continue;
}
float hintDistanceToTarget = hintArea->GetTravelDistanceToBombTarget();
if ( hintDistanceToTarget > bombInfo.m_flMinBattleFront && hintDistanceToTarget < bombInfo.m_flMaxBattleFront )
{
if ( bShouldCheckForBlockingObjects )
{
// check for blocking players and objects
CBaseEntity *pList[256];
int count = UTIL_EntitiesInBox( pList, ARRAYSIZE( pList ), vNestPosition + VEC_HULL_MIN, vNestPosition + VEC_HULL_MAX, FL_CLIENT|FL_OBJECT );
if ( count > 0 )
{
continue;
}
}
// this hint is in range of the front
if ( pCurrentNest->IsStaleNest() )
{
// some dead engineer was here and left his object(s) behind. I should take over
staleAtFrontHintVector.AddToTail( pCurrentNest );
}
else
{
if ( VectorLength( bombInfo.m_vPosition - vNestPosition ) < tf_bot_engineer_mvm_hint_min_distance_from_bomb.GetFloat() )
{
// the hint is too close to the bomb, don't go there
continue;
}
// this hint is also unowned
freeAtFrontHintVector.AddToTail( pCurrentNest );
}
}
else if ( hintDistanceToTarget > bombInfo.m_flMaxBattleFront )
{
forwardOutOfRangeHintVector.AddToTail( pCurrentNest );
}
else
{
backwardOutOfRangeHintVector.AddToTail( pCurrentNest );
}
}
CTFBotHintEngineerNest *hint = NULL;
if ( freeAtFrontHintVector.Count() == 0 && staleAtFrontHintVector.Count() == 0 )
{
if ( bAllowOutOfRangeNest )
{
// try to advance forward before falling backward
hint = SelectOutOfRangeNest( forwardOutOfRangeHintVector );
if ( !hint )
{
hint = SelectOutOfRangeNest( backwardOutOfRangeHintVector );
}
}
// no hints are in range, or they are all in use
if ( pFoundNest )
{
*pFoundNest = hint;
}
}
else
{
// try to pick stale nest in range first
if ( staleAtFrontHintVector.Count() )
{
int whichHint = RandomInt( 0, staleAtFrontHintVector.Count()-1 );
hint = staleAtFrontHintVector[ whichHint ];
}
// if I didn't find any stale nest, try to find a free one
else if ( freeAtFrontHintVector.Count() )
{
int whichHint = RandomInt( 0, freeAtFrontHintVector.Count()-1 );
hint = freeAtFrontHintVector[ whichHint ];
}
if ( pFoundNest )
{
*pFoundNest = hint;
}
}
return hint != NULL;
}
//--------------------------------------------------------------------------------------------------------------
CON_COMMAND_F( tf_bot_mvm_show_engineer_hint_region, "Show the nav areas MvM engineer bots will consider when selecting sentry and teleporter hints", FCVAR_CHEAT )
{
if ( !UTIL_IsCommandIssuedByServerAdmin() )
return;
CBasePlayer *pPlayer = UTIL_GetCommandClient();
trace_t result;
Vector forward;
pPlayer->EyeVectors( &forward );
UTIL_TraceLine( pPlayer->EyePosition(),
pPlayer->EyePosition() + forward * 10000.0f, MASK_SOLID,
pPlayer, COLLISION_GROUP_NONE, &result );
float flDrawTime = 5.0f;
if ( result.DidHit() )
{
CTFNavArea *area = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( result.endpos );
if ( area )
{
float battlefront = area->GetTravelDistanceToBombTarget();
float maxBattlefront = battlefront + tf_bot_engineer_mvm_sentry_hint_bomb_backward_range.GetFloat();
float minBattlefront = battlefront - tf_bot_engineer_mvm_sentry_hint_bomb_forward_range.GetFloat();
CUtlVector< CTFNavArea * > battlefrontAreaVector;
TheTFNavMesh()->CollectAreaWithinBombTravelRange( &battlefrontAreaVector, minBattlefront, maxBattlefront );
CUtlVector< CTFNavArea * > hintAreaVector;
for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i )
{
CBaseTFBotHintEntity *pHint = static_cast< CBaseTFBotHintEntity* >( ITFBotHintEntityAutoList::AutoList()[i] );
hintAreaVector.AddToTail( (CTFNavArea*)TheNavMesh->GetNearestNavArea( pHint ) );
}
for( int i=0; i<battlefrontAreaVector.Count(); ++i )
{
CTFNavArea *fillArea = battlefrontAreaVector[i];
if ( fillArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE || TF_NAV_SPAWN_ROOM_RED ) )
{
continue;
}
fillArea->DrawFilled( 255, 100, 0, 0, flDrawTime );
for ( int j=0; j<hintAreaVector.Count(); ++j )
{
if ( fillArea == hintAreaVector[j] )
{
CBaseTFBotHintEntity *pHint = static_cast< CBaseTFBotHintEntity* >( ITFBotHintEntityAutoList::AutoList()[j] );
Color color;
if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_SENTRYGUN ) )
{
color = Color( 0, 255, 0 );
}
else if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_TELEPORTER_EXIT ) )
{
color = Color( 0, 0, 255 );
}
else
{
bool bTooCloseToBomb = VectorLength( result.endpos - pHint->GetAbsOrigin() ) < tf_bot_engineer_mvm_hint_min_distance_from_bomb.GetFloat();
color = bTooCloseToBomb ? Color( 255, 0, 0 ) : Color( 255, 255, 0 );
}
NDebugOverlay::Sphere( pHint->GetAbsOrigin(), 50, color.r(), color.g(), color.b(), true, flDrawTime );
}
}
}
NDebugOverlay::Sphere( result.endpos, tf_bot_engineer_mvm_hint_min_distance_from_bomb.GetFloat(), 255, 255, 0, false, flDrawTime );
}
}
}
@@ -0,0 +1,55 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#ifndef TF_BOT_MVM_ENGINEER_IDLE_H
#define TF_BOT_MVM_ENGINEER_IDLE_H
#include "Path/NextBotPathFollow.h"
class CBaseTFBotHintEntity;
class CTFBotHintSentrygun;
class CTFBotHintTeleporterExit;
class CTFBotHintEngineerNest;
class CTFBotMvMEngineerIdle : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const;
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual const char *GetName( void ) const { return "MvMEngineerIdle"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
CountdownTimer m_sentryInjuredTimer;
CountdownTimer m_sentryRebuildTimer;
CountdownTimer m_teleporterRebuildTimer;
CountdownTimer m_findHintTimer;
CountdownTimer m_reevaluateNestTimer;
int m_nTeleportedCount;
bool m_bTeleportedToHint;
CHandle< CTFBotHintTeleporterExit > m_teleporterHint;
CHandle< CTFBotHintSentrygun > m_sentryHint;
CHandle< CTFBotHintEngineerNest > m_nestHint;
void TakeOverStaleNest( CBaseTFBotHintEntity* pHint, CTFBot *me );
bool ShouldAdvanceNestSpot( CTFBot *me );
void TryToDetonateStaleNest();
bool m_bTriedToDetonateStaleNest;
};
class CTFBotMvMEngineerHintFinder
{
public:
static bool FindHint( bool bShouldCheckForBlockingObjects, bool bAllowOutOfRangeNest, CHandle< CTFBotHintEngineerNest >* pFoundNest = NULL );
};
#endif // TF_BOT_MVM_ENGINEER_IDLE_H
@@ -0,0 +1,95 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_gamerules.h"
#include "bot/tf_bot.h"
#include "tf_obj_sentrygun.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h"
#include "bot/map_entities/tf_bot_hint_entity.h"
#include "string_t.h"
#include "tf_fx.h"
#include "player_vs_environment/tf_population_manager.h"
//---------------------------------------------------------------------------------------------
CTFBotMvMEngineerTeleportSpawn::CTFBotMvMEngineerTeleportSpawn( CBaseTFBotHintEntity* pHint, bool bFirstTeleportSpawn )
{
m_hintEntity = pHint;
m_bFirstTeleportSpawn = bFirstTeleportSpawn;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerTeleportSpawn::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
if ( !me->HasAttribute( CTFBot::TELEPORT_TO_HINT ) )
{
return Done( "Cannot teleport to hint with out Attributes TeleportToHint" );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerTeleportSpawn::Update( CTFBot *me, float interval )
{
if ( !m_teleportDelay.HasStarted() )
{
m_teleportDelay.Start( 0.1f );
if ( m_hintEntity )
TFGameRules()->PushAllPlayersAway( m_hintEntity->GetAbsOrigin(), 400, 500, TF_TEAM_RED );
}
else if ( m_teleportDelay.IsElapsed() )
{
if ( !m_hintEntity )
return Done( "Cannot teleport to hint as m_hintEntity is NULL" );
// teleport the engineer to the sentry spawn point
QAngle angles = m_hintEntity->GetAbsAngles();
Vector origin = m_hintEntity->GetAbsOrigin();
origin.z += 10.f; // move up off the around a little bit to prevent the engineer from getting stuck in the ground
me->Teleport( &origin, &angles, NULL );
CPVSFilter filter( origin );
TE_TFParticleEffect( filter, 0.0, "teleported_blue", origin, vec3_angle );
TE_TFParticleEffect( filter, 0.0, "player_sparkles_blue", origin, vec3_angle );
if ( m_bFirstTeleportSpawn )
{
// notify players that engineer's teleported into the map
TE_TFParticleEffect( filter, 0.0, "teleported_mvm_bot", origin, vec3_angle );
me->EmitSound( "Engineer.MVM_BattleCry07" );
m_hintEntity->EmitSound( "MVM.Robot_Engineer_Spawn" );
if ( g_pPopulationManager )
{
CWave *pWave = g_pPopulationManager->GetCurrentWave();
if ( pWave )
{
if ( pWave->NumEngineersTeleportSpawned() == 0 )
{
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_First_Engineer_Teleport_Spawned" );
}
else
{
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Another_Engineer_Teleport_Spawned" );
}
pWave->IncrementEngineerTeleportSpawned();
}
}
}
return Done( "Teleported" );
}
return Continue();
}
@@ -0,0 +1,28 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#ifndef TF_BOT_MVM_ENGINEER_TELEPORT_SPAWN_H
#define TF_BOT_MVM_ENGINEER_TELEPORT_SPAWN_H
class CBaseTFBotHintEntity;
class CTFBotMvMEngineerTeleportSpawn : public Action< CTFBot >
{
public:
CTFBotMvMEngineerTeleportSpawn( CBaseTFBotHintEntity* pHint, bool bFirstTeleportSpawn );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual const char *GetName( void ) const { return "MvMEngineerTeleportSpawn"; };
private:
CountdownTimer m_teleportDelay;
CHandle< CBaseTFBotHintEntity > m_hintEntity;
bool m_bFirstTeleportSpawn;
};
#endif // TF_BOT_MVM_ENGINEER_TELEPORT_SPAWN_H
@@ -0,0 +1,118 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build.cpp
// Engineer building his buildings
// Michael Booth, February 2009
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_build.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "raid/tf_raid_logic.h"
// this was useful when engineers build at their normal (slow) rate to make sure initial sentries get built in time
ConVar tf_raid_engineer_infinte_metal( "tf_raid_engineer_infinte_metal", "1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
//---------------------------------------------------------------------------------------------
Action< CTFBot > *CTFBotEngineerBuild::InitialContainedAction( CTFBot *me )
{
if ( TFGameRules()->IsPVEModeActive() )
{
return new CTFBotEngineerMoveToBuild;
}
return new CTFBotEngineerBuildTeleportEntrance;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuild::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuild::Update( CTFBot *me, float interval )
{
if ( TFGameRules()->IsPVEModeActive() && tf_raid_engineer_infinte_metal.GetBool() )
{
// infinite ammo
me->GiveAmmo( 1000, TF_AMMO_METAL, true );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuild::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerBuild::OnTerritoryLost( CTFBot *me, int territoryID )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
// Hack to disable ammo/health gathering elsewhere
QueryResultType CTFBotEngineerBuild::ShouldHurry( const INextBot *meBot ) const
{
CTFBot *me = (CTFBot *)meBot->GetEntity();
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER );
if ( mySentry && myDispenser && !mySentry->IsBuilding() && !myDispenser->IsBuilding() && me->GetActiveTFWeapon() && me->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_WRENCH )
{
if ( me->IsAmmoLow() && myDispenser->GetAvailableMetal() <= 0 )
{
// we're totally out of metal - collect some nearby
return ANSWER_NO;
}
// by being in a "hurry" we wont collect health and ammo
return ANSWER_YES;
}
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotEngineerBuild::ShouldAttack( const INextBot *meBot, const CKnownEntity *them ) const
{
CTFBot *me = (CTFBot *)meBot->GetEntity();
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
CTFPlayer *themPlayer = ToTFPlayer( them->GetEntity() );
if ( themPlayer && themPlayer->IsPlayerClass( TF_CLASS_SPY ) )
{
// Engineers hate Spies
return ANSWER_YES;
}
if ( mySentry && me->IsRangeLessThan( mySentry, 100.0f ) )
{
// focus on keeping our sentry alive
return ANSWER_NO;
}
return ANSWER_UNDEFINED;
}
@@ -0,0 +1,31 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build.h
// Engineer building his buildings
// Michael Booth, February 2009
#ifndef TF_BOT_ENGINEER_BUILD_H
#define TF_BOT_ENGINEER_BUILD_H
class CTFBotHintTeleporterExit;
class CTFBotEngineerBuild : public Action< CTFBot >
{
public:
virtual Action< CTFBot > *InitialContainedAction( CTFBot *me );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
virtual const char *GetName( void ) const { return "EngineerBuild"; };
};
#endif // TF_BOT_ENGINEER_BUILD_H
@@ -0,0 +1,212 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_dispenser.cpp
// Engineer building his Dispenser near his Sentry
// Michael Booth, May 2010
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_dispenser.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "bot/behavior/tf_bot_get_ammo.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildDispenser::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_placementTriesLeft = 3;
return Continue();
}
//---------------------------------------------------------------------------------------------
class PressFireButtonIfValidBuildPositionReply : public INextBotReply
{
public:
PressFireButtonIfValidBuildPositionReply( void )
{
m_builder = NULL;
}
void SetBuilder( CTFWeaponBuilder *builder )
{
m_builder = builder;
}
// invoked when process completed successfully
virtual void OnSuccess( INextBot *bot )
{
if ( m_builder != NULL && m_builder->IsValidPlacement() )
{
INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
if ( playerInput )
{
playerInput->PressFireButton();
}
}
}
CTFWeaponBuilder *m_builder;
};
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildDispenser::Update( CTFBot *me, float interval )
{
if ( me->GetTimeSinceLastInjury() < 1.0f )
{
return Done( "Ouch! I'm under attack" );
}
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
if ( !mySentry )
{
return Done( "No Sentry" );
}
if ( mySentry->GetTimeSinceLastInjury() < 1.0f || mySentry->GetHealth() < mySentry->GetMaxHealth() )
{
return Done( "Need to repair my Sentry" );
}
CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER );
if ( myDispenser )
{
return Done( "Dispenser built" );
}
if ( m_placementTriesLeft <= 0 )
{
return Done( "Can't find a place to build a Dispenser" );
}
if ( me->CanBuild( OBJ_DISPENSER ) == CB_NEED_RESOURCES )
{
if ( m_getAmmoTimer.IsElapsed() && CTFBotGetAmmo::IsPossible( me ) )
{
// need more metal - get some
m_getAmmoTimer.Start( 1.0f );
return SuspendFor( new CTFBotGetAmmo, "Need more metal to build" );
}
/*
else
{
// work on my sentry while I wait for ammo to show up
me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on sentry while I wait for ammo to show up" );
me->PressFireButton();
return Continue();
}
*/
}
/*
// if my sentry is under attack, forgo building a dispenser - focus on keeping the sentry alive
if ( mySentry->GetTimeSinceLastInjury() < 1.0f )
{
CBaseCombatWeapon *wrench = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
if ( wrench )
{
me->Weapon_Switch( wrench );
}
me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Focusing on keeping my besieged sentry alive" );
me->PressFireButton();
return Continue();
}
*/
// move behind the Sentry (our chosen build location)
Vector buildSpot = mySentry->GetAbsOrigin() - 75.0f * mySentry->BodyDirection2D();
// the ground might be steeply sloped (ie: stairs), so find the actual ground
buildSpot.z += HumanHeight;
TheNavMesh->GetSimpleGroundHeight( buildSpot, &buildSpot.z );
if ( me->IsDistanceBetweenLessThan( buildSpot, 100.0f ) )
{
// crouch as we get close so we slow down and hit our mark
me->PressCrouchButton();
}
// if too far away from our build location, move closer
if ( me->IsDistanceBetweenGreaterThan( buildSpot, 25.0f ) )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, buildSpot, cost );
}
m_path.Update( me );
return Continue();
}
// we're at our build spot behind our sentry now - build a Dispenser
CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( me->GetActiveTFWeapon() );
if ( !builder || builder->GetType() != OBJ_DISPENSER || builder->m_hObjectBeingBuilt == NULL )
{
// at home position, build the object
me->StartBuildingObjectOfType( OBJ_DISPENSER );
}
else if ( m_searchTimer.IsElapsed() )
{
// rotate to find valid spot
Vector toSentry = mySentry->GetAbsOrigin() - me->GetAbsOrigin();
toSentry.NormalizeInPlace();
Vector forward = -toSentry;
float angle = RandomFloat( -M_PI/2.0f, M_PI/2.0f );
float s, c;
FastSinCos( angle, &s, &c );
forward.x = toSentry.x * c - toSentry.y * s;
forward.y = toSentry.x * s + toSentry.y * c;
forward.z = 0.0f;
static PressFireButtonIfValidBuildPositionReply buildReply;
buildReply.SetBuilder( builder );
me->GetBodyInterface()->AimHeadTowards( me->EyePosition() - 100.0f * forward, IBody::CRITICAL, 1.0f, &buildReply, "Trying to place my dispenser" );
m_searchTimer.Start( 1.0f );
--m_placementTriesLeft;
}
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotEngineerBuildDispenser::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
me->GetBodyInterface()->ClearPendingAimReply();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildDispenser::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_path.Invalidate();
m_repathTimer.Invalidate();
me->GetBodyInterface()->ClearPendingAimReply();
return Continue();
}
@@ -0,0 +1,31 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_dispenser.h
// Engineer building his Dispenser near his Sentry
// Michael Booth, May 2010
#ifndef TF_BOT_ENGINEER_BUILD_DISPENSER_H
#define TF_BOT_ENGINEER_BUILD_DISPENSER_H
class CTFBotEngineerBuildDispenser : public Action< CTFBot >
{
public:
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 > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual const char *GetName( void ) const { return "EngineerBuildDispenser"; };
private:
CountdownTimer m_searchTimer;
CountdownTimer m_getAmmoTimer;
CountdownTimer m_repathTimer;
int m_placementTriesLeft;
PathFollower m_path;
};
#endif // TF_BOT_ENGINEER_BUILD_DISPENSER_H
@@ -0,0 +1,194 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_sentrygun.cpp
// Engineer building his Sentry gun
// Michael Booth, May 2010
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h"
#include "bot/behavior/tf_bot_get_ammo.h"
#include "bot/map_entities/tf_bot_hint_sentrygun.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuildSentryGun::CTFBotEngineerBuildSentryGun( void )
{
m_sentryBuildHint = NULL;
}
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuildSentryGun::CTFBotEngineerBuildSentryGun( CTFBotHintSentrygun *sentryBuildHint )
{
m_sentryBuildHint = sentryBuildHint;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildSentryGun::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_sentryTriesLeft = 5;
m_giveUpTimer.Invalidate();
m_searchTimer.Invalidate();
m_wanderWay = 1;
m_needToAimSentry = true;
m_sentryBuildLocation = ( m_sentryBuildHint == NULL ) ? me->GetAbsOrigin() : m_sentryBuildHint->GetAbsOrigin();
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildSentryGun::Update( CTFBot *me, float interval )
{
if ( me->GetTimeSinceLastInjury() < 1.0f )
{
return Done( "Ouch! I'm under attack" );
}
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
if ( mySentry )
{
return Done( "Sentry built" );
}
// collect metal as we move to our build location
if ( me->CanBuild( OBJ_SENTRYGUN ) == CB_NEED_RESOURCES )
{
if ( m_getAmmoTimer.IsElapsed() && CTFBotGetAmmo::IsPossible( me ) )
{
// need more metal - get some
m_getAmmoTimer.Start( 1.0f );
return SuspendFor( new CTFBotGetAmmo, "Need more metal to build my Sentry" );
}
}
// various interruptions could mean we're away from our build location - move to it
if ( me->IsRangeGreaterThan( m_sentryBuildLocation, 25.0f ) )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, m_sentryBuildLocation, cost );
}
m_path.Update( me );
if ( !m_path.IsValid() )
{
return Done( "Path failed" );
}
return Continue();
}
// we are at our build location
if ( m_sentryTriesLeft <= 0 )
{
// couldn't build here
return Done( "Couldn't find a place to build" );
}
// attempt to build a Sentry here
if ( m_sentryBuildHint != NULL )
{
// directly create a sentry gun at the precise position and orientation desired
CObjectSentrygun *mySentry = (CObjectSentrygun *)CreateEntityByName( "obj_sentrygun" );
if ( mySentry )
{
m_sentryBuildHint->IncrementUseCount();
mySentry->SetAbsOrigin( m_sentryBuildHint->GetAbsOrigin() );
mySentry->SetAbsAngles( QAngle( 0, m_sentryBuildHint->GetAbsAngles().y, 0 ) );
mySentry->Spawn();
mySentry->StartPlacement( me );
mySentry->StartBuilding( me );
}
}
else
{
// no precise build location - go through the normal build process
CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( me->GetActiveTFWeapon() );
if ( !builder || builder->GetType() != OBJ_SENTRYGUN || builder->m_hObjectBeingBuilt == NULL )
{
// at home position, build a sentry (switch to the sentry builder "gun")
me->StartBuildingObjectOfType( OBJ_SENTRYGUN );
return Continue();
}
// orient sentry towards where enemies enter this region
if ( m_needToAimSentry )
{
CTFNavArea *myArea = (CTFNavArea *)me->GetLastKnownArea();
if ( myArea )
{
CUtlVector< CTFNavArea * > invasionVector;
myArea->GetEnemyInvasionAreaVector( me->GetTeamNumber() );
if ( invasionVector.Count() > 0 )
{
// orient sentry towards where enemies enter this region
int which = RandomInt( 0, invasionVector.Count()-1 );
me->GetBodyInterface()->AimHeadTowards( invasionVector[ which ]->GetCenter(), IBody::CRITICAL, 1.0f, NULL, "Sentry build orientation" );
m_needToAimSentry = false;
}
}
}
if ( me->GetBodyInterface()->IsHeadSteady() )
{
if ( builder->IsValidPlacement() )
{
// build the sentry
me->PressFireButton();
}
else
{
// move around a bit to find valid spot
if ( m_searchTimer.IsElapsed() )
{
m_wanderWay = RandomInt( 0, 3 );
m_needToAimSentry = true;
m_searchTimer.Start( RandomFloat( 0.1f, 0.25f ) );
--m_sentryTriesLeft;
}
switch( m_wanderWay )
{
case 0: me->PressForwardButton(); break;
case 1: me->PressBackwardButton(); break;
case 2: me->PressRightButton(); break;
case 3: me->PressLeftButton(); break;
}
}
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildSentryGun::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_path.Invalidate();
m_repathTimer.Invalidate();
return Continue();
}
@@ -0,0 +1,44 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_sentrygun.h
// Engineer building his Sentry gun
// Michael Booth, May 2010
#ifndef TF_BOT_ENGINEER_BUILD_SENTRYGUN_H
#define TF_BOT_ENGINEER_BUILD_SENTRYGUN_H
class CTFBotHintSentrygun;
class CTFBotEngineerBuildSentryGun : public Action< CTFBot >
{
public:
CTFBotEngineerBuildSentryGun( void );
CTFBotEngineerBuildSentryGun( CTFBotHintSentrygun *sentryBuildHint );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual const char *GetName( void ) const { return "EngineerBuildSentryGun"; };
private:
CountdownTimer m_searchTimer;
CountdownTimer m_giveUpTimer;
CountdownTimer m_getAmmoTimer;
CountdownTimer m_repathTimer;
CountdownTimer m_buildTeleporterExitTimer;
int m_sentryTriesLeft;
PathFollower m_path;
CTFBotHintSentrygun *m_sentryBuildHint;
Vector m_sentryBuildLocation;
int m_wanderWay;
bool m_needToAimSentry;
Vector m_sentryBuildAimTarget;
};
#endif // TF_BOT_ENGINEER_BUILD_SENTRYGUN_H
@@ -0,0 +1,112 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_teleport_entrance.cpp
// Engineer building a teleport entrance right outside of the spawn room
// Michael Booth, May 2009
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "bot/behavior/tf_bot_get_ammo.h"
extern ConVar tf_bot_path_lookahead_range;
ConVar tf_bot_max_teleport_entrance_travel( "tf_bot_max_teleport_entrance_travel", "1500", FCVAR_CHEAT, "Don't plant teleport entrances farther than this travel distance from our spawn room" );
ConVar tf_bot_teleport_build_surface_normal_limit( "tf_bot_teleport_build_surface_normal_limit", "0.99", FCVAR_CHEAT, "If the ground normal Z component is less that this value, Engineer bots won't place their entrance teleporter" );
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildTeleportEntrance::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildTeleportEntrance::Update( CTFBot *me, float interval )
{
CTeamControlPoint *point = me->GetMyControlPoint();
if ( !point )
{
// wait until a control point becomes available
return Continue();
}
CTFNavArea *myArea = me->GetLastKnownArea();
if ( !myArea )
{
return Done( "No nav mesh!" );
}
if ( myArea->GetIncursionDistance( me->GetTeamNumber() ) > tf_bot_max_teleport_entrance_travel.GetFloat() )
{
return ChangeTo( new CTFBotEngineerMoveToBuild, "Too far from our spawn room to build teleporter entrance" );
}
// make sure we go back to our resupply cabinet after planting the teleporter entrance before we move on
if ( !me->IsAmmoFull() && CTFBotGetAmmo::IsPossible( me ) )
{
return SuspendFor( new CTFBotGetAmmo, "Refilling ammo" );
}
CBaseObject *myTeleportEntrance = me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE );
if ( myTeleportEntrance )
{
// successfully built
return ChangeTo( new CTFBotEngineerMoveToBuild, "Teleport entrance built" );
}
// head towards the control point and build as soon as we can
if ( !m_path.IsValid() )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, point->GetAbsOrigin(), cost );
}
m_path.Update( me );
// build
CTFWeaponBase *myGun = me->GetActiveTFWeapon();
if ( myGun )
{
CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( myGun );
if ( builder )
{
// don't build on slopes - causes player blockages
Vector forward;
me->EyeVectors( &forward );
const float placementRange = 30.0f;
forward *= placementRange;
trace_t result;
UTIL_TraceLine( me->WorldSpaceCenter() + Vector( forward.x, forward.y, 0.0f ), me->WorldSpaceCenter() + Vector( forward.x, forward.y, -200.0f ), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
if ( builder->IsValidPlacement() && result.DidHit() && result.plane.normal.z > tf_bot_teleport_build_surface_normal_limit.GetFloat() )
{
// place it down
me->PressFireButton();
}
}
else
{
// switch to teleporter builder
me->StartBuildingObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerBuildTeleportEntrance::OnStuck( CTFBot *me )
{
m_path.Invalidate();
return TryContinue();
}
@@ -0,0 +1,23 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_teleport_entrance.h
// Engineer building a teleport entrance right outside of the spawn room
// Michael Booth, May 2009
#ifndef TF_BOT_ENGINEER_BUILD_TELEPORT_ENTRANCE_H
#define TF_BOT_ENGINEER_BUILD_TELEPORT_ENTRANCE_H
class CTFBotEngineerBuildTeleportEntrance : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual const char *GetName( void ) const { return "EngineerBuildTeleportEntrance"; };
private:
PathFollower m_path;
};
#endif // TF_BOT_ENGINEER_BUILD_TELEPORT_ENTRANCE_H
@@ -0,0 +1,190 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_teleport_exit.cpp
// Engineer building a teleport exit
// Michael Booth, May 2010
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h"
#include "bot/behavior/tf_bot_get_ammo.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuildTeleportExit::CTFBotEngineerBuildTeleportExit( void )
{
m_hasPreciseBuildLocation = false;
}
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuildTeleportExit::CTFBotEngineerBuildTeleportExit( const Vector &buildLocation, float buildAngle )
{
m_hasPreciseBuildLocation = true;
m_buildLocation = buildLocation;
m_buildAngle = buildAngle;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildTeleportExit::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
if ( !m_hasPreciseBuildLocation )
{
// if no specific build location given, just build right where we are
m_buildLocation = me->GetAbsOrigin();
}
m_giveUpTimer.Start( 3.1f );
m_path.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildTeleportExit::Update( CTFBot *me, float interval )
{
if ( me->GetTimeSinceLastInjury() < 1.0f )
{
return Done( "Ouch! I'm under attack" );
}
CBaseObject *myTeleportEntrance = me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT );
if ( myTeleportEntrance )
{
// successfully built
return Done( "Teleport exit built" );
}
// collect metal as we move to our build location
if ( me->CanBuild( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT ) == CB_NEED_RESOURCES )
{
if ( m_getAmmoTimer.IsElapsed() && CTFBotGetAmmo::IsPossible( me ) )
{
// need more metal - get some
m_getAmmoTimer.Start( 1.0f );
return SuspendFor( new CTFBotGetAmmo, "Need more metal to build my Teleporter Exit" );
}
}
// move near our build position
const float buildRange = 50.0f;
if ( me->IsRangeGreaterThan( m_buildLocation, buildRange ) )
{
// move into position
if ( !m_path.IsValid() || m_repathTimer.IsElapsed() )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, m_buildLocation, cost );
m_repathTimer.Start( RandomFloat( 2.0f, 3.0f ) );
}
m_path.Update( me );
// don't give up until we've reached our build location
m_giveUpTimer.Reset();
return Continue();
}
// in position to build
if ( m_giveUpTimer.IsElapsed() )
{
return Done( "Taking too long - giving up" );
}
if ( m_hasPreciseBuildLocation )
{
me->GetBodyInterface()->AimHeadTowards( m_buildLocation, IBody::CRITICAL, 1.0f, NULL, "Looking toward my precise build location" );
// directly create a teleporter exit at the precise position and orientation desired
CObjectTeleporter *myTeleporterExit = (CObjectTeleporter *)CreateEntityByName( "obj_teleporter" );
if ( myTeleporterExit )
{
myTeleporterExit->SetObjectMode( MODE_TELEPORTER_EXIT );
myTeleporterExit->SetAbsOrigin( m_buildLocation );
myTeleporterExit->SetAbsAngles( QAngle( 0, m_buildAngle, 0 ) );
myTeleporterExit->Spawn();
myTeleporterExit->StartPlacement( me );
myTeleporterExit->StartBuilding( me );
myTeleporterExit->SetBuilder( me );
// teleporter exits are solid blockers - put engineer on top of exit or he'll be stuck
Vector myNewOrigin = me->GetAbsOrigin();
myNewOrigin.z += me->GetLocomotionInterface()->GetStepHeight();
me->SetAbsOrigin( myNewOrigin );
return Done( "Teleport exit built at precise location" );
}
return Continue();
}
// build exit roughly at this spot
CTFWeaponBase *myGun = me->GetActiveTFWeapon();
if ( myGun )
{
CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( myGun );
if ( builder )
{
if ( builder->IsValidPlacement() )
{
// place it down
me->PressFireButton();
}
else if ( m_searchTimer.IsElapsed() )
{
// rotate to find valid spot
Vector forward;
float angle = RandomFloat( -M_PI, M_PI );
FastSinCos( angle, &forward.y, &forward.x );
forward.z = 0.0f;
me->GetBodyInterface()->AimHeadTowards( me->EyePosition() - 100.0f * forward, IBody::CRITICAL, 1.0f, NULL, "Trying to place my teleport exit" );
m_searchTimer.Start( 1.0f );
}
}
else
{
// switch to teleporter builder
me->StartBuildingObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildTeleportExit::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_giveUpTimer.Reset();
m_path.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerBuildTeleportExit::OnStuck( CTFBot *me )
{
m_path.Invalidate();
return TryContinue();
}
@@ -0,0 +1,36 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_teleport_exit.h
// Engineer building a teleport exit
// Michael Booth, May 2010
#ifndef TF_BOT_ENGINEER_BUILD_TELEPORT_EXIT_H
#define TF_BOT_ENGINEER_BUILD_TELEPORT_EXIT_H
class CTFBotEngineerBuildTeleportExit : public Action< CTFBot >
{
public:
CTFBotEngineerBuildTeleportExit( void );
CTFBotEngineerBuildTeleportExit( const Vector &buildLocation, float buildAngle );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual const char *GetName( void ) const { return "EngineerBuildTeleportExit"; };
private:
PathFollower m_path;
bool m_hasPreciseBuildLocation;
Vector m_buildLocation;
float m_buildAngle;
CountdownTimer m_giveUpTimer;
CountdownTimer m_repathTimer;
CountdownTimer m_getAmmoTimer;
CountdownTimer m_searchTimer;
};
#endif // TF_BOT_ENGINEER_BUILD_TELEPORT_EXIT_H
@@ -0,0 +1,497 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_building.cpp
// At building location, constructing buildings
// Michael Booth, May 2010
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "team_train_watcher.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_building.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_dispenser.h"
#include "bot/behavior/tf_bot_attack.h"
#include "bot/behavior/tf_bot_get_ammo.h"
#include "bot/map_entities/tf_bot_hint_teleporter_exit.h"
#include "bot/map_entities/tf_bot_hint_sentrygun.h"
#include "NextBotUtil.h"
ConVar tf_bot_engineer_retaliate_range( "tf_bot_engineer_retaliate_range", "750", FCVAR_CHEAT, "If attacker who destroyed sentry is closer than this, attack. Otherwise, retreat" );
ConVar tf_bot_engineer_exit_near_sentry_range( "tf_bot_engineer_exit_near_sentry_range", "2500", FCVAR_CHEAT, "Maximum travel distance between a bot's Sentry gun and its Teleporter Exit" );
ConVar tf_bot_engineer_max_sentry_travel_distance_to_point( "tf_bot_engineer_max_sentry_travel_distance_to_point", "2500", FCVAR_CHEAT, "Maximum travel distance between a bot's Sentry gun and the currently contested point" );
extern ConVar tf_bot_path_lookahead_range;
const int MaxPlacementAttempts = 5;
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuilding::CTFBotEngineerBuilding( void )
{
m_sentryBuildHint = NULL;
}
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuilding::CTFBotEngineerBuilding( CTFBotHintSentrygun *sentryBuildHint )
{
m_sentryBuildHint = sentryBuildHint;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuilding::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_sentryTriesLeft = MaxPlacementAttempts;
m_territoryRangeTimer.Invalidate();
m_hasBuiltSentry = false;
m_isSentryOutOfPosition = false;
m_nearbyMetalStatus = NEARBY_METAL_UNKNOWN;
return Continue();
}
//---------------------------------------------------------------------------------------------
// Everything is built, upgrade/maintain it
// TODO: Upgrade/maintain nearby friendly buildings, too.
void CTFBotEngineerBuilding::UpgradeAndMaintainBuildings( CTFBot *me )
{
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER );
if ( !mySentry )
{
return;
}
CBaseCombatWeapon *wrench = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
if ( wrench )
{
me->Weapon_Switch( wrench );
}
const float tooFarRange = 75.0f;
if ( !myDispenser )
{
// just work on our sentry
float rangeToSentry = me->GetDistanceBetween( mySentry );
if ( rangeToSentry < 1.2f * tooFarRange )
{
// crouch both for cover behind our buildings, but also to slow us down so we hit our move goal more accurately
me->PressCrouchButton();
}
if ( rangeToSentry > tooFarRange )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, mySentry->GetAbsOrigin(), cost );
}
m_path.Update( me );
}
else
{
// we are in position - work on our buildings
me->StopLookingAroundForEnemies();
me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my Sentry" );
me->PressFireButton();
}
return;
}
// sit near both buildings
Vector betweenMyBuildings = ( mySentry->GetAbsOrigin() + myDispenser->GetAbsOrigin() ) / 2.0f;
// try to equalize distance between both
float rangeToSentry = me->GetDistanceBetween( mySentry );
float rangeToDispenser = me->GetDistanceBetween( myDispenser );
const float equalTolerance = 25.0f;
if ( rangeToSentry < 1.2f * tooFarRange && rangeToDispenser < 1.2f * tooFarRange )
{
// crouch both for cover behind our buildings, but also to slow us down so we hit our move goal more accurately
me->PressCrouchButton();
}
if ( fabs( rangeToDispenser - rangeToSentry ) > equalTolerance || rangeToSentry > tooFarRange || rangeToDispenser > tooFarRange )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, betweenMyBuildings, cost );
}
m_path.Update( me );
}
if ( rangeToSentry < tooFarRange || rangeToDispenser < tooFarRange )
{
// we are (nearly) in position - work on our buildings
m_searchTimer.Invalidate();
CBaseObject *workTarget = mySentry;
if ( mySentry->HasSapper() || mySentry->IsPlasmaDisabled() )
workTarget = mySentry;
else if ( myDispenser->HasSapper() || myDispenser->IsPlasmaDisabled() )
workTarget = myDispenser;
else if ( mySentry->GetTimeSinceLastInjury() < 1.0f || mySentry->GetHealth() < mySentry->GetMaxHealth() )
workTarget = mySentry;
else if ( mySentry->IsBuilding() )
workTarget = mySentry;
else if ( myDispenser->IsBuilding() )
workTarget = myDispenser;
else if ( mySentry->GetUpgradeLevel() < 3 )
workTarget = mySentry;
else if ( myDispenser->GetHealth() < myDispenser->GetMaxHealth() )
workTarget = myDispenser;
else if ( myDispenser->GetUpgradeLevel() < mySentry->GetUpgradeLevel() )
workTarget = myDispenser;
me->StopLookingAroundForEnemies();
me->GetBodyInterface()->AimHeadTowards( workTarget->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my buildings" );
me->PressFireButton();
}
}
//---------------------------------------------------------------------------------------------
bool CTFBotEngineerBuilding::IsMetalSourceNearby( CTFBot *me ) const
{
CUtlVector< CNavArea * > nearbyVector;
CollectSurroundingAreas( &nearbyVector, me->GetLastKnownArea(), 2000.0f, me->GetLocomotionInterface()->GetStepHeight(), me->GetLocomotionInterface()->GetStepHeight() );
for( int i=0; i<nearbyVector.Count(); ++i )
{
CTFNavArea *area = (CTFNavArea *)nearbyVector[i];
if ( area->HasAttributeTF( TF_NAV_HAS_AMMO ) )
{
return true;
}
// this assumes all spawn rooms have resupply cabinets
if ( me->GetTeamNumber() == TF_TEAM_RED && area->HasAttributeTF( TF_NAV_SPAWN_ROOM_RED ) )
{
return true;
}
if ( me->GetTeamNumber() == TF_TEAM_BLUE && area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) )
{
return true;
}
}
return false;
}
//---------------------------------------------------------------------------------------------
bool CTFBotEngineerBuilding::CheckIfSentryIsOutOfPosition( CTFBot *me ) const
{
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
if ( !mySentry )
{
return false;
}
// payload
if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT )
{
CTeamTrainWatcher *trainWatcher;
if ( me->GetTeamNumber() == TF_TEAM_BLUE )
{
trainWatcher = TFGameRules()->GetPayloadToPush( me->GetTeamNumber() );
}
else
{
trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() );
}
if ( trainWatcher )
{
float sentryDistanceAlongPath;
trainWatcher->ProjectPointOntoPath( mySentry->GetAbsOrigin(), NULL, &sentryDistanceAlongPath );
const float behindTrainTolerance = SENTRY_MAX_RANGE;
return ( trainWatcher->GetTrainDistanceAlongTrack() > sentryDistanceAlongPath + behindTrainTolerance );
}
}
// control points
mySentry->UpdateLastKnownArea();
CNavArea *sentryArea = mySentry->GetLastKnownArea();
CTeamControlPoint *point = me->GetMyControlPoint();
if ( point )
{
CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() );
if ( sentryArea && pointArea )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
if ( NavAreaTravelDistance( sentryArea, pointArea, cost, tf_bot_engineer_max_sentry_travel_distance_to_point.GetFloat() ) < 0 &&
NavAreaTravelDistance( pointArea, sentryArea, cost, tf_bot_engineer_max_sentry_travel_distance_to_point.GetFloat() ) < 0 )
{
return true;
}
}
}
return false;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuilding::Update( CTFBot *me, float interval )
{
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER );
CObjectTeleporter *myTeleportEntrance = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE );
CObjectTeleporter *myTeleportExit = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT );
bool isUnderAttack = ( me->GetTimeSinceLastInjury() < 1.0f );
isUnderAttack |= ( mySentry && ( mySentry->HasSapper() || mySentry->IsPlasmaDisabled() ) );
isUnderAttack |= ( myDispenser && ( myDispenser->HasSapper() || myDispenser->IsPlasmaDisabled() ) );
me->StartLookingAroundForEnemies();
// try to build a Sentry
if ( !mySentry )
{
m_nearbyMetalStatus = NEARBY_METAL_UNKNOWN;
// react to nearby threats if our sentry is down
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
me->EquipBestWeaponForThreat( threat );
}
if ( !m_hasBuiltSentry && m_sentryTriesLeft > 0 )
{
--m_sentryTriesLeft;
if ( m_sentryBuildHint )
{
return SuspendFor( new CTFBotEngineerBuildSentryGun( m_sentryBuildHint ), "Building a Sentry at a hint location" );
}
return SuspendFor( new CTFBotEngineerBuildSentryGun, "Building a Sentry" );
}
else
{
// can't build a Sentry here - pick a new place
return ChangeTo( new CTFBotEngineerMoveToBuild, "Couldn't find a place to build" );
}
}
// I have a Sentry
m_hasBuiltSentry = true;
if ( m_sentryBuildHint != NULL && !m_sentryBuildHint->IsEnabled() )
{
// our hint has been disabled and no longer has influence on our behavior
m_sentryBuildHint = NULL;
}
// periodically check that our Sentry is still near the contested point
if ( m_sentryBuildHint == NULL || !m_sentryBuildHint->IsSticky() )
{
if ( !m_isSentryOutOfPosition && m_territoryRangeTimer.IsElapsed() )
{
m_territoryRangeTimer.Start( RandomFloat( 3.0f, 5.0f ) );
m_isSentryOutOfPosition = CheckIfSentryIsOutOfPosition( me );
}
m_isSentryOutOfPosition = false;
if ( m_isSentryOutOfPosition )
{
// the point has moved, only keep sentry as long as it keeps attacking
if ( mySentry->GetTimeSinceLastFired() > 10.0f )
{
mySentry->DetonateObject();
// if we built here because of a hint, disable that hint so we don't use it and rebuild here again
if ( m_sentryBuildHint != NULL )
{
inputdata_t dummy;
m_sentryBuildHint->InputDisable( dummy );
m_sentryBuildHint = NULL;
}
if ( myDispenser )
{
myDispenser->DetonateObject();
}
if ( myTeleportExit )
{
myTeleportExit->DetonateObject();
}
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_MOVEUP );
return ChangeTo( new CTFBotEngineerMoveToBuild, "Need to move my gear closer to the point!" );
}
}
}
// if my dispenser is too far away from my sentry, destroy and rebuild it next update
// @TODO: Flag hint-built entities for a larger range
const float maxSeparation = 500.0f;
if ( myDispenser )
{
if ( ( mySentry->GetAbsOrigin() - myDispenser->GetAbsOrigin() ).IsLengthGreaterThan( maxSeparation ) )
{
myDispenser->DestroyObject();
myDispenser = NULL;
}
}
// build up the sentry all the way if there is a metal source nearby
if ( mySentry->GetUpgradeLevel() < 3 )
{
if ( m_nearbyMetalStatus == NEARBY_METAL_UNKNOWN )
{
m_nearbyMetalStatus = IsMetalSourceNearby( me ) ? NEARBY_METAL_EXISTS : NEARBY_METAL_NONE;
}
if ( m_nearbyMetalStatus == NEARBY_METAL_EXISTS )
{
UpgradeAndMaintainBuildings( me );
return Continue();
}
}
/*
if ( myTeleportExit )
{
// if my teleporter exit is too far away from my sentry, destroy and rebuild it next update
if ( ( mySentry->GetAbsOrigin() - myTeleportExit->GetAbsOrigin() ).IsLengthGreaterThan( maxSeparation ) )
{
myTeleportExit->DestroyObject();
myTeleportExit = NULL;
}
}
*/
// try to build a Dispenser (build after tele exit in training)
if ( !TFGameRules()->IsInTraining() || myTeleportExit )
{
const float dispenserRebuildInterval = 10.0f;
if ( myDispenser )
{
// don't rebuild immediately after building is destroyed
m_dispenserRetryTimer.Start( dispenserRebuildInterval );
}
else if ( m_dispenserRetryTimer.IsElapsed() && !isUnderAttack )
{
m_dispenserRetryTimer.Start( dispenserRebuildInterval );
return SuspendFor( new CTFBotEngineerBuildDispenser, "Building a Dispenser" );
}
}
// try to build a Teleporter Exit
const float exitRebuildInterval = TFGameRules()->IsInTraining() ? 5.0f : 30.0f;
if ( myTeleportExit )
{
// don't rebuild immediately after building is destroyed
m_teleportExitRetryTimer.Start( exitRebuildInterval );
}
else if ( m_teleportExitRetryTimer.IsElapsed() && myTeleportEntrance && !isUnderAttack )
{
m_teleportExitRetryTimer.Start( exitRebuildInterval );
// we need to build a teleporter exit yet
if ( m_sentryBuildHint != NULL )
{
// if there are teleporter exit hints, find the closest one to our sentry and use it
CUtlVector< CBaseEntity * > hintVector;
CTFBotHintTeleporterExit *hint = NULL;
while( ( hint = (CTFBotHintTeleporterExit *)gEntList.FindEntityByClassname( hint, "bot_hint_teleporter_exit" ) ) != NULL )
{
if ( hint->IsEnabled() && hint->InSameTeam( me ) )
{
hintVector.AddToTail( hint );
}
}
if ( hintVector.Count() > 0 )
{
mySentry->UpdateLastKnownArea();
CBaseEntity *closeHint = SelectClosestEntityByTravelDistance( me, hintVector, mySentry->GetLastKnownArea(), tf_bot_engineer_exit_near_sentry_range.GetFloat() );
if ( closeHint )
{
return SuspendFor( new CTFBotEngineerBuildTeleportExit( closeHint->GetAbsOrigin(), closeHint->GetAbsAngles().y ), "Building teleporter exit at nearby hint" );
}
}
}
else if ( me->IsRangeLessThan( mySentry, 300.0f ) )
{
// drop a teleporter exit near our sentry
return SuspendFor( new CTFBotEngineerBuildTeleportExit(), "Building teleporter exit" );
}
}
// everything is built - maintain them
UpgradeAndMaintainBuildings( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotEngineerBuilding::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
me->StartLookingAroundForEnemies();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuilding::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerBuilding::OnTerritoryLost( CTFBot *me, int territoryID )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerBuilding::OnTerritoryCaptured( CTFBot *me, int territoryID )
{
return TryContinue();
}
@@ -0,0 +1,64 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_building.h
// At building location, constructing buildings
// Michael Booth, May 2010
#ifndef TF_BOT_ENGINEER_BUILDING_H
#define TF_BOT_ENGINEER_BUILDING_H
class CTFBotHintSentrygun;
class CTFBotEngineerBuilding : public Action< CTFBot >
{
public:
CTFBotEngineerBuilding( void );
CTFBotEngineerBuilding( CTFBotHintSentrygun *sentryBuildHint );
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 > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID );
virtual const char *GetName( void ) const { return "EngineerBuilding"; };
private:
CountdownTimer m_searchTimer;
CountdownTimer m_getAmmoTimer;
CountdownTimer m_repathTimer;
CountdownTimer m_buildTeleporterExitTimer;
int m_sentryTriesLeft;
CountdownTimer m_dispenserRetryTimer;
CountdownTimer m_teleportExitRetryTimer;
PathFollower m_path;
CHandle< CTFBotHintSentrygun > m_sentryBuildHint;
bool m_hasBuiltSentry;
enum NearbyMetalType
{
NEARBY_METAL_UNKNOWN,
NEARBY_METAL_NONE,
NEARBY_METAL_EXISTS
};
NearbyMetalType m_nearbyMetalStatus;
CountdownTimer m_territoryRangeTimer;
bool m_isSentryOutOfPosition;
bool CheckIfSentryIsOutOfPosition( CTFBot *me ) const;
void UpgradeAndMaintainBuildings( CTFBot *me );
bool IsMetalSourceNearby( CTFBot *me ) const;
};
#endif // TF_BOT_ENGINEER_BUILDING_H
@@ -0,0 +1,504 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_move_to_build.cpp
// Engineer moving into position to build
// Michael Booth, February 2009
#include "cbase.h"
#include "nav_mesh/tf_nav_mesh.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_obj_sentrygun.h"
#include "tf_weapon_builder.h"
#include "team_train_watcher.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_build.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "bot/behavior/engineer/tf_bot_engineer_building.h"
#include "bot/map_entities/tf_bot_hint_sentrygun.h"
#include "bot/behavior/tf_bot_get_ammo.h"
#include "bot/behavior/tf_bot_retreat_to_cover.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h"
#include "trigger_area_capture.h"
#include "raid/tf_raid_logic.h"
extern ConVar tf_bot_path_lookahead_range;
ConVar tf_bot_debug_sentry_placement( "tf_bot_debug_sentry_placement", "0", FCVAR_CHEAT );
ConVar tf_bot_max_teleport_exit_travel_to_point( "tf_bot_max_teleport_exit_travel_to_point", "2500", FCVAR_CHEAT, "In an offensive engineer bot's tele exit is farther from the point than this, destroy it" );
ConVar tf_bot_min_teleport_travel( "tf_bot_min_teleport_travel", "3000", FCVAR_CHEAT, "Minimum travel distance between teleporter entrance and exit before engineer bot will build one" );
//--------------------------------------------------------------------------------------------------------
static Vector s_pointCentroid;
int CompareRangeToPoint( CTFNavArea * const *area1, CTFNavArea * const *area2 )
{
float d1 = ( (*area1)->GetCenter() - s_pointCentroid ).LengthSqr();
float d2 = ( (*area2)->GetCenter() - s_pointCentroid ).LengthSqr();
// reversed so farthest is sorted first in the vector
if ( d1 < d2 )
return 1;
if ( d1 > d2 )
return -1;
return 0;
}
//---------------------------------------------------------------------------------------------
void CTFBotEngineerMoveToBuild::CollectBuildAreas( CTFBot *me )
{
// if we have a predesignated build area, we're done
if ( me->GetHomeArea() )
return;
m_sentryAreaVector.RemoveAll();
CUtlVector< CTFNavArea * > pointAreaVector;
Vector pointCentroid = vec3_origin;
float pointEnemyIncursion = 0.0f;
int i;
int myTeam = me->GetTeamNumber();
int enemyTeam = ( myTeam == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE;
CCaptureZone *zone = me->GetFlagCaptureZone();
if ( zone )
{
// NOTE: Not strictly the right thing - should defend location of our team's flag
CTFNavArea *zoneArea = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( zone->WorldSpaceCenter(), false, 500.0f, true );
if ( zoneArea )
{
pointAreaVector.AddToTail( zoneArea );
pointCentroid += zoneArea->GetCenter();
pointEnemyIncursion += zoneArea->GetIncursionDistance( enemyTeam );
}
}
else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT )
{
CTeamTrainWatcher *trainWatcher;
if ( myTeam == TF_TEAM_BLUE )
{
trainWatcher = TFGameRules()->GetPayloadToPush( me->GetTeamNumber() );
}
else
{
trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() );
}
if ( trainWatcher )
{
Vector checkpointPos = trainWatcher->GetNextCheckpointPosition();
CTFNavArea *checkpointArea = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( checkpointPos, false, 500.0f, true );
if ( checkpointArea )
{
pointAreaVector.AddToTail( checkpointArea );
pointCentroid += checkpointArea->GetCenter();
pointEnemyIncursion += checkpointArea->GetIncursionDistance( enemyTeam );
}
}
}
else
{
// collect all areas overlapping the point
CTeamControlPoint *ctrlPoint = me->GetMyControlPoint();
if ( !ctrlPoint )
return;
const CUtlVector< CTFNavArea * > *ctrlPointAreaVector = TheTFNavMesh()->GetControlPointAreas( ctrlPoint->GetPointIndex() );
if ( ctrlPointAreaVector )
{
for( i=0; i<ctrlPointAreaVector->Count(); ++i )
{
CTFNavArea *area = ctrlPointAreaVector->Element(i);
pointAreaVector.AddToTail( area );
pointCentroid += area->GetCenter();
pointEnemyIncursion += area->GetIncursionDistance( enemyTeam );
}
}
}
if ( pointAreaVector.Count() == 0 )
return;
pointCentroid /= pointAreaVector.Count();
pointEnemyIncursion /= pointAreaVector.Count();
// collect all areas that can see the point
CUtlVector< CTFNavArea * > exposedAreaVector;
for( i=0; i<pointAreaVector.Count(); ++i )
{
CTFAreaCollector collect;
pointAreaVector[i]->ForAllPotentiallyVisibleAreas( collect );
for( int j=0; j<collect.m_vector.Count(); ++j )
{
CTFNavArea *visibleArea = collect.m_vector[j];
if ( visibleArea->GetIncursionDistance( myTeam ) < 0 || visibleArea->GetIncursionDistance( enemyTeam ) < 0 )
continue;
if ( TFGameRules()->IsInKothMode() )
{
// ignore areas the enemy can reach first
if ( visibleArea->GetIncursionDistance( myTeam ) >= visibleArea->GetIncursionDistance( enemyTeam ) )
continue;
}
// incursion flow is badly behaved at cap #1, stage #2 in dustbowl
// else
// {
// if ( pointEnemyIncursion > visibleArea->GetIncursionDistance( enemyTeam ) )
// continue;
// }
if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP )
{
// don't build directly on the point
if ( visibleArea->HasAttributeTF( TF_NAV_CONTROL_POINT ) )
continue;
// ignore areas below the point
const float tooFarBelow = 150.0f;
if ( visibleArea->GetCenter().z < pointCentroid.z - tooFarBelow )
continue;
// ignore areas too far from the point for the sentry gun to reach
const float tolerance = 1.1f;
if ( ( visibleArea->GetCenter() - pointCentroid ).IsLengthGreaterThan( SENTRY_MAX_RANGE * tolerance ) )
continue;
}
// ignore areas that don't have clear line of FIRE (not sight)
const float sentryEyeHeight = 60.0f;
const float pointFlagHeight = 70.0f; // 100.0f;
if ( !me->IsLineOfFireClear( visibleArea->GetCenter() + Vector( 0, 0, sentryEyeHeight ), pointCentroid + Vector( 0, 0, pointFlagHeight ) ) )
continue;
if ( !exposedAreaVector.HasElement( visibleArea ) )
exposedAreaVector.AddToTail( visibleArea );
}
}
// keep the farthest away areas
const float keepRatio = 1.0f; // 0.5f;
s_pointCentroid = pointCentroid;
exposedAreaVector.Sort( CompareRangeToPoint );
for( i=0; i<exposedAreaVector.Count() * keepRatio; ++i )
{
CTFNavArea *usableArea = exposedAreaVector[i];
m_sentryAreaVector.AddToTail( usableArea );
}
// calculate total surface area
m_totalSurfaceArea = 0.0f;
FOR_EACH_VEC( m_sentryAreaVector, it )
{
CTFNavArea *area = m_sentryAreaVector[ it ];
m_totalSurfaceArea += area->GetSizeX() * area->GetSizeY();
if ( tf_bot_debug_sentry_placement.GetBool() )
{
TheNavMesh->AddToSelectedSet( area );
}
}
}
//---------------------------------------------------------------------------------------------
/**
* Doesn't recompute the potential areas, just reselected from the list
*/
void CTFBotEngineerMoveToBuild::SelectBuildLocation( CTFBot *me )
{
m_path.Invalidate();
m_sentryBuildHint = NULL;
m_sentryBuildLocation = vec3_origin;
// if we have a build spot, use it
if ( me->GetHomeArea() )
{
m_sentryBuildLocation = me->GetHomeArea()->GetCenter();
return;
}
// if we have a set of specific build locations, pick one of them
CUtlVector< CTFBotHintSentrygun * > sentryHintVector;
CTFBotHintSentrygun *sentryHint;
for( sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( NULL, "bot_hint_sentrygun" ) );
sentryHint;
sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( sentryHint, "bot_hint_sentrygun" ) ) )
{
// clear the previous owner if it is us
if ( sentryHint->GetPlayerOwner() == me )
{
sentryHint->SetPlayerOwner( NULL );
}
if ( sentryHint->IsAvailableForSelection( me ) )
{
sentryHintVector.AddToTail( sentryHint );
}
}
if ( sentryHintVector.Count() > 0 )
{
int which = RandomInt( 0, sentryHintVector.Count()-1 );
m_sentryBuildHint = sentryHintVector[ which ];
m_sentryBuildHint->SetPlayerOwner( me );
m_sentryBuildLocation = m_sentryBuildHint->GetAbsOrigin();
return;
}
// collect nav area candidates
CollectBuildAreas( me );
// choose based on surface area to avoid biasing finely subdivided areas of the mesh
float which = RandomFloat( 0.0f, m_totalSurfaceArea - 1.0f );
float soFar = 0.0f;
FOR_EACH_VEC( m_sentryAreaVector, sit )
{
CTFNavArea *area = m_sentryAreaVector[ sit ];
soFar += area->GetSizeX() * area->GetSizeY();
if ( which < soFar )
{
m_sentryBuildLocation = area->GetRandomPoint();
return;
}
}
if ( !HushAsserts() )
{
Assert( !"Failed to find a build location" );
}
m_sentryBuildLocation = me->GetAbsOrigin();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerMoveToBuild::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
#ifdef TF_RAID_MODE
if ( TFGameRules()->IsRaidMode() )
{
if ( me->GetHomeArea() && TFGameRules()->GetRaidLogic() )
{
// try to pick a new area
CTFNavArea *sentryArea = TFGameRules()->GetRaidLogic()->SelectRaidSentryArea();
if ( sentryArea )
{
me->SetHomeArea( sentryArea );
}
}
}
#endif // TF_RAID_MODE
SelectBuildLocation( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerMoveToBuild::Update( CTFBot *me, float interval )
{
if ( me->WasPointJustLost() )
{
if ( m_fallBackTimer.HasStarted() )
{
if ( m_fallBackTimer.IsElapsed() )
{
SelectBuildLocation( me );
m_fallBackTimer.Invalidate();
}
else
{
// wait a moment while we decide where to build near fallback point
return Continue();
}
}
}
CBaseObject *mySentry = me->GetObjectOfType( OBJ_SENTRYGUN );
if ( mySentry )
{
// we already have a sentry from a previous life - continue what we were doing
// if we used a sentry hint last time, reuse it
CTFBotHintSentrygun *sentryHint;
for( sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( NULL, "bot_hint_sentrygun" ) );
sentryHint;
sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( sentryHint, "bot_hint_sentrygun" ) ) )
{
if ( sentryHint->GetPlayerOwner() == me )
{
return ChangeTo( new CTFBotEngineerBuilding( sentryHint ), "Going back to my existing sentry nest and reusing a sentry hint" );
}
}
return ChangeTo( new CTFBotEngineerBuilding, "Going back to my existing sentry nest" );
}
// offensive engineers need to place a forward teleporter
if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP && !TFGameRules()->IsInKothMode() && me->GetTeamNumber() == TF_TEAM_BLUE )
{
CObjectTeleporter *myTeleportExit = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT );
int myTeam = me->GetTeamNumber();
if ( myTeleportExit )
{
// if exit is too far from the point, destroy it and try again
CTeamControlPoint *point = me->GetMyControlPoint();
if ( point )
{
CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() );
myTeleportExit->UpdateLastKnownArea();
CTFNavArea *exitArea = (CTFNavArea *)myTeleportExit->GetLastKnownArea();
if ( pointArea && exitArea )
{
float travelToPoint = fabs( exitArea->GetIncursionDistance( myTeam ) - pointArea->GetIncursionDistance( myTeam ) );
if ( travelToPoint > tf_bot_max_teleport_exit_travel_to_point.GetFloat() )
{
// too far, destroy it
myTeleportExit->DestroyObject();
myTeleportExit = NULL;
}
}
}
}
else
{
CObjectTeleporter *myTeleportEntrance = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE );
CTFNavArea *myArea = me->GetLastKnownArea();
bool shouldBuildExit = true;
// if we have a teleporter entrance, don't place the exit too close to it
if ( myTeleportEntrance && myArea )
{
myTeleportEntrance->UpdateLastKnownArea();
CTFNavArea *enterArea = (CTFNavArea *)myTeleportEntrance->GetLastKnownArea();
if ( enterArea )
{
float travelBetween = fabs( enterArea->GetIncursionDistance( myTeam ) - myArea->GetIncursionDistance( myTeam ) );
if ( travelBetween < tf_bot_min_teleport_travel.GetFloat() )
{
shouldBuildExit = false;
}
}
}
if ( shouldBuildExit )
{
// no exit yet - need to place one
// when we see the enemy, retreat to cover and build the exit there
if ( me->GetVisionInterface()->GetPrimaryKnownThreat( true ) )
{
if ( !me->m_Shared.InCond( TF_COND_INVULNERABLE ) && ShouldRetreat( me ) != ANSWER_NO )
{
Action< CTFBot > *nextActionWhenInCover = new CTFBotEngineerBuildTeleportExit;
return SuspendFor( new CTFBotRetreatToCover( nextActionWhenInCover ), "Retreating to a safe place to build my teleporter exit" );
}
}
}
}
}
// move to build position
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, m_sentryBuildLocation, cost );
}
Vector forward;
me->EyeVectors( &forward );
forward.z = 0.0f;
forward.NormalizeInPlace();
Vector myBlueprintPosition = me->GetAbsOrigin() + 50.0f * forward;
const float closeToHome = 25.0f;
Vector toBuild = m_sentryBuildLocation - myBlueprintPosition;
Vector toMe = m_sentryBuildLocation - me->GetAbsOrigin();
if ( me->GetLocomotionInterface()->IsOnGround() )
{
// we need to wait until we're on the ground since the Build action assumes our position OnStart is where we are going to build
if ( toMe.AsVector2D().IsLengthLessThan( closeToHome ) || toBuild.AsVector2D().IsLengthLessThan( closeToHome ) )
{
if ( m_sentryBuildHint != NULL )
{
return ChangeTo( new CTFBotEngineerBuilding( m_sentryBuildHint ), "Reached my precise build location" );
}
return ChangeTo( new CTFBotEngineerBuilding, "Reached my build location" );
}
m_path.Update( me );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnStuck( CTFBot *me )
{
// SelectBuildLocation( me );
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnMoveToSuccess( CTFBot *me, const Path *path )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
SelectBuildLocation( me );
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnTerritoryLost( CTFBot *me, int territoryID )
{
// we have to wait a moment until contested point changes to select a new build spot
m_fallBackTimer.Start( 0.2f );
return TryContinue();
}
@@ -0,0 +1,43 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_move_to_build.h
// Engineer moving into position to build
// Michael Booth, February 2009
#ifndef TF_BOT_ENGINEER_MOVE_TO_BUILD_H
#define TF_BOT_ENGINEER_MOVE_TO_BUILD_H
#include "Path/NextBotPathFollow.h"
class CTFBotHintSentrygun;
class CTFBotEngineerMoveToBuild : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual const char *GetName( void ) const { return "EngineerMoveToBuild"; };
private:
CHandle< CTFBotHintSentrygun > m_sentryBuildHint;
Vector m_sentryBuildLocation;
PathFollower m_path;
CountdownTimer m_repathTimer;
CUtlVector< CTFNavArea * > m_sentryAreaVector;
float m_totalSurfaceArea;
void CollectBuildAreas( CTFBot *me );
void SelectBuildLocation( CTFBot *me );
CountdownTimer m_fallBackTimer;
};
#endif // TF_BOT_ENGINEER_MOVE_TO_BUILD_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,69 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_medic_heal.h
// Heal a teammate
// Michael Booth, February 2009
#ifndef TF_BOT_MEDIC_HEAL_H
#define TF_BOT_MEDIC_HEAL_H
#include "Path/NextBotChasePath.h"
class CWeaponMedigun;
class CTFBotMedicHeal : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual EventDesiredResult< CTFBot > OnActorEmoted( CTFBot *me, CBaseCombatCharacter *emoter, int emote );
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
virtual QueryResultType ShouldRetreat( const INextBot *bot ) const;
virtual const char *GetName( void ) const { return "Heal"; };
private:
ChasePath m_chasePath;
CTFPlayer *SelectPatient( CTFBot *me, CTFPlayer *current );
CountdownTimer m_changePatientTimer;
CountdownTimer m_delayUberTimer;
CHandle< CTFPlayer > m_patient;
Vector m_patientAnchorPos; // a spot where the patient was, to track if they are moving
CountdownTimer m_isPatientRunningTimer;
bool IsPatientRunning( void ) const;
bool IsStable( CTFPlayer *patient ) const; // return true if the given patient is healthy and safe for now
CTFNavArea *FindCoverArea( CTFBot *me );
CTFNavArea *m_coverArea;
CountdownTimer m_coverTimer;
PathFollower m_coverPath;
void ComputeFollowPosition( CTFBot *me );
Vector m_followGoal;
bool IsVisibleToEnemy( CTFBot *me, const Vector &where ) const;
bool IsReadyToDeployUber( const CWeaponMedigun* pMedigun ) const;
bool IsGoodUberTarget( CTFPlayer *who ) const;
bool CanDeployUber( CTFBot *me, const CWeaponMedigun* pMedigun ) const;
};
inline bool CTFBotMedicHeal::IsPatientRunning( void ) const
{
return m_isPatientRunningTimer.IsElapsed() ? false : true;
}
#endif // TF_BOT_MEDIC_HEAL_H
@@ -0,0 +1,144 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_retreat.cpp
// Retreat towards our spawn to find another patient
// Michael Booth, May 2009
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_weapon_medigun.h"
#include "bot/tf_bot.h"
#include "bot/behavior/medic/tf_bot_medic_retreat.h"
#include "nav_mesh.h"
extern ConVar tf_bot_path_lookahead_range;
extern ConVar tf_bot_medic_follow_range;
extern ConVar tf_bot_force_class;
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMedicRetreat::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
CTFNavArea *homeArea = me->GetSpawnArea();
if ( homeArea == NULL )
{
return Done( "No home area!" );
}
m_path.SetMinLookAheadDistance( tf_bot_path_lookahead_range.GetFloat() );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, homeArea->GetCenter(), cost );
return Continue();
}
//---------------------------------------------------------------------------------------------
class CUsefulHealTargetFilter : public INextBotEntityFilter
{
public:
CUsefulHealTargetFilter( int team )
{
m_team = team;
}
virtual bool IsAllowed( CBaseEntity *entity ) const
{
if ( entity && entity->IsPlayer() && entity->GetTeamNumber() == m_team )
{
return !ToTFPlayer( entity )->IsPlayerClass( TF_CLASS_MEDIC ) && !ToTFPlayer( entity )->IsPlayerClass( TF_CLASS_SNIPER );
}
return false;
}
int m_team;
};
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMedicRetreat::Update( CTFBot *me, float interval )
{
// equip the syringegun and defend ourselves!
CTFWeaponBase *myWeapon = me->m_Shared.GetActiveTFWeapon();
if ( myWeapon )
{
if ( myWeapon->GetWeaponID() != TF_WEAPON_SYRINGEGUN_MEDIC )
{
CBaseCombatWeapon *syringeGun = me->Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
if ( syringeGun )
{
me->Weapon_Switch( syringeGun );
}
}
}
m_path.Update( me );
// look around to try to spot a friend to heal
if ( m_lookAroundTimer.IsElapsed() )
{
m_lookAroundTimer.Start( RandomFloat( 0.33f, 1.0f ) );
QAngle angle;
angle.x = 0.0f;
angle.y = RandomFloat( -180.0f, 180.0f );
angle.z = 0.0f;
Vector forward;
AngleVectors( angle, &forward );
me->GetBodyInterface()->AimHeadTowards( me->EyePosition() + forward, IBody::IMPORTANT, 0.1f, NULL, "Looking for someone to heal" );
}
// if we see a friend, heal them
CUsefulHealTargetFilter filter( me->GetTeamNumber() );
const CKnownEntity *known = me->GetVisionInterface()->GetClosestKnown( filter );
if ( known )
{
return Done( "I know of a teammate" );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMedicRetreat::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, me->GetSpawnArea()->GetCenter(), cost );
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotMedicRetreat::OnStuck( CTFBot *me )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, me->GetSpawnArea()->GetCenter(), cost );
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotMedicRetreat::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, me->GetSpawnArea()->GetCenter(), cost );
return TryContinue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotMedicRetreat::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
// defend ourselves!
return ANSWER_YES;
}
@@ -0,0 +1,29 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_retreat.cpp
// Retreat towards our spawn to find another patient
// Michael Booth, May 2009
#ifndef TF_BOT_MEDIC_RETREAT_H
#define TF_BOT_MEDIC_RETREAT_H
#include "Path/NextBotChasePath.h"
class CTFBotMedicRetreat : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const;
virtual const char *GetName( void ) const { return "Retreat"; };
private:
PathFollower m_path;
CountdownTimer m_lookAroundTimer;
};
#endif // TF_BOT_MEDIC_RETREAT_H
@@ -0,0 +1,104 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_mission_destroy_sentries.cpp
// Seek and destroy enemy sentries and ignore everything else
// Michael Booth, June 2011
#include "cbase.h"
#include "team.h"
#include "bot/tf_bot.h"
#include "bot/behavior/missions/tf_bot_mission_destroy_sentries.h"
#include "bot/behavior/spy/tf_bot_spy_sap.h"
#include "bot/behavior/tf_bot_destroy_enemy_sentry.h"
#include "bot/behavior/medic/tf_bot_medic_heal.h"
#include "bot/behavior/missions/tf_bot_mission_suicide_bomber.h"
#include "tf_obj_sentrygun.h"
//
// NOTE: This behavior is deprecated and unused for now.
// The only sentry destroying mission is the Sentry Buster right now (suicide bomber).
//
//---------------------------------------------------------------------------------------------
CTFBotMissionDestroySentries::CTFBotMissionDestroySentries( CObjectSentrygun *goalSentry )
{
m_goalSentry = goalSentry;
}
//---------------------------------------------------------------------------------------------
CObjectSentrygun *CTFBotMissionDestroySentries::SelectSentryTarget( CTFBot *me )
{
return NULL;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMissionDestroySentries::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
if ( me->IsPlayerClass( TF_CLASS_MEDIC ) )
{
return ChangeTo( new CTFBotMedicHeal, "My job is to heal/uber the others in the mission" );
}
// focus only on the mission
me->SetAttribute( CTFBot::IGNORE_ENEMIES );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMissionDestroySentries::Update( CTFBot *me, float interval )
{
if ( m_goalSentry == NULL )
{
// first destroy the sentry we were assigned to, or any sentry we discovered or that is attacking us
m_goalSentry = me->GetEnemySentry();
if ( m_goalSentry == NULL )
{
// next destroy the most dangerous sentry
int iTeam = ( me->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED;
if ( TFGameRules() && TFGameRules()->IsPVEModeActive() )
{
iTeam = TF_TEAM_PVE_DEFENDERS;
}
m_goalSentry = TFGameRules()->FindSentryGunWithMostKills( iTeam );
}
}
// for suicide bombers, we never want them to revert to normal behavior even if there is no sentry to kill
if ( me->IsPlayerClass( TF_CLASS_DEMOMAN ) )
{
return SuspendFor( new CTFBotMissionSuicideBomber, "On a suicide mission to blow up a sentry" );
}
if ( m_goalSentry == NULL )
{
// no sentries left to destroy - our mission is complete
me->SetMission( CTFBot::NO_MISSION, MISSION_DOESNT_RESET_BEHAVIOR_SYSTEM );
return ChangeTo( GetParentAction()->InitialContainedAction( me ), "Mission complete - reverting to normal behavior" );
}
if ( m_goalSentry != me->GetEnemySentry() )
{
me->RememberEnemySentry( m_goalSentry, m_goalSentry->WorldSpaceCenter() );
}
if ( me->IsPlayerClass( TF_CLASS_SPY ) )
{
return SuspendFor( new CTFBotSpySap( m_goalSentry ), "On a mission to sap a sentry" );
}
return SuspendFor( new CTFBotDestroyEnemySentry, "On a mission to destroy a sentry" );
}
//---------------------------------------------------------------------------------------------
void CTFBotMissionDestroySentries::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
me->ClearAttribute( CTFBot::IGNORE_ENEMIES );
}
@@ -0,0 +1,30 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_mission_destroy_sentries.h
// Seek and destroy enemy sentries and ignore everything else
// Michael Booth, June 2011
#ifndef TF_BOT_MISSION_DESTROY_SENTRIES_H
#define TF_BOT_MISSION_DESTROY_SENTRIES_H
//-----------------------------------------------------------------------------
class CTFBotMissionDestroySentries : public Action< CTFBot >
{
public:
CTFBotMissionDestroySentries( CObjectSentrygun *goalSentry = NULL );
virtual ~CTFBotMissionDestroySentries() { }
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 const char *GetName( void ) const { return "MissionDestroySentries"; };
private:
CHandle< CObjectSentrygun > m_goalSentry;
CObjectSentrygun *SelectSentryTarget( CTFBot *me );
};
#endif // TF_BOT_MISSION_DESTROY_SENTRIES_H
@@ -0,0 +1,377 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_mission_reprogrammed.cpp
// Move to target and explode
// Michael Booth, October 2011
#include "cbase.h"
#include "tf_team.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "bot/behavior/missions/tf_bot_mission_reprogrammed.h"
#include "particle_parse.h"
#include "tf_obj_sentrygun.h"
#ifdef STAGING_ONLY
extern ConVar tf_bot_path_lookahead_range;
extern ConVar tf_bot_suicide_bomb_range;
ConVar tf_bot_reprogrammed_time( "tf_bot_reprogrammed_time", "8", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
CTFBotMissionReprogrammed::CTFBotMissionReprogrammed( void )
{
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMissionReprogrammed::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_detonateTimer.Invalidate();
m_detonateSeekTimer.Invalidate();
m_reprogrammedTimer.Invalidate();
m_hasDetonated = false;
m_consecutivePathFailures = 0;
m_wasSuccessful = false;
if ( me->HasTheFlag() )
me->DropFlag();
me->SetFlagTarget( NULL );
m_victim = me->GetMissionTarget();
// Find nearest, accessible teammate
if ( !m_victim )
{
CTFPlayer *pTarget = FindNearestEnemy( me );
if ( pTarget )
{
me->SetMissionTarget( pTarget );
m_victim = pTarget;
}
}
if ( m_victim )
{
m_lastKnownVictimPosition = m_victim->GetAbsOrigin();
}
// We're guaranteed to stay alive for a period of time
me->SetHealth( 1 );
me->m_takedamage = DAMAGE_NO;
// Duration
m_reprogrammedTimer.Start( tf_bot_reprogrammed_time.GetFloat() );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMissionReprogrammed::Update( CTFBot *me, float interval )
{
bool bDetonate = false;
// one we start detonating, there's no turning back
if ( m_detonateTimer.HasStarted() )
{
if ( m_detonateTimer.IsElapsed() )
{
m_vecDetLocation = me->GetAbsOrigin();
Detonate( me );
return Done( "KABOOM!" );
}
return Continue();
}
// Find nearest, accessible teammate
if ( !m_victim || !m_victim->IsAlive() )
{
m_victim.Set( NULL );
CTFPlayer *pTarget = FindNearestEnemy( me );
if ( pTarget )
{
me->SetMissionTarget( pTarget );
m_victim = pTarget;
}
}
if ( m_victim )
{
me->m_Shared.RemoveCond( TF_COND_STUNNED );
// update chase destination
if ( m_victim->IsAlive() && !m_victim->IsEffectActive( EF_NODRAW ) )
{
m_lastKnownVictimPosition = m_victim->GetAbsOrigin();
}
}
if ( m_talkTimer.IsElapsed() )
{
m_talkTimer.Start( 4.0f );
me->EmitSound( "MVM.SentryBusterIntro" );
}
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, m_lastKnownVictimPosition, cost );
if ( m_path.Compute( me, m_lastKnownVictimPosition, cost ) == false )
{
++m_consecutivePathFailures;
if ( m_consecutivePathFailures >= 3 )
{
bDetonate = true;
}
}
}
// move to the victim
m_path.Update( me );
// Reprogramming time is up. Find nearest and detonate.
if ( m_reprogrammedTimer.IsElapsed() )
{
// Limit this mode to 5 seconds
if ( !m_detonateSeekTimer.HasStarted() )
{
m_detonateSeekTimer.Start( 5.f );
}
else if ( m_detonateSeekTimer.IsElapsed() )
{
bDetonate = true;
}
// Get to a third of the damage range before detonating
const float detonateRange = tf_bot_suicide_bomb_range.GetFloat() / 3.0f;
if ( me->IsDistanceBetweenLessThan( m_lastKnownVictimPosition, detonateRange ) )
{
if ( me->IsLineOfFireClear( m_lastKnownVictimPosition + Vector( 0, 0, StepHeight ) ) )
{
bDetonate = true;
}
}
}
if ( bDetonate )
{
StartDetonate( me, true );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotMissionReprogrammed::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
if ( me->IsAlive() )
{
me->ForceChangeTeam( TEAM_SPECTATOR );
}
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotMissionReprogrammed::OnKilled( CTFBot *me, const CTakeDamageInfo &info )
{
// Keep us alive, but run to nearest enemy
if ( !m_hasDetonated )
{
if ( !m_detonateTimer.HasStarted() )
{
StartDetonate( me );
}
else
{
Detonate( me );
}
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotMissionReprogrammed::OnStuck( CTFBot *me )
{
// we're stuck, decide to detonate now!
if ( !m_hasDetonated && !m_detonateTimer.HasStarted() )
{
StartDetonate( me );
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
void CTFBotMissionReprogrammed::StartDetonate( CTFBot *me, bool wasSuccessful /* = false */ )
{
if ( m_detonateTimer.HasStarted() )
return;
if ( !me->IsAlive() || me->GetHealth() < 1 )
{
if ( me->GetTeamNumber() != TEAM_SPECTATOR)
{
me->m_lifeState = LIFE_ALIVE;
me->SetHealth( 1 );
}
}
m_wasSuccessful = wasSuccessful;
me->Taunt( TAUNT_BASE_WEAPON );
m_detonateTimer.Start( 2.f );
me->EmitSound( "MvM.SentryBusterSpin" );
}
//---------------------------------------------------------------------------------------------
void CTFBotMissionReprogrammed::Detonate( CTFBot *me )
{
// BLAST!
m_hasDetonated = true;
DispatchParticleEffect( "explosionTrail_seeds_mvm", me->GetAbsOrigin(), me->GetAbsAngles() );
DispatchParticleEffect( "fluidSmokeExpl_ring_mvm", me->GetAbsOrigin(), me->GetAbsAngles() );
me->EmitSound( "MVM.SentryBusterExplode" );
UTIL_ScreenShake( me->GetAbsOrigin(), 25.0f, 5.0f, 5.0f, 1000.0f, SHAKE_START );
if ( !m_wasSuccessful )
{
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_SENTRY_BUSTER_DOWN, TF_TEAM_PVE_DEFENDERS );
}
}
CUtlVector< CTFPlayer* > playerVector;
CUtlVector< CBaseCombatCharacter* > victimVector;
// Only damage our original team (reprogramming switches team)
CTFTeam *pTeam = me->GetOpposingTFTeam();
if ( pTeam )
{
CollectPlayers( &playerVector, pTeam->GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );
// objects
for ( int i = 0; i < pTeam->GetNumObjects(); ++i )
{
CBaseObject *object = pTeam->GetObject( i );
if ( object )
{
victimVector.AddToTail( object );
}
}
}
// players
for ( int i = 0; i < playerVector.Count(); ++i )
{
victimVector.AddToTail( playerVector[i] );
}
// non-player bots
CUtlVector< INextBot * > botVector;
TheNextBots().CollectAllBots( &botVector );
for( int i = 0; i < botVector.Count(); ++i )
{
CBaseCombatCharacter *bot = botVector[i]->GetEntity();
if ( !bot->IsPlayer() && bot->IsAlive() )
{
victimVector.AddToTail( bot );
}
}
// Clear my mission before we have everyone take damage so I will die with the rest
me->SetMission( CTFBot::NO_MISSION, MISSION_DOESNT_RESET_BEHAVIOR_SYSTEM );
me->m_takedamage = DAMAGE_YES;
// kill victims (including me)
for( int i = 0; i < victimVector.Count(); ++i )
{
CBaseCombatCharacter *victim = victimVector[i];
Vector toVictim = victim->WorldSpaceCenter() - me->WorldSpaceCenter();
if ( toVictim.IsLengthGreaterThan( tf_bot_suicide_bomb_range.GetFloat() ) )
continue;
if ( victim->IsPlayer() )
{
color32 colorHit = { 255, 255, 255, 255 };
UTIL_ScreenFade( victim, colorHit, 1.0f, 0.1f, FFADE_IN );
}
if ( me->IsLineOfFireClear( victim ) )
{
toVictim.NormalizeInPlace();
const int nDamage = 1000;
CTakeDamageInfo info( me, me, nDamage, DMG_BLAST, TF_DMG_CUSTOM_NONE );
CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 1.0f );
victim->TakeDamage( info );
}
}
// make sure we're removed (in case we detonated in our spawn area where we are invulnerable)
me->ForceChangeTeam( TEAM_SPECTATOR );
}
//---------------------------------------------------------------------------------------------
CTFPlayer *CTFBotMissionReprogrammed::FindNearestEnemy( CTFBot *me )
{
CUtlVector< CTFPlayer* > playerVector;
CollectPlayers( &playerVector, GetEnemyTeam( me->GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS );
CTFPlayer *pClosestPlayer = NULL;
float flClosestPlayerDist = FLT_MAX;
FOR_EACH_VEC( playerVector, i )
{
if ( !playerVector[i] )
continue;
if ( playerVector[i] == me )
continue;
me->GetDistanceBetween( playerVector[i] );
// Find closest
float flDist = me->GetDistanceBetween( playerVector[i] );
if ( flDist < flClosestPlayerDist )
{
pClosestPlayer = playerVector[i];
flClosestPlayerDist = flDist;
}
}
return pClosestPlayer;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotMissionReprogrammed::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
return ( m_detonateTimer.HasStarted() ) ? ANSWER_NO : ANSWER_YES;
}
#endif // STAGING_ONLY
@@ -0,0 +1,53 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_mission_suicide_bomber.h
// Move to target and explode
// Michael Booth, October 2011
#ifndef TF_BOT_MISSION_REPROGRAMMED_H
#define TF_BOT_MISSION_REPROGRAMMED_H
#include "Path/NextBotPathFollow.h"
class CTFBotMissionReprogrammed : public Action< CTFBot >
{
#ifdef STAGING_ONLY
public:
CTFBotMissionReprogrammed( void );
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 EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnKilled( 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 "MissionReprogrammed"; };
private:
CHandle< CBaseEntity > m_victim; // the victim we are trying to destroy
Vector m_lastKnownVictimPosition;
PathFollower m_path;
CountdownTimer m_repathTimer;
CountdownTimer m_talkTimer;
CountdownTimer m_detonateTimer;
CountdownTimer m_detonateSeekTimer;
CountdownTimer m_reprogrammedTimer;
void StartDetonate( CTFBot *me, bool wasSuccessful = false );
void Detonate( CTFBot *me );
CTFPlayer *FindNearestEnemy( CTFBot *me );
bool m_hasDetonated;
bool m_wasSuccessful;
int m_consecutivePathFailures;
Vector m_vecDetLocation;
#endif // STAGING_ONLY
};
#endif // TF_BOT_MISSION_REPROGRAMMED_H
@@ -0,0 +1,400 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_mission_suicide_bomber.cpp
// Move to target and explode
// Michael Booth, October 2011
#include "cbase.h"
#include "tf_team.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "bot/behavior/missions/tf_bot_mission_suicide_bomber.h"
#include "particle_parse.h"
#include "tf_obj_sentrygun.h"
#include "player_vs_environment/tf_populators.h"
extern ConVar tf_bot_path_lookahead_range;
ConVar tf_bot_suicide_bomb_range( "tf_bot_suicide_bomb_range", "300", FCVAR_CHEAT );
ConVar tf_bot_suicide_bomb_friendly_fire( "tf_bot_suicide_bomb_friendly_fire", "1", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
CTFBotMissionSuicideBomber::CTFBotMissionSuicideBomber( void )
{
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMissionSuicideBomber::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_detonateTimer.Invalidate();
m_bHasDetonated = false;
m_consecutivePathFailures = 0;
m_bWasSuccessful = false;
m_bWasKilled = false;
m_victim = me->GetMissionTarget();
if ( m_victim != NULL )
{
m_lastKnownVictimPosition = m_victim->GetAbsOrigin();
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMissionSuicideBomber::Update( CTFBot *me, float interval )
{
// one we start detonating, there's no turning back
if ( m_detonateTimer.HasStarted() )
{
if ( m_detonateTimer.IsElapsed() )
{
m_vecDetLocation = me->GetAbsOrigin();
Detonate( me );
// Send out an event
if ( m_bWasSuccessful && m_victim && m_victim->IsBaseObject() )
{
CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( m_victim.Get() );
if ( sentry && sentry->GetOwner() )
{
CTFPlayer *pOwner = ToTFPlayer( sentry->GetOwner() );
if ( pOwner )
{
IGameEvent *event = gameeventmanager->CreateEvent( "mvm_sentrybuster_detonate" );
if ( event )
{
event->SetInt( "player", pOwner->entindex() );
event->SetFloat( "det_x", m_vecDetLocation.x );
event->SetFloat( "det_y", m_vecDetLocation.y );
event->SetFloat( "det_z", m_vecDetLocation.z );
gameeventmanager->FireEvent( event );
}
}
}
}
return Done( "KABOOM!" );
}
return Continue();
}
if ( me->GetHealth() == 1 )
{
// low on health - detonate where we are!
StartDetonate( me, false, true );
return Continue();
}
if ( m_victim != NULL )
{
// update chase destination
if ( m_victim->IsAlive() && !m_victim->IsEffectActive( EF_NODRAW ) )
{
m_lastKnownVictimPosition = m_victim->GetAbsOrigin();
}
// if the engineer is carrying his sentry, he becomes the victim
if ( m_victim->IsBaseObject() )
{
CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( m_victim.Get() );
if ( sentry && sentry->IsCarried() && sentry->GetOwner() )
{
// path to the engineer carrying the sentry
m_lastKnownVictimPosition = sentry->GetOwner()->GetAbsOrigin();
}
}
}
// Get to a third of the damage range before detonating
const float detonateRange = tf_bot_suicide_bomb_range.GetFloat() / 3.0f;
if ( me->IsDistanceBetweenLessThan( m_lastKnownVictimPosition, detonateRange ) )
{
if ( me->IsLineOfFireClear( m_lastKnownVictimPosition + Vector( 0, 0, StepHeight ) ) )
{
StartDetonate( me, true );
}
}
if ( m_talkTimer.IsElapsed() )
{
m_talkTimer.Start( 4.0f );
me->EmitSound( "MVM.SentryBusterIntro" );
}
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
if ( m_path.Compute( me, m_lastKnownVictimPosition, cost ) == false )
{
++m_consecutivePathFailures;
if ( m_consecutivePathFailures >= 3 )
{
// really can't reach my victim - detonate!
StartDetonate( me );
}
}
else
{
m_consecutivePathFailures = 0;
}
}
// move to the victim
m_path.Update( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotMissionSuicideBomber::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotMissionSuicideBomber::OnKilled( CTFBot *me, const CTakeDamageInfo &info )
{
if ( !m_bHasDetonated )
{
if ( !m_detonateTimer.HasStarted() )
{
StartDetonate( me );
}
else if ( m_detonateTimer.IsElapsed() )
{
Detonate( me );
}
else
{
// We're in detonate mode, and something's trying to kill us. Prevent it.
if ( me->GetTeamNumber() != TEAM_SPECTATOR )
{
me->m_lifeState = LIFE_ALIVE;
me->SetHealth( 1 );
}
}
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotMissionSuicideBomber::OnStuck( CTFBot *me )
{
// we're stuck, decide to detonate now!
if ( !m_bHasDetonated && !m_detonateTimer.HasStarted() )
{
StartDetonate( me );
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
void CTFBotMissionSuicideBomber::StartDetonate( CTFBot *me, bool bWasSuccessful /* = false */, bool bWasKilled /*= false*/ )
{
if ( m_detonateTimer.HasStarted() )
return;
if ( !me->IsAlive() || me->GetHealth() < 1 )
{
if ( me->GetTeamNumber() != TEAM_SPECTATOR)
{
me->m_lifeState = LIFE_ALIVE;
me->SetHealth( 1 );
}
}
m_bWasSuccessful = bWasSuccessful;
m_bWasKilled = bWasKilled;
me->m_takedamage = DAMAGE_NO;
me->Taunt( TAUNT_BASE_WEAPON );
m_detonateTimer.Start( 2.0f );
me->EmitSound( "MvM.SentryBusterSpin" );
}
//---------------------------------------------------------------------------------------------
void CTFBotMissionSuicideBomber::Detonate( CTFBot *me )
{
// BLAST!
m_bHasDetonated = true;
DispatchParticleEffect( "explosionTrail_seeds_mvm", me->GetAbsOrigin(), me->GetAbsAngles() );
DispatchParticleEffect( "fluidSmokeExpl_ring_mvm", me->GetAbsOrigin(), me->GetAbsAngles() );
me->EmitSound( "MVM.SentryBusterExplode" );
UTIL_ScreenShake( me->GetAbsOrigin(), 25.0f, 5.0f, 5.0f, 1000.0f, SHAKE_START );
if ( !m_bWasSuccessful )
{
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_SENTRY_BUSTER_DOWN, TF_TEAM_PVE_DEFENDERS );
// ACHIEVEMENT_TF_MVM_KILL_SENTRY_BUSTER
for ( int iDamager = 0 ; iDamager < MAX_ACHIEVEMENT_HISTORY_SLOTS ; iDamager ++ )
{
EntityHistory_t *damagerHistory = me->m_AchievementData.GetDamagerHistory( iDamager );
if ( damagerHistory )
{
if ( damagerHistory->hEntity && ( gpGlobals->curtime - damagerHistory->flTimeDamage <= 5.0f ) )
{
CTFPlayer *pRecentDamager = ToTFPlayer( damagerHistory->hEntity );
if ( pRecentDamager )
{
pRecentDamager->AwardAchievement( ACHIEVEMENT_TF_MVM_KILL_SENTRY_BUSTER );
}
}
}
}
}
}
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
CUtlVector< CBaseCombatCharacter * > victimVector;
int i;
// players
for ( i=0; i<playerVector.Count(); ++i )
{
victimVector.AddToTail( playerVector[i] );
}
// objects
CTFTeam *team = GetGlobalTFTeam( TF_TEAM_BLUE );
if ( team )
{
for ( i=0; i<team->GetNumObjects(); ++i )
{
CBaseObject *object = team->GetObject( i );
if ( object )
{
victimVector.AddToTail( object );
}
}
}
team = GetGlobalTFTeam( TF_TEAM_RED );
if ( team )
{
for ( i=0; i<team->GetNumObjects(); ++i )
{
CBaseObject *object = team->GetObject( i );
if ( object )
{
victimVector.AddToTail( object );
}
}
}
// non-player bots
CUtlVector< INextBot * > botVector;
TheNextBots().CollectAllBots( &botVector );
for( i=0; i<botVector.Count(); ++i )
{
CBaseCombatCharacter *bot = botVector[i]->GetEntity();
if ( !bot->IsPlayer() && bot->IsAlive() )
{
victimVector.AddToTail( bot );
}
}
// Send out an event whenever players damaged us to the point where we had to detonate
if ( m_bWasKilled )
{
IGameEvent *event = gameeventmanager->CreateEvent( "mvm_sentrybuster_killed" );
if ( event )
{
event->SetInt( "sentry_buster", me->entindex() );
gameeventmanager->FireEvent( event );
}
}
// Clear my mission before we have everyone take damage so I will die with the rest
me->SetMission( CTFBot::NO_MISSION, MISSION_DOESNT_RESET_BEHAVIOR_SYSTEM );
me->m_takedamage = DAMAGE_YES;
// kill victims (including me)
for( int i=0; i<victimVector.Count(); ++i )
{
CBaseCombatCharacter *victim = victimVector[i];
Vector toVictim = victim->WorldSpaceCenter() - me->WorldSpaceCenter();
if ( toVictim.IsLengthGreaterThan( tf_bot_suicide_bomb_range.GetFloat() ) )
continue;
if ( victim->IsPlayer() )
{
color32 colorHit = { 255, 255, 255, 255 };
UTIL_ScreenFade( victim, colorHit, 1.0f, 0.1f, FFADE_IN );
}
if ( me->IsLineOfFireClear( victim ) )
{
toVictim.NormalizeInPlace();
int damage = MAX( victim->GetMaxHealth(), victim->GetHealth() );
CTakeDamageInfo info( me, me, 4 * damage, DMG_BLAST, TF_DMG_CUSTOM_NONE );
if ( tf_bot_suicide_bomb_friendly_fire.GetBool() )
{
info.SetForceFriendlyFire( true );
}
CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 1.0f );
victim->TakeDamage( info );
}
}
// make sure we're removed (in case we detonated in our spawn area where we are invulnerable)
me->CommitSuicide( false, true );
if ( me->IsAlive() )
{
me->ForceChangeTeam( TEAM_SPECTATOR );
}
if ( m_bWasKilled )
{
// increment num sentry killed this wave
CWave *pWave = g_pPopulationManager ? g_pPopulationManager->GetCurrentWave() : NULL;
if ( pWave )
{
pWave->IncrementSentryBustersKilled();
}
}
}
// Should we attack "them"?
QueryResultType CTFBotMissionSuicideBomber::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
// buster never "attacks", just approaches and self-detonates
return ANSWER_NO;
}
@@ -0,0 +1,49 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_mission_suicide_bomber.h
// Move to target and explode
// Michael Booth, October 2011
#ifndef TF_BOT_MISSION_SUICIDE_BOMBER_H
#define TF_BOT_MISSION_SUICIDE_BOMBER_H
#include "Path/NextBotPathFollow.h"
class CTFBotMissionSuicideBomber : public Action< CTFBot >
{
public:
CTFBotMissionSuicideBomber( void );
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 EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnKilled( 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 "MissionSuicideBomber"; };
private:
CHandle< CBaseEntity > m_victim; // the victim we are trying to destroy
Vector m_lastKnownVictimPosition;
PathFollower m_path;
CountdownTimer m_repathTimer;
CountdownTimer m_talkTimer;
CountdownTimer m_detonateTimer;
void StartDetonate( CTFBot *me, bool bWasSuccessful = false, bool bWasKilled = false );
void Detonate( CTFBot *me );
bool m_bHasDetonated;
bool m_bWasSuccessful;
bool m_bWasKilled;
int m_consecutivePathFailures;
Vector m_vecDetLocation;
};
#endif // TF_BOT_MISSION_SUICIDE_BOMBER_H
@@ -0,0 +1,156 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_nav_ent_destroy_entity.h
// Destroy the given entity, under nav entity control
// Michael Booth, September 2009
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "bot/behavior/nav_entities/tf_bot_nav_ent_destroy_entity.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
CTFBotNavEntDestroyEntity::CTFBotNavEntDestroyEntity( const CFuncNavPrerequisite *prereq )
{
m_prereq = prereq;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotNavEntDestroyEntity::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
if ( m_prereq == NULL )
{
return Done( "Prerequisite has been removed before we started" );
}
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_wasIgnoringEnemies = me->HasAttribute( CTFBot::IGNORE_ENEMIES );
m_isReadyToLaunchSticky = true;
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotNavEntDestroyEntity::DetonateStickiesWhenSet( CTFBot *me, CTFPipebombLauncher *stickyLauncher ) const
{
if ( !stickyLauncher )
return;
if ( stickyLauncher->GetPipeBombCount() >= 8 || 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();
}
}
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotNavEntDestroyEntity::Update( CTFBot *me, float interval )
{
if ( m_prereq == NULL )
{
return Done( "Prerequisite has been removed" );
}
CBaseEntity *target = m_prereq->GetTaskEntity();
if ( target == NULL )
{
return Done( "Target entity is NULL" );
}
float attackRange = me->GetMaxAttackRange();
if ( m_prereq->GetTaskValue() > 0.0f )
{
attackRange = MIN( attackRange, m_prereq->GetTaskValue() );
}
if ( me->IsDistanceBetweenLessThan( target, attackRange ) && me->GetVisionInterface()->IsLineOfSightClearToEntity( target ) )
{
me->SetAttribute( CTFBot::IGNORE_ENEMIES );
me->GetBodyInterface()->AimHeadTowards( target->WorldSpaceCenter(), IBody::CRITICAL, 0.2f, NULL, "Aiming at target we need to destroy to progress" );
if ( me->GetBodyInterface()->IsHeadAimingOnTarget() )
{
// attack
if ( me->IsPlayerClass( TF_CLASS_DEMOMAN ) )
{
// demomen use stickybombs to destroy the barrier
CTFWeaponBase *myCurrentWeapon = me->m_Shared.GetActiveTFWeapon();
CTFPipebombLauncher *stickyLauncher = dynamic_cast< CTFPipebombLauncher * >( me->Weapon_GetSlot( TF_WPN_TYPE_SECONDARY ) );
if ( myCurrentWeapon && myCurrentWeapon->GetWeaponID() != TF_WEAPON_PIPEBOMBLAUNCHER )
{
me->Weapon_Switch( stickyLauncher );
}
if ( m_isReadyToLaunchSticky )
{
me->PressFireButton();
}
m_isReadyToLaunchSticky = !m_isReadyToLaunchSticky;
DetonateStickiesWhenSet( me, stickyLauncher );
return Continue();
}
me->EquipBestWeaponForThreat( NULL );
me->PressFireButton();
}
return Continue();
}
if ( !m_wasIgnoringEnemies )
{
me->ClearAttribute( CTFBot::IGNORE_ENEMIES );
}
// move into view of our target
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, target->GetAbsOrigin(), cost );
}
m_path.Update( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotNavEntDestroyEntity::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
if ( !m_wasIgnoringEnemies )
{
me->ClearAttribute( CTFBot::IGNORE_ENEMIES );
}
}
@@ -0,0 +1,35 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_nav_ent_destroy_entity.h
// Destroy the given entity, under nav entity control
// Michael Booth, September 2009
#ifndef TF_BOT_NAV_ENT_DESTROY_ENTITY_H
#define TF_BOT_NAV_ENT_DESTROY_ENTITY_H
#include "Path/NextBotPathFollow.h"
#include "NextBot/NavMeshEntities/func_nav_prerequisite.h"
#include "tf_weapon_pipebomblauncher.h"
class CTFBotNavEntDestroyEntity : public Action< CTFBot >
{
public:
CTFBotNavEntDestroyEntity( const CFuncNavPrerequisite *prereq );
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 const char *GetName( void ) const { return "NavEntDestroyEntity"; };
private:
CHandle< CFuncNavPrerequisite > m_prereq;
PathFollower m_path; // how we get to the target
CountdownTimer m_repathTimer;
bool m_wasIgnoringEnemies;
void DetonateStickiesWhenSet( CTFBot *me, CTFPipebombLauncher *stickyLauncher ) const;
bool m_isReadyToLaunchSticky;
};
#endif // TF_BOT_NAV_ENT_DESTROY_ENTITY_H
@@ -0,0 +1,110 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_nav_ent_move_to.cpp
// Move onto target and wait, as directed by nav entity
// Michael Booth, September 2009
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "bot/behavior/nav_entities/tf_bot_nav_ent_move_to.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
CTFBotNavEntMoveTo::CTFBotNavEntMoveTo( const CFuncNavPrerequisite *prereq )
{
m_prereq = prereq;
m_pGoalArea = NULL;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotNavEntMoveTo::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
if ( m_prereq == NULL )
{
return Done( "Prerequisite has been removed before we started" );
}
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_waitTimer.Invalidate();
CBaseEntity *target = m_prereq->GetTaskEntity();
if ( target == NULL )
{
return Done( "Prerequisite target entity is NULL" );
}
Extent targetExtent;
targetExtent.Init( target );
// pick random ground position within target entity as move-to goal
m_goalPosition = targetExtent.lo + Vector( RandomFloat( 0.0f, targetExtent.SizeX() ), RandomFloat( 0.0f, targetExtent.SizeY() ), targetExtent.SizeZ() );
TheNavMesh->GetSimpleGroundHeight( m_goalPosition, &m_goalPosition.z );
m_pGoalArea = (CTFNavArea*)TheNavMesh->GetNavArea( m_goalPosition );
if ( !m_pGoalArea )
{
return Done( "There's no nav area for the goal position" );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotNavEntMoveTo::Update( CTFBot *me, float interval )
{
if ( m_prereq == NULL )
{
return Done( "Prerequisite has been removed" );
}
if ( !m_prereq->IsEnabled() )
{
return Done( "Prerequisite has been disabled" );
}
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
// prepare to fight
me->EquipBestWeaponForThreat( threat );
}
if ( m_waitTimer.HasStarted() )
{
if ( m_waitTimer.IsElapsed() )
{
return Done( "Wait duration elapsed" );
}
}
else
{
// move to the goal area
if ( m_pGoalArea == me->GetLastKnownArea() )
{
// in area
m_waitTimer.Start( m_prereq->GetTaskValue() );
}
else
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, m_goalPosition, cost );
}
// move into position
m_path.Update( me );
}
}
return Continue();
}
@@ -0,0 +1,35 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_nav_ent_move_to.h
// Move onto target and wait, as directed by nav entity
// Michael Booth, September 2009
#ifndef TF_BOT_NAV_ENT_MOVE_TO_H
#define TF_BOT_NAV_ENT_MOVE_TO_H
#include "Path/NextBotPathFollow.h"
#include "NextBot/NavMeshEntities/func_nav_prerequisite.h"
class CTFBotNavEntMoveTo : public Action< CTFBot >
{
public:
CTFBotNavEntMoveTo( const CFuncNavPrerequisite *prereq );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual const char *GetName( void ) const { return "NavEntMoveTo"; };
private:
CHandle< CFuncNavPrerequisite > m_prereq;
Vector m_goalPosition; // specific position within entity to move to
CTFNavArea* m_pGoalArea;
CountdownTimer m_waitTimer;
PathFollower m_path; // how we get to the loot
CountdownTimer m_repathTimer;
};
#endif // TF_BOT_NAV_ENT_MOVE_TO_H
@@ -0,0 +1,56 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_nav_ent_wait.cpp
// Wait for awhile, as directed by nav entity
// Michael Booth, September 2009
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "bot/behavior/nav_entities/tf_bot_nav_ent_wait.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
CTFBotNavEntWait::CTFBotNavEntWait( const CFuncNavPrerequisite *prereq )
{
m_prereq = prereq;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotNavEntWait::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
if ( m_prereq == NULL )
{
return Done( "Prerequisite has been removed before we started" );
}
m_timer.Start( m_prereq->GetTaskValue() );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotNavEntWait::Update( CTFBot *me, float interval )
{
if ( m_prereq == NULL )
{
return Done( "Prerequisite has been removed" );
}
if ( !m_prereq->IsEnabled() )
{
return Done( "Prerequisite has been disabled" );
}
if ( m_timer.IsElapsed() )
{
return Done( "Wait time elapsed" );
}
return Continue();
}
@@ -0,0 +1,28 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_nav_ent_wait.h
// Wait for awhile, as directed by nav entity
// Michael Booth, September 2009
#ifndef TF_BOT_NAV_ENT_WAIT_H
#define TF_BOT_NAV_ENT_WAIT_H
#include "NextBot/NavMeshEntities/func_nav_prerequisite.h"
class CTFBotNavEntWait : public Action< CTFBot >
{
public:
CTFBotNavEntWait( const CFuncNavPrerequisite *prereq );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual const char *GetName( void ) const { return "NavEntWait"; };
private:
CHandle< CFuncNavPrerequisite > m_prereq;
CountdownTimer m_timer;
};
#endif // TF_BOT_NAV_ENT_WAIT_H
@@ -0,0 +1,231 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_capture_point.cpp
// Move to and try to capture the next point
// Michael Booth, February 2009
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "team_control_point_master.h"
#include "trigger_area_capture.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/capture_point/tf_bot_capture_point.h"
#include "bot/behavior/scenario/capture_point/tf_bot_defend_point.h"
#include "bot/behavior/tf_bot_seek_and_destroy.h"
extern ConVar tf_bot_path_lookahead_range;
ConVar tf_bot_offense_must_push_time( "tf_bot_offense_must_push_time", "120", FCVAR_CHEAT, "If timer is less than this, bots will push hard to cap" );
ConVar tf_bot_capture_seek_and_destroy_min_duration( "tf_bot_capture_seek_and_destroy_min_duration", "15", FCVAR_CHEAT, "If a capturing bot decides to go hunting, this is the min duration he will hunt for before reconsidering" );
ConVar tf_bot_capture_seek_and_destroy_max_duration( "tf_bot_capture_seek_and_destroy_max_duration", "30", FCVAR_CHEAT, "If a capturing bot decides to go hunting, this is the max duration he will hunt for before reconsidering" );
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotCapturePoint::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
VPROF_BUDGET( "CTFBotCapturePoint::OnStart", "NextBot" );
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_path.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotCapturePoint::Update( CTFBot *me, float interval )
{
if ( TFGameRules()->InSetup() )
{
// wait until the gates open, then path
m_path.Invalidate();
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
return Continue();
}
CTeamControlPoint *point = me->GetMyControlPoint();
if ( point == NULL )
{
const float roamTime = 10.0f;
return SuspendFor( new CTFBotSeekAndDestroy( roamTime ), "Seek and destroy until a point becomes available" );
}
if ( point->GetTeamNumber() == me->GetTeamNumber() )
{
return ChangeTo( new CTFBotDefendPoint, "We need to defend our point(s)" );
}
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
// prepare to fight
me->EquipBestWeaponForThreat( threat );
}
bool isPushingToCapture = ( me->IsPointBeingCaptured( point ) && !me->IsInCombat() ) || // a friend is capturing
me->IsCapturingPoint() || // we're capturing
// me->m_Shared.InCond( TF_COND_INVULNERABLE ) || // we're ubered
TFGameRules()->InOvertime() || // the game is in overtime
me->GetTimeLeftToCapture() < tf_bot_offense_must_push_time.GetFloat() || // nearly out of tim
TFGameRules()->IsInTraining() || // teach newbies to capture
me->IsNearPoint( point );
// if we see an enemy at a good combat range, stop and engage them unless we're running out of time
if ( !isPushingToCapture )
{
if ( threat && threat->IsVisibleRecently() )
{
return SuspendFor( new CTFBotSeekAndDestroy( RandomFloat( tf_bot_capture_seek_and_destroy_min_duration.GetFloat(), tf_bot_capture_seek_and_destroy_max_duration.GetFloat() ) ), "Too early to capture - hunting" );
}
}
if ( me->IsCapturingPoint() )
{
// move around on the point while we capture
const CUtlVector< CTFNavArea * > *controlPointAreas = TheTFNavMesh()->GetControlPointAreas( point->GetPointIndex() );
if ( controlPointAreas )
{
if ( controlPointAreas->Count() == 0 )
{
Assert( controlPointAreas->Count() );
Continue(); // this control point has no nav areas for bot to move around
}
// move to a random spot on this control point
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) );
int which = RandomInt( 0, controlPointAreas->Count() - 1 );
CTFNavArea *goalArea = controlPointAreas->Element( which );
if ( goalArea )
{
CTFBotPathCost cost( me, DEFAULT_ROUTE );
m_path.Compute( me, goalArea->GetRandomPoint(), cost );
}
}
m_path.Update( me );
}
}
else
{
// move toward the point, periodically repathing to account for changing situation
if ( m_repathTimer.IsElapsed() )
{
VPROF_BUDGET( "CTFBotCapturePoint::Update( repath )", "NextBot" );
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, point->GetAbsOrigin(), cost );
m_repathTimer.Start( RandomFloat( 2.0f, 3.0f ) );
}
if ( TFGameRules()->IsInTraining() && !me->IsAnyPointBeingCaptured() )
{
// stop short of capturing until the human trainee starts it
if ( m_path.GetLength() < 1000.0f )
{
// hold here and yell at player to get on the point
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_GO );
return Continue();
}
}
// move towards next capture point
m_path.Update( me );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotCapturePoint::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_repathTimer.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotCapturePoint::OnStuck( CTFBot *me )
{
m_repathTimer.Invalidate();
me->GetLocomotionInterface()->ClearStuckStatus();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotCapturePoint::OnMoveToSuccess( CTFBot *me, const Path *path )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotCapturePoint::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
m_repathTimer.Invalidate();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotCapturePoint::OnTerritoryContested( CTFBot *me, int territoryID )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotCapturePoint::OnTerritoryCaptured( CTFBot *me, int territoryID )
{
// we got it, move on
m_repathTimer.Invalidate();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotCapturePoint::OnTerritoryLost( CTFBot *me, int territoryID )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotCapturePoint::ShouldRetreat( const INextBot *bot ) const
{
CTFBot *me = (CTFBot *)bot->GetEntity();
// if we're running out of time, we have to go for it
if ( me->GetTimeLeftToCapture() < tf_bot_offense_must_push_time.GetFloat() )
return ANSWER_NO;
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotCapturePoint::ShouldHurry( const INextBot *bot ) const
{
CTFBot *me = (CTFBot *)bot->GetEntity();
// if we're running out of time, we have to go for it
if ( me->GetTimeLeftToCapture() < tf_bot_offense_must_push_time.GetFloat() )
return ANSWER_YES;
return ANSWER_UNDEFINED;
}
@@ -0,0 +1,36 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_capture_point.h
// Move to and try to capture the next point
// Michael Booth, February 2009
#ifndef TF_BOT_CAPTURE_POINT_H
#define TF_BOT_CAPTURE_POINT_H
#include "Path/NextBotPathFollow.h"
class CTFBotCapturePoint : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual EventDesiredResult< CTFBot > OnTerritoryContested( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual const char *GetName( void ) const { return "CapturePoint"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
};
#endif // TF_BOT_CAPTURE_POINT_H
@@ -0,0 +1,442 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_defend_point.h
// Move to and defend current point from capture
// Michael Booth, February 2009
#include "cbase.h"
#include "nav_mesh/tf_nav_mesh.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "team_control_point_master.h"
#include "trigger_area_capture.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/capture_point/tf_bot_defend_point.h"
#include "bot/behavior/scenario/capture_point/tf_bot_capture_point.h"
#include "bot/behavior/medic/tf_bot_medic_heal.h"
#include "bot/behavior/tf_bot_attack.h"
#include "bot/behavior/tf_bot_seek_and_destroy.h"
#include "bot/behavior/engineer/tf_bot_engineer_build.h"
#include "bot/behavior/scenario/capture_point/tf_bot_defend_point_block_capture.h"
#include "bot/behavior/sniper/tf_bot_sniper_attack.h"
#include "bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h"
extern ConVar tf_bot_path_lookahead_range;
extern ConVar tf_bot_min_setup_gate_defend_range;
extern ConVar tf_bot_max_setup_gate_defend_range;
extern ConVar tf_bot_min_setup_gate_sniper_defend_range;
extern ConVar tf_bot_offense_must_push_time;
ConVar tf_bot_defense_must_defend_time( "tf_bot_defense_must_defend_time", "300", FCVAR_CHEAT, "If timer is less than this, bots will stay near point and guard" );
ConVar tf_bot_max_point_defend_range( "tf_bot_max_point_defend_range", "1250", FCVAR_CHEAT, "How far (in travel distance) from the point defending bots will take up positions" );
ConVar tf_bot_defense_debug( "tf_bot_defense_debug", "0", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotDefendPoint::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_defenseArea = NULL;
// higher skilled bots prefer to seek and destroy until the time is almost up
static float roamChance[ CTFBot::NUM_DIFFICULTY_LEVELS ] = { 10.0f, 50.0f, 75.0f, 90.0f };
m_isAllowedToRoam = ( RandomFloat( 0.0f, 100.0f ) < roamChance[ (int)clamp( me->GetDifficulty(), CTFBot::EASY, CTFBot::EXPERT ) ] );
return Continue();
}
//---------------------------------------------------------------------------------------------
/**
* Return true if we're in immediate danger of losing the point
*/
bool CTFBotDefendPoint::IsPointThreatened( CTFBot *me )
{
CTeamControlPoint *point = me->GetMyControlPoint();
if ( point == NULL )
return false;
if ( point->LastContestedAt() > 0.0f && ( gpGlobals->curtime - point->LastContestedAt() ) < 5.0f )
{
// the point is, or was very recently, contested
return true;
}
// if we just lost a point, we should fall back and stand on the next point to defend against a rush
if ( me->WasPointJustLost() )
{
return true;
}
/*
// if an enemy is closer to the point than we are, head them off
// TODO: Compare time to reach, not distance
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat )
{
const float tolerance = 100.0f;
float themRange = ( threat->GetLastKnownPosition() - point->GetAbsOrigin() ).Length();
float myRange = ( me->GetAbsOrigin() - point->GetAbsOrigin() ).Length();
if ( myRange + tolerance > themRange )
return true;
}
*/
return false;
}
//---------------------------------------------------------------------------------------------
// Are we smart enough to get on the point to block the cap
bool CTFBotDefendPoint::WillBlockCapture( CTFBot *me ) const
{
if ( TFGameRules()->IsInTraining() )
return false;
if ( me->IsDifficulty( CTFBot::EASY ) )
return false;
if ( me->IsDifficulty( CTFBot::NORMAL ) )
{
// 50% chance of blocking cap
return me->TransientlyConsistentRandomValue() > 0.5f;
}
return true;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotDefendPoint::Update( CTFBot *me, float interval )
{
// King of the Hill logic
CTeamControlPointMaster *master = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
if ( master && master->GetNumPoints() == 1 )
{
// if we don't own the only point, switch to capture behavior
CTeamControlPoint *point = master->GetControlPoint( 0 );
if ( point && point->GetOwner() != me->GetTeamNumber() )
{
return ChangeTo( new CTFBotCapturePoint, "We need to capture the point!" );
}
}
CTeamControlPoint *point = me->GetMyControlPoint();
if ( point == NULL )
{
const float roamTime = 10.0f;
return SuspendFor( new CTFBotSeekAndDestroy( roamTime ), "Seek and destroy until a point becomes available" );
}
if ( point->GetTeamNumber() != me->GetTeamNumber() )
{
return ChangeTo( new CTFBotCapturePoint, "We need to capture our point(s)" );
}
// if point in is danger - get ON the point!
// Don't do this in training to keep things easy for the new trainee
if ( IsPointThreatened( me ) && WillBlockCapture( me ) )
{
// point is being captured - get on it!
return SuspendFor( new CTFBotDefendPointBlockCapture, "Moving to block point capture!" );
}
// point is safe for the moment
// if I'm uber'd, go get 'em!
if ( me->m_Shared.InCond( TF_COND_INVULNERABLE ) )
{
const float uberChargeTime = 6.0;
return SuspendFor( new CTFBotSeekAndDestroy( uberChargeTime ), "Attacking because I'm uber'd!" );
}
if ( point && point->IsLocked() )
{
return SuspendFor( new CTFBotSeekAndDestroy, "Seek and destroy until the point unlocks" );
}
if ( m_isAllowedToRoam && me->GetTimeLeftToCapture() > tf_bot_defense_must_defend_time.GetFloat() )
{
return SuspendFor( new CTFBotSeekAndDestroy( 15.0f ), "Seek and destroy - we have lots of time" );
}
if ( TFGameRules()->InSetup() )
{
// don't lose patience during setup time
m_idleTimer.Reset();
}
// if we see an enemy as we have a melee weapon equipped, chase them down
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
me->EquipBestWeaponForThreat( threat );
if ( threat && threat->IsVisibleRecently() )
{
// we're aware of an enemy
m_idleTimer.Reset();
if ( me->IsPlayerClass( TF_CLASS_PYRO ) )
{
// go get 'em
return SuspendFor( new CTFBotSeekAndDestroy( 15.0f ), "Going after an enemy" );
}
CTFWeaponBase *myWeapon = me->m_Shared.GetActiveTFWeapon();
if ( myWeapon && ( myWeapon->IsMeleeWeapon() || myWeapon->IsWeapon( TF_WEAPON_FLAMETHROWER ) ) )
{
// TODO: Check if threat is visible and if not, move to last known position
CTFBotPathCost cost( me, me->IsPlayerClass( TF_CLASS_PYRO ) ? SAFEST_ROUTE : FASTEST_ROUTE );
m_chasePath.Update( me, threat->GetEntity(), cost );
return Continue();
}
}
// choose where we'll defend from
if ( m_defenseArea == NULL || m_idleTimer.IsElapsed() )
{
m_defenseArea = SelectAreaToDefendFrom( me );
}
if ( m_defenseArea )
{
if ( me->GetLastKnownArea() == m_defenseArea )
{
// at our defense position
if ( CTFBotPrepareStickybombTrap::IsPossible( me ) )
{
return SuspendFor( new CTFBotPrepareStickybombTrap, "Laying sticky bombs!" );
}
}
else
{
// move to our desired defense position, repathing periodically to account for changing situation
VPROF_BUDGET( "CTFBotDefendPoint::Update( repath )", "NextBot" );
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 2.0f, 3.0f ) );
CTFBotPathCost cost( me, DEFAULT_ROUTE );
m_path.Compute( me, m_defenseArea->GetCenter(), cost );
}
m_path.Update( me );
// we're not idle while we're moving to our defend position
m_idleTimer.Reset();
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotDefendPoint::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
// may have lost point - recheck
me->ClearMyControlPoint();
m_repathTimer.Invalidate();
m_path.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPoint::OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPoint::OnStuck( CTFBot *me )
{
m_path.Invalidate();
m_defenseArea = SelectAreaToDefendFrom( me );
me->GetLocomotionInterface()->ClearStuckStatus();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPoint::OnMoveToSuccess( CTFBot *me, const Path *path )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPoint::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
m_path.Invalidate();
m_defenseArea = SelectAreaToDefendFrom( me );
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPoint::OnTerritoryContested( CTFBot *me, int territoryID )
{
// handled in the Update() loop
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPoint::OnTerritoryCaptured( CTFBot *me, int territoryID )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPoint::OnTerritoryLost( CTFBot *me, int territoryID )
{
// we lost it, fall back to next point
me->ClearMyControlPoint();
m_defenseArea = SelectAreaToDefendFrom( me );
m_repathTimer.Invalidate();
m_path.Invalidate();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
class CSelectDefenseAreaForPoint : public ISearchSurroundingAreasFunctor
{
public:
CSelectDefenseAreaForPoint( CTFNavArea *pointArea, int myTeam, CUtlVector< CTFNavArea * > *areaVector )
{
m_pointArea = pointArea;
m_myTeam = myTeam;
// don't select areas that are beyond the point
m_incursionFlowLimit = pointArea->GetIncursionDistance( m_myTeam ) + 250.0f;
m_areaVector = areaVector;
m_areaVector->RemoveAll();
}
virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
{
CTFNavArea *area = (CTFNavArea *)baseArea;
if ( !TFGameRules()->IsInKothMode() )
{
// don't select areas that are beyond the point
if ( area->GetIncursionDistance( m_myTeam ) > m_incursionFlowLimit )
return true;
}
if ( area->IsPotentiallyVisible( m_pointArea ) )
{
// a bit of a hack here to avoid bots choosing to defend in bottom of ravine at stage 3 of dustbowl
const float tooLow = 220.0f;
if ( m_pointArea->GetCenter().z - area->GetCenter().z < tooLow )
{
// valid defense position
m_areaVector->AddToTail( area );
}
}
return true;
}
virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
{
if ( adjArea->IsBlocked( TFGameRules()->IsInKothMode() ? TEAM_ANY : m_myTeam ) )
{
return false;
}
if ( travelDistanceSoFar > tf_bot_max_point_defend_range.GetFloat() )
{
// too far away
return false;
}
const float maxHeightChange = 65.0f;
float deltaZ = currentArea->ComputeAdjacentConnectionHeightChange( adjArea );
return ( fabs( deltaZ ) < maxHeightChange );
}
CTFNavArea *m_pointArea;
CUtlVector< CTFNavArea * > *m_areaVector;
float m_incursionFlowLimit;
int m_myTeam;
};
//---------------------------------------------------------------------------------------------
/**
* Select the area where we will guard the point from
*/
CTFNavArea *CTFBotDefendPoint::SelectAreaToDefendFrom( CTFBot *me )
{
VPROF_BUDGET( "CTFBotDefendPoint::SelectAreaToDefendFrom", "NextBot" );
CTeamControlPoint *point = me->GetMyControlPoint();
if ( !point )
{
return NULL;
}
// decide where we will defend from
CUtlVector< CTFNavArea * > defenseAreas;
/*
if ( !TFGameRules()->IsInKothMode() &&
point->GetTeamCapPercentage( me->GetTeamNumber() ) <= 0.0f && // point is currently safe
( ObjectiveResource()->GetPreviousPointForPoint( point->GetPointIndex(), me->GetTeamNumber(), 0 ) < 0 || // this is the first cap point
me->IsPlayerClass( TF_CLASS_PYRO ) ) ) // pyros are skirmishers
{
if ( TheTFNavMesh()->GetSetupGateDefenseAreas() )
{
defenseAreas = *TheTFNavMesh()->GetSetupGateDefenseAreas();
}
}
*/
if ( defenseAreas.Count() == 0 )
{
CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() );
if ( pointArea )
{
// search outwards from the point along walkable areas (not drop downs) to make sure we can get back to the point quickly
CSelectDefenseAreaForPoint defenseScan( pointArea, me->GetTeamNumber(), &defenseAreas );
SearchSurroundingAreas( pointArea, defenseScan );
}
}
// select a specific area from the potential defense set
if ( defenseAreas.Count() == 0 )
{
return NULL;
}
// how long will we wait if we don't see any action
m_idleTimer.Start( RandomFloat( 10.0f, 20.0f ) );
if ( tf_bot_defense_debug.GetBool() )
{
for( int i=0; i<defenseAreas.Count(); ++i )
{
defenseAreas[i]->DrawFilled( 0, 200, 200, 999.9f );
}
}
// select one of the defense areas
int which = RandomInt( 0, defenseAreas.Count()-1 );
return defenseAreas[ which ];
}
@@ -0,0 +1,48 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_defend_point.h
// Move to and defend current point from capture
// Michael Booth, February 2009
#ifndef TF_BOT_DEFEND_POINT_H
#define TF_BOT_DEFEND_POINT_H
#include "Path/NextBotPathFollow.h"
#include "Path/NextBotChasePath.h"
class CTFBotDefendPoint : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result = NULL );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual EventDesiredResult< CTFBot > OnTerritoryContested( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual const char *GetName( void ) const { return "DefendPoint"; };
private:
PathFollower m_path; // for moving to a defense position
ChasePath m_chasePath; // for chasing enemies
CountdownTimer m_repathTimer;
CountdownTimer m_lookAroundTimer;
CountdownTimer m_idleTimer;
CTFNavArea *m_defenseArea;
CTFNavArea *SelectAreaToDefendFrom( CTFBot *me );
bool IsPointThreatened( CTFBot *me );
bool WillBlockCapture( CTFBot *me ) const;
bool m_isAllowedToRoam;
};
#endif // TF_BOT_DEFEND_POINT_H
@@ -0,0 +1,247 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_defend_point_block_capture.h
// Move to and defend current point from capture
// Michael Booth, February 2009
#include "cbase.h"
#include "nav_mesh/tf_nav_mesh.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "trigger_area_capture.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/capture_point/tf_bot_defend_point_block_capture.h"
#include "bot/behavior/medic/tf_bot_medic_heal.h"
#include "bot/behavior/tf_bot_attack.h"
#include "bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h"
extern ConVar tf_bot_path_lookahead_range;
ConVar tf_bot_defend_owned_point_percent( "tf_bot_defend_owned_point_percent", "0.5", FCVAR_CHEAT, "Stay on the contested point we own until enemy cap percent falls below this" );
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotDefendPointBlockCapture::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_point = me->GetMyControlPoint();
if ( m_point == NULL )
{
return Done( "Point is NULL" );
}
m_defenseArea = static_cast< CTFNavArea * >( TheTFNavMesh()->GetNearestNavArea( m_point->GetAbsOrigin() ) );
if ( m_defenseArea == NULL )
{
return Done( "Can't find nav area on point" );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
bool CTFBotDefendPointBlockCapture::IsPointSafe( CTFBot *me )
{
// if a point was just captured, defend this point for awhile
if ( me->WasPointJustLost() )
{
return false;
}
if ( m_point == NULL )
{
return true;
}
if ( m_point->GetTeamCapPercentage( me->GetTeamNumber() ) < tf_bot_defend_owned_point_percent.GetFloat() )
{
// we're not in complete control of this point yet
return false;
}
// is point is being contested, or was just being contested, its not safe
if ( m_point->HasBeenContested() && ( gpGlobals->curtime - m_point->LastContestedAt() ) < 5.0f )
{
return false;
}
// if we still see a near threat, stay put
const CKnownEntity *knownThreat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( knownThreat )
{
const float dangerRange = 500.0f;
if ( ( knownThreat->GetLastKnownPosition() - m_point->GetAbsOrigin() ).IsLengthLessThan( dangerRange ) )
return false;
}
return true;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotDefendPointBlockCapture::Update( CTFBot *me, float interval )
{
// if point is safe, we can move back to our defense positions
if ( IsPointSafe( me ) )
{
return Done( "Point is safe again" );
}
if ( me->IsPlayerClass( TF_CLASS_MEDIC ) )
{
// medics look ridiculous rushing to the point - they need to heal
return SuspendFor( new CTFBotMedicHeal );
}
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
me->EquipBestWeaponForThreat( threat );
Extent pointExtent;
pointExtent.Init( m_point );
bool isStandingOnThePoint = pointExtent.Contains( me->GetAbsOrigin() );
const CUtlVector< CTFNavArea * > *controlPointAreas = TheTFNavMesh()->GetControlPointAreas( m_point->GetPointIndex() );
if ( controlPointAreas )
{
for( int i=0; i<controlPointAreas->Count(); ++i )
{
if ( me->GetLastKnownArea() && me->GetLastKnownArea()->GetID() == controlPointAreas->Element(i)->GetID() )
{
isStandingOnThePoint = true;
}
}
}
if ( isStandingOnThePoint && CTFBotPrepareStickybombTrap::IsPossible( me ) )
{
return SuspendFor( new CTFBotPrepareStickybombTrap, "Placing stickies for defense" );
}
if ( controlPointAreas )
{
// move to a random spot on this control point
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) );
float totalArea = 0.0f;
int i;
for( i=0; i<controlPointAreas->Count(); ++i )
{
CTFNavArea *area = controlPointAreas->Element(i);
totalArea += area->GetSizeX() * area->GetSizeY();
}
float which = RandomFloat( 0.0f, totalArea - 1.0f );
CTFNavArea *goalArea = NULL;
for( i=0; i<controlPointAreas->Count(); ++i )
{
CTFNavArea *area = controlPointAreas->Element(i);
which -= area->GetSizeX() * area->GetSizeY();
if ( which <= 0.0f )
{
goalArea = area;
break;
}
}
if ( goalArea )
{
CTFBotPathCost cost( me, DEFAULT_ROUTE );
m_path.Compute( me, goalArea->GetRandomPoint(), cost );
}
}
m_path.Update( me );
}
else if ( !isStandingOnThePoint )
{
// get on the point!
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) );
CTFBotPathCost cost( me, DEFAULT_ROUTE );
m_path.Compute( me, ( pointExtent.lo + pointExtent.hi )/2.0f, cost );
}
m_path.Update( me );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotDefendPointBlockCapture::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_path.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnStuck( CTFBot *me )
{
m_path.Invalidate();
me->GetLocomotionInterface()->ClearStuckStatus();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnMoveToSuccess( CTFBot *me, const Path *path )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
m_path.Invalidate();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnTerritoryContested( CTFBot *me, int territoryID )
{
return TryToSustain();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnTerritoryCaptured( CTFBot *me, int territoryID )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnTerritoryLost( CTFBot *me, int territoryID )
{
// we lost it, fall back
return TryDone( RESULT_CRITICAL, "Lost the point" );
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotDefendPointBlockCapture::ShouldHurry( const INextBot *me ) const
{
// hurry up and get on the point!
return ANSWER_YES;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotDefendPointBlockCapture::ShouldRetreat( const INextBot *me ) const
{
// get on the point!
return ANSWER_NO;
}
@@ -0,0 +1,40 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_defend_point_block_capture.h
// Move to and defend current point from capture
// Michael Booth, February 2009
#ifndef TF_BOT_DEFEND_POINT_BLOCK_CAPTURE_H
#define TF_BOT_DEFEND_POINT_BLOCK_CAPTURE_H
class CTFBotDefendPointBlockCapture : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual EventDesiredResult< CTFBot > OnTerritoryContested( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual QueryResultType ShouldRetreat( const INextBot *me ) const;
virtual const char *GetName( void ) const { return "BlockCapture"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
CTeamControlPoint *m_point;
CTFNavArea *m_defenseArea;
bool IsPointSafe( CTFBot *me );
};
#endif // TF_BOT_DEFEND_POINT_BLOCK_CAPTURE_H
@@ -0,0 +1,126 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_attack_flag_defenders.cpp
// Attack enemies that are preventing the flag from reaching its destination
// Michael Booth, May 2011
#include "cbase.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.h"
ConVar tf_bot_flag_escort_range( "tf_bot_flag_escort_range", "500", FCVAR_CHEAT );
extern ConVar tf_bot_flag_escort_max_count;
extern int GetBotEscortCount( int team );
//---------------------------------------------------------------------------------------------
CTFBotAttackFlagDefenders::CTFBotAttackFlagDefenders( float minDuration )
{
if ( minDuration > 0.0f )
{
m_minDurationTimer.Start( minDuration );
}
else
{
m_minDurationTimer.Invalidate();
}
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotAttackFlagDefenders::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_chasePlayer = NULL;
return CTFBotAttack::OnStart( me, priorAction );
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotAttackFlagDefenders::Update( CTFBot *me, float interval )
{
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
// prepare to fight
me->EquipBestWeaponForThreat( threat );
}
if ( m_watchFlagTimer.IsElapsed() && m_minDurationTimer.IsElapsed() )
{
m_watchFlagTimer.Start( RandomFloat( 1.0f, 3.0f ) );
CCaptureFlag *flag = me->GetFlagToFetch();
if ( !flag )
{
return Done( "No flag" );
}
// can't reach flag if it is at home
if ( !TFGameRules()->IsMannVsMachineMode() || !flag->IsHome() )
{
CTFPlayer *carrier = ToTFPlayer( flag->GetOwnerEntity() );
if ( !carrier )
{
return Done( "Flag was dropped" );
}
if ( me->IsSelf( carrier ) )
{
return Done( "I picked up the flag!" );
}
// escort the flag carrier, unless the carrier is in a squad
CTFBot *botCarrier = ToTFBot( carrier );
if ( !botCarrier || !botCarrier->IsInASquad() )
{
if ( me->IsRangeLessThan( carrier, tf_bot_flag_escort_range.GetFloat() ) )
{
if ( GetBotEscortCount( me->GetTeamNumber() ) < tf_bot_flag_escort_max_count.GetInt() )
{
return ChangeTo( new CTFBotEscortFlagCarrier, "Near flag carrier - escorting" );
}
}
}
}
}
ActionResult< CTFBot > result = CTFBotAttack::Update( me, interval );
if ( result.IsDone() )
{
// nothing to attack, move towards a random player
if ( m_chasePlayer == NULL || !m_chasePlayer->IsAlive() )
{
m_chasePlayer = me->SelectRandomReachableEnemy();
}
if ( m_chasePlayer == NULL )
{
// everyone is dead or hiding in the spawn room - go escort the flag
return ChangeTo( new CTFBotEscortFlagCarrier, "No reachable victim - escorting flag" );
}
// cheat and "see" our victim so we know where to go
me->GetVisionInterface()->AddKnownEntity( m_chasePlayer );
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 3.0f ) );
CTFBotPathCost cost( me, DEFAULT_ROUTE );
float maxPathLength = TFGameRules()->IsMannVsMachineMode() ? TFBOT_MVM_MAX_PATH_LENGTH : 0.0f;
m_path.Compute( me, m_chasePlayer, cost, maxPathLength );
}
m_path.Update( me );
}
return Continue();
}
@@ -0,0 +1,34 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_attack_flag_defenders.h
// Attack enemies that are preventing the flag from reaching its destination
// Michael Booth, May 2011
#ifndef TF_BOT_ATTACK_FLAG_DEFENDERS_H
#define TF_BOT_ATTACK_FLAG_DEFENDERS_H
#include "Path/NextBotPathFollow.h"
#include "bot/behavior/tf_bot_attack.h"
//-----------------------------------------------------------------------------
class CTFBotAttackFlagDefenders : public CTFBotAttack
{
public:
CTFBotAttackFlagDefenders( float minDuration = -1.0f );
virtual ~CTFBotAttackFlagDefenders() { }
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual const char *GetName( void ) const { return "AttackFlagDefenders"; }
private:
CountdownTimer m_minDurationTimer;
CountdownTimer m_watchFlagTimer;
CHandle< CTFPlayer > m_chasePlayer;
PathFollower m_path;
CountdownTimer m_repathTimer;
};
#endif // TF_BOT_ATTACK_FLAG_DEFENDERS_H
@@ -0,0 +1,422 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_deliver_flag.cpp
// Take the flag we are holding to its destination
// Michael Booth, May 2011
#include "cbase.h"
#include "tf_player_shared.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.h"
#include "bot/behavior/tf_bot_taunt.h"
#include "bot/behavior/tf_bot_mvm_deploy_bomb.h"
#include "tf_objective_resource.h"
#include "player_vs_environment/tf_population_manager.h"
#include "econ_item_system.h"
#include "tf_gamestats.h"
#include "bot/behavior/nav_entities/tf_bot_nav_ent_move_to.h"
#include "bot/behavior/nav_entities/tf_bot_nav_ent_wait.h"
#include "particle_parse.h"
ConVar tf_mvm_bot_allow_flag_carrier_to_fight( "tf_mvm_bot_allow_flag_carrier_to_fight", "1", FCVAR_CHEAT );
ConVar tf_mvm_bot_flag_carrier_interval_to_1st_upgrade( "tf_mvm_bot_flag_carrier_interval_to_1st_upgrade", "5", FCVAR_CHEAT );
ConVar tf_mvm_bot_flag_carrier_interval_to_2nd_upgrade( "tf_mvm_bot_flag_carrier_interval_to_2nd_upgrade", "15", FCVAR_CHEAT );
ConVar tf_mvm_bot_flag_carrier_interval_to_3rd_upgrade( "tf_mvm_bot_flag_carrier_interval_to_3rd_upgrade", "15", FCVAR_CHEAT );
ConVar tf_mvm_bot_flag_carrier_health_regen( "tf_mvm_bot_flag_carrier_health_regen", "45.0f", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotDeliverFlag::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_flTotalTravelDistance = -1.0f;
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
if ( tf_mvm_bot_allow_flag_carrier_to_fight.GetBool() == false )
{
me->SetAttribute( CTFBot::SUPPRESS_FIRE );
}
// mini-bosses don't upgrade - they are already tough
if ( me->IsMiniBoss() )
{
m_upgradeLevel = DONT_UPGRADE;
if ( TFObjectiveResource() )
{
// Set threat level to max
TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 4 );
TFObjectiveResource()->SetBaseMvMBombUpgradeTime( -1 );
TFObjectiveResource()->SetNextMvMBombUpgradeTime( -1 );
}
}
else
{
m_upgradeLevel = 0;
m_upgradeTimer.Start( tf_mvm_bot_flag_carrier_interval_to_1st_upgrade.GetFloat() );
if ( TFObjectiveResource() )
{
TFObjectiveResource()->SetBaseMvMBombUpgradeTime( gpGlobals->curtime );
TFObjectiveResource()->SetNextMvMBombUpgradeTime( gpGlobals->curtime + m_upgradeTimer.GetRemainingTime() );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
// In Mann Vs Machine, the flag carrier gets stronger the longer he carries the flag
bool CTFBotDeliverFlag::UpgradeOverTime( CTFBot *me )
{
if ( TFGameRules()->IsMannVsMachineMode() && m_upgradeLevel != DONT_UPGRADE )
{
CTFNavArea *myArea = me->GetLastKnownArea();
int spawnRoomFlag = me->GetTeamNumber() == TF_TEAM_RED ? TF_NAV_SPAWN_ROOM_RED : TF_NAV_SPAWN_ROOM_BLUE;
if ( myArea && myArea->HasAttributeTF( spawnRoomFlag ) )
{
// don't start counting down until we leave the spawn
m_upgradeTimer.Start( tf_mvm_bot_flag_carrier_interval_to_1st_upgrade.GetFloat() );
TFObjectiveResource()->SetBaseMvMBombUpgradeTime( gpGlobals->curtime );
TFObjectiveResource()->SetNextMvMBombUpgradeTime( gpGlobals->curtime + m_upgradeTimer.GetRemainingTime() );
}
// do defensive buff effect ourselves (since we're not a soldier)
if ( m_upgradeLevel > 0 && m_buffPulseTimer.IsElapsed() )
{
m_buffPulseTimer.Start( 1.0f );
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, me->GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );
const float buffRadius = 450.0f;
for( int i=0; i<playerVector.Count(); ++i )
{
if ( me->IsRangeLessThan( playerVector[i], buffRadius ) )
{
playerVector[i]->m_Shared.AddCond( TF_COND_DEFENSEBUFF_NO_CRIT_BLOCK, 1.2f );
}
}
}
// the flag carrier gets stronger the longer he holds the flag
if ( m_upgradeTimer.IsElapsed() )
{
const int maxLevel = 3;
if ( m_upgradeLevel < maxLevel )
{
++m_upgradeLevel;
TFGameRules()->BroadcastSound( 255, "MVM.Warning" );
switch( m_upgradeLevel )
{
//---------------------------------------
case 1:
m_upgradeTimer.Start( tf_mvm_bot_flag_carrier_interval_to_2nd_upgrade.GetFloat() );
// permanent buff banner effect (handled above)
// update the objective resource so clients have the information
if ( TFObjectiveResource() )
{
TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 1 );
TFObjectiveResource()->SetBaseMvMBombUpgradeTime( gpGlobals->curtime );
TFObjectiveResource()->SetNextMvMBombUpgradeTime( gpGlobals->curtime + m_upgradeTimer.GetRemainingTime() );
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_BOMB_CARRIER_UPGRADE1, TF_TEAM_PVE_DEFENDERS );
DispatchParticleEffect( "mvm_levelup1", PATTACH_POINT_FOLLOW, me, "head" );
}
return true;
//---------------------------------------
case 2:
{
static CSchemaAttributeDefHandle pAttrDef_HealthRegen( "health regen" );
m_upgradeTimer.Start( tf_mvm_bot_flag_carrier_interval_to_3rd_upgrade.GetFloat() );
if ( !pAttrDef_HealthRegen )
{
Warning( "TFBotSpawner: Invalid attribute 'health regen'\n" );
}
else
{
CAttributeList *pAttrList = me->GetAttributeList();
if ( pAttrList )
{
pAttrList->SetRuntimeAttributeValue( pAttrDef_HealthRegen, tf_mvm_bot_flag_carrier_health_regen.GetFloat() );
}
}
// update the objective resource so clients have the information
if ( TFObjectiveResource() )
{
TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 2 );
TFObjectiveResource()->SetBaseMvMBombUpgradeTime( gpGlobals->curtime );
TFObjectiveResource()->SetNextMvMBombUpgradeTime( gpGlobals->curtime + m_upgradeTimer.GetRemainingTime() );
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_BOMB_CARRIER_UPGRADE2, TF_TEAM_PVE_DEFENDERS );
DispatchParticleEffect( "mvm_levelup2", PATTACH_POINT_FOLLOW, me, "head" );
}
return true;
}
//---------------------------------------
case 3:
// add critz
me->m_Shared.AddCond( TF_COND_CRITBOOSTED );
// update the objective resource so clients have the information
if ( TFObjectiveResource() )
{
TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 3 );
TFObjectiveResource()->SetBaseMvMBombUpgradeTime( -1 );
TFObjectiveResource()->SetNextMvMBombUpgradeTime( -1 );
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_BOMB_CARRIER_UPGRADE3, TF_TEAM_PVE_DEFENDERS );
DispatchParticleEffect( "mvm_levelup3", PATTACH_POINT_FOLLOW, me, "head" );
}
return true;
}
}
}
}
return false;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotDeliverFlag::Update( CTFBot *me, float interval )
{
CCaptureFlag *flag = me->GetFlagToFetch();
if ( !flag )
{
return Done( "No flag" );
}
CTFPlayer *carrier = ToTFPlayer( flag->GetOwnerEntity() );
if ( !carrier || !me->IsSelf( carrier ) )
{
return Done( "I'm no longer carrying the flag" );
}
if ( TFGameRules()->IsMannVsMachineMode() )
{
// let the bomb carrier use it's buff banners/etc
Action< CTFBot > *result = me->OpportunisticallyUseWeaponAbilities();
if ( result )
{
return SuspendFor( result, "Opportunistically using buff item" );
}
}
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
// prepare to fight
me->EquipBestWeaponForThreat( threat );
}
// deliver the flag
if ( m_repathTimer.IsElapsed() )
{
CCaptureZone *zone = me->GetFlagCaptureZone();
if ( !zone )
{
return Done( "No flag capture zone exists!" );
}
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, zone->WorldSpaceCenter(), cost );
float flOldTravelDistance = m_flTotalTravelDistance;
m_flTotalTravelDistance = NavAreaTravelDistance( me->GetLastKnownArea(), TheNavMesh->GetNavArea( zone->WorldSpaceCenter() ), cost );
if ( flOldTravelDistance != -1.0f && m_flTotalTravelDistance - flOldTravelDistance > 2000.0f )
{
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Bomb_Reset" );
// Look for players that helped with the reset and send an event
CUtlVector<CTFPlayer *> playerVector;
CollectPlayers( &playerVector, TF_TEAM_PVE_DEFENDERS );
FOR_EACH_VEC( playerVector, i )
{
CTFPlayer *pPlayer = playerVector[i];
if ( !pPlayer )
continue;
if ( me->m_AchievementData.IsPusherInHistory( pPlayer, 3.f ) )
{
IGameEvent *event = gameeventmanager->CreateEvent( "mvm_bomb_reset_by_player" );
if ( event )
{
event->SetInt( "player", pPlayer->entindex() );
gameeventmanager->FireEvent( event );
}
CTF_GameStats.Event_PlayerAwardBonusPoints( pPlayer, me, 100 );
}
}
}
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
}
m_path.Update( me );
if ( UpgradeOverTime( me ) )
{
return SuspendFor( new CTFBotTaunt, "Taunting for our new upgrade" );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotDeliverFlag::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
me->ClearAttribute( CTFBot::SUPPRESS_FIRE );
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
{
me->m_Shared.ResetRageBuffs();
}
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotDeliverFlag::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
if ( tf_mvm_bot_allow_flag_carrier_to_fight.GetBool() )
{
return ANSWER_UNDEFINED;
}
return ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
// are we in a hurry?
QueryResultType CTFBotDeliverFlag::ShouldHurry( const INextBot *me ) const
{
return ANSWER_YES;
}
//---------------------------------------------------------------------------------------------
// is it time to retreat?
QueryResultType CTFBotDeliverFlag::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotDeliverFlag::OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result )
{
if ( TFGameRules()->IsMannVsMachineMode() && other && FClassnameIs( other, "func_capturezone" ) )
{
return TrySuspendFor( new CTFBotMvMDeployBomb, RESULT_CRITICAL, "Delivering the bomb!" );
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
CTFBotPushToCapturePoint::CTFBotPushToCapturePoint( Action< CTFBot > *nextAction )
{
m_nextAction = nextAction;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotPushToCapturePoint::Update( CTFBot *me, float interval )
{
// flag collection and delivery is handled by our parent behavior, ScenarioMonitor
CCaptureZone *zone = me->GetFlagCaptureZone();
if ( !zone )
{
if ( m_nextAction )
{
return ChangeTo( m_nextAction, "No flag capture zone exists!" );
}
return Done( "No flag capture zone exists!" );
}
Vector toZone = zone->WorldSpaceCenter() - me->GetAbsOrigin();
if ( toZone.AsVector2D().IsLengthLessThan( 50.0f ) )
{
if ( m_nextAction )
{
return ChangeTo( m_nextAction, "At destination" );
}
return Done( "At destination" );
}
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
// prepare to fight
me->EquipBestWeaponForThreat( threat );
}
if ( m_repathTimer.IsElapsed() )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, zone->WorldSpaceCenter(), cost );
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
}
m_path.Update( me );
return Continue();
}
//-----------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPushToCapturePoint::OnNavAreaChanged( CTFBot *me, CNavArea *newArea, CNavArea *oldArea )
{
// does the area we are entering have a prerequisite?
if ( newArea && newArea->HasPrerequisite( me ) )
{
const CUtlVector< CHandle< CFuncNavPrerequisite > > &prereqVector = newArea->GetPrerequisiteVector();
for( int i=0; i<prereqVector.Count(); ++i )
{
const CFuncNavPrerequisite *prereq = prereqVector[i];
if ( prereq && prereq->IsEnabled() && const_cast< CFuncNavPrerequisite * >( prereq )->PassesTriggerFilters( me ) )
{
// this prerequisite applies to me
if ( prereq->IsTask( CFuncNavPrerequisite::TASK_WAIT ) )
{
return TrySuspendFor( new CTFBotNavEntWait( prereq ), RESULT_IMPORTANT, "Prerequisite commands me to wait" );
}
else if ( prereq->IsTask( CFuncNavPrerequisite::TASK_MOVE_TO_ENTITY ) )
{
return TrySuspendFor( new CTFBotNavEntMoveTo( prereq ), RESULT_IMPORTANT, "Prerequisite commands me to move to an entity" );
}
}
}
}
return TryContinue();
}
@@ -0,0 +1,63 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_deliver_flag.h
// Take the flag we are holding to its destination
// Michael Booth, May 2011
#ifndef TF_BOT_DELIVER_FLAG_H
#define TF_BOT_DELIVER_FLAG_H
#include "Path/NextBotPathFollow.h"
//-----------------------------------------------------------------------------
class CTFBotDeliverFlag : public Action< CTFBot >
{
public:
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 QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const;
virtual QueryResultType ShouldHurry( const INextBot *me ) const;
virtual QueryResultType ShouldRetreat( const INextBot *me ) const;
virtual EventDesiredResult< CTFBot > OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result = NULL );
virtual const char *GetName( void ) const { return "DeliverFlag"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
float m_flTotalTravelDistance;
bool UpgradeOverTime( CTFBot *me );
CountdownTimer m_upgradeTimer;
#define DONT_UPGRADE -1
int m_upgradeLevel;
CountdownTimer m_buffPulseTimer;
};
//-----------------------------------------------------------------------------
class CTFBotPushToCapturePoint : public Action< CTFBot >
{
public:
CTFBotPushToCapturePoint( Action< CTFBot > *nextAction = NULL );
virtual ~CTFBotPushToCapturePoint() { }
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual EventDesiredResult< CTFBot > OnNavAreaChanged( CTFBot *me, CNavArea *newArea, CNavArea *oldArea );
virtual const char *GetName( void ) const { return "PushToCapturePoint"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
Action< CTFBot > *m_nextAction;
};
#endif // TF_BOT_DELIVER_FLAG_H
@@ -0,0 +1,142 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_escort_flag_carrier.cpp
// Escort the flag carrier to their destination
// Michael Booth, May 2011
#include "cbase.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.h"
extern ConVar tf_bot_flag_escort_range;
ConVar tf_bot_flag_escort_give_up_range( "tf_bot_flag_escort_give_up_range", "1000", FCVAR_CHEAT );
ConVar tf_bot_flag_escort_max_count( "tf_bot_flag_escort_max_count", "4", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
//
// Count the number of TFBots currently engaged in the "EscortFlagCarrier" behavior
//
int GetBotEscortCount( int team )
{
int count = 0;
CUtlVector< CTFPlayer * > livePlayerVector;
CollectPlayers( &livePlayerVector, team, COLLECT_ONLY_LIVING_PLAYERS );
int i;
for( i=0; i<livePlayerVector.Count(); ++i )
{
CTFBot *bot = dynamic_cast< CTFBot * >( livePlayerVector[i] );
if ( bot )
{
Behavior< CTFBot > *behavior = (Behavior< CTFBot > *)bot->GetIntentionInterface()->FirstContainedResponder();
if ( behavior )
{
Action< CTFBot > *action = (Action< CTFBot > *)behavior->FirstContainedResponder();
while( action && action->GetActiveChildAction() )
{
action = action->GetActiveChildAction();
}
if ( action && action->IsNamed( "EscortFlagCarrier" ) )
{
++count;
}
}
}
}
return count;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEscortFlagCarrier::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEscortFlagCarrier::Update( CTFBot *me, float interval )
{
CCaptureFlag *flag = me->GetFlagToFetch();
if ( !flag )
{
return Done( "No flag" );
}
CTFPlayer *carrier = ToTFPlayer( flag->GetOwnerEntity() );
if ( !carrier )
{
return Done( "Flag was dropped" );
}
else if ( me->IsSelf( carrier ) )
{
return Done( "I picked up the flag!" );
}
// stay near the carrier
if ( me->IsRangeGreaterThan( carrier, tf_bot_flag_escort_give_up_range.GetFloat() ) )
{
if ( me->SelectRandomReachableEnemy() )
{
// too far away - give up
return ChangeTo( new CTFBotAttackFlagDefenders, "Too far from flag carrier - attack defenders!" );
}
}
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
// prepare to fight
me->EquipBestWeaponForThreat( threat );
}
CTFWeaponBase *myWeapon = me->m_Shared.GetActiveTFWeapon();
if ( myWeapon && myWeapon->IsMeleeWeapon() )
{
if ( me->IsRangeLessThan( carrier, tf_bot_flag_escort_range.GetFloat() ) && me->IsLineOfSightClear( carrier ) )
{
ActionResult< CTFBot > result = m_meleeAttackAction.Update( me, interval );
if ( result.IsContinue() )
{
// we have a melee target, and we're still reasonably close to the flag carrier
return Continue();
}
}
}
if ( me->IsRangeGreaterThan( carrier, 0.5f * tf_bot_flag_escort_range.GetFloat() ) )
{
// move near carrier
if ( m_repathTimer.IsElapsed() )
{
if ( GetBotEscortCount( me->GetTeamNumber() ) > tf_bot_flag_escort_max_count.GetInt() )
{
if ( me->SelectRandomReachableEnemy() )
{
return Done( "Too many flag escorts - giving up" );
}
}
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, carrier, cost );
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
}
m_path.Update( me );
}
return Continue();
}
@@ -0,0 +1,31 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_escort_flag_carrier.h
// Escort the flag carrier to their destination
// Michael Booth, May 2011
#ifndef TF_BOT_ESCORT_FLAG_CARRIER_H
#define TF_BOT_ESCORT_FLAG_CARRIER_H
#include "Path/NextBotPathFollow.h"
#include "bot/behavior/tf_bot_melee_attack.h"
//-----------------------------------------------------------------------------
class CTFBotEscortFlagCarrier : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual const char *GetName( void ) const { return "EscortFlagCarrier"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
CTFBotMeleeAttack m_meleeAttackAction;
};
#endif // TF_BOT_ESCORT_FLAG_CARRIER_H
@@ -0,0 +1,128 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_fetch_flag.cpp
// Go get the flag!
// Michael Booth, May 2011
#include "cbase.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.h"
//---------------------------------------------------------------------------------------------
CTFBotFetchFlag::CTFBotFetchFlag( bool isTemporary )
{
m_isTemporary = isTemporary;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotFetchFlag::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotFetchFlag::Update( CTFBot *me, float interval )
{
CCaptureFlag *flag = me->GetFlagToFetch();
if ( !flag )
{
if ( TFGameRules()->IsMannVsMachineMode() )
{
return SuspendFor( new CTFBotAttackFlagDefenders, "Flag flag exists - Attacking the enemy flag defenders" );
}
return Done( "No flag" );
}
// uncloak so we can attack
if ( me->m_Shared.IsStealthed() )
{
me->PressAltFireButton();
}
if ( TFGameRules()->IsMannVsMachineMode() && flag->IsHome() )
{
if ( gpGlobals->curtime - me->GetSpawnTime() < 1.0f && me->GetTeamNumber() != TEAM_SPECTATOR )
{
// we just spawned - give us the flag
flag->PickUp( me, true );
}
else
{
if ( m_isTemporary )
{
return Done( "Flag unreachable" );
}
// flag is at home and we're out in the world - can't reach it
return SuspendFor( new CTFBotAttackFlagDefenders, "Flag unreachable at home - Attacking the enemy flag defenders" );
}
}
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat )
{
me->EquipBestWeaponForThreat( threat );
}
CTFPlayer *carrier = ToTFPlayer( flag->GetOwnerEntity() );
if ( carrier )
{
if ( m_isTemporary )
{
return Done( "Someone else picked up the flag" );
}
// NOTE: if I've picked up the flag, the ScenarioMonitor will handle it
return SuspendFor( new CTFBotAttackFlagDefenders, "Someone has the flag - attacking the enemy defenders" );
}
// go pick up the flag
if ( m_repathTimer.IsElapsed() )
{
CTFBotPathCost cost( me, DEFAULT_ROUTE );
float maxPathLength = TFGameRules()->IsMannVsMachineMode() ? TFBOT_MVM_MAX_PATH_LENGTH : 0.0f;
if ( m_path.Compute( me, flag->WorldSpaceCenter(), cost, maxPathLength ) == false )
{
if ( flag->IsDropped() )
{
// flag is unreachable - attack for awhile and hope someone else can dislodge it
return SuspendFor( new CTFBotAttackFlagDefenders( RandomFloat( 5.0f, 10.0f ) ), "Flag unreachable - Attacking" );
// just give it to me
// flag->PickUp( me, true );
}
}
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
}
m_path.Update( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
// are we in a hurry?
QueryResultType CTFBotFetchFlag::ShouldHurry( const INextBot *me ) const
{
return ANSWER_YES;
}
//---------------------------------------------------------------------------------------------
// is it time to retreat?
QueryResultType CTFBotFetchFlag::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_NO;
}
@@ -0,0 +1,35 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_fetch_flag.h
// Go get the flag!
// Michael Booth, May 2011
#ifndef TF_BOT_FETCH_FLAG_H
#define TF_BOT_FETCH_FLAG_H
#include "Path/NextBotPathFollow.h"
//-----------------------------------------------------------------------------
class CTFBotFetchFlag : public Action< CTFBot >
{
public:
#define TEMPORARY_FLAG_FETCH true
CTFBotFetchFlag( bool isTemporary = false );
virtual ~CTFBotFetchFlag() { }
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual QueryResultType ShouldHurry( const INextBot *me ) const;
virtual QueryResultType ShouldRetreat( const INextBot *me ) const;
virtual const char *GetName( void ) const { return "FetchFlag"; };
private:
bool m_isTemporary;
PathFollower m_path;
CountdownTimer m_repathTimer;
};
#endif // TF_BOT_FETCH_FLAG_H
@@ -0,0 +1,240 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_creep_wave.cpp
// Move in a "creep wave" to the next available control point to capture it
// Michael Booth, August 2010
#include "cbase.h"
#ifdef TF_CREEP_MODE
#include "team.h"
#include "team_control_point_master.h"
#include "bot/tf_bot_manager.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/creep_wave/tf_bot_creep_wave.h"
ConVar tf_creep_aggro_range( "tf_creep_aggro_range", "250" );
ConVar tf_creep_give_up_range( "tf_creep_give_up_range", "300" );
CTFPlayer *FindNearestEnemy( CTFBot *me, float maxRange )
{
CBasePlayer *closest = NULL;
float closeRangeSq = maxRange * maxRange;
for( int i=1; i<=gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast< CBasePlayer * >( UTIL_PlayerByIndex( i ) );
if ( !player )
continue;
if ( FNullEnt( player->edict() ) )
continue;
if ( me->IsFriend( player ) )
continue;
if ( !player->IsAlive() )
continue;
float rangeSq = me->GetRangeSquaredTo( player );
if ( rangeSq < closeRangeSq )
{
if ( me->IsLineOfFireClear( player ) )
{
closeRangeSq = rangeSq;
closest = player;
}
}
}
return (CTFPlayer *)closest;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotCreepWave::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
me->StopLookingAroundForEnemies();
m_stuckTimer.Invalidate();
me->GetPlayerClass()->SetCustomModel( "models/bots/bot_heavy.mdl", USE_CLASS_ANIMATIONS );
me->UpdateModel();
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotCreepWave::Update( CTFBot *me, float interval )
{
if ( !me->IsAlive() && me->StateGet() != TF_STATE_DYING )
{
// remove dead creeps for now
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", me->GetUserID() ) );
}
CBaseCombatWeapon *melee = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
if ( melee )
{
me->Weapon_Switch( melee );
}
CUtlVector< CTeamControlPoint * > captureVector;
TFGameRules()->CollectCapturePoints( me, &captureVector );
if ( captureVector.Count() == 0 )
{
return Continue();
}
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, captureVector[0]->WorldSpaceCenter(), cost );
}
m_path.Update( me );
if ( m_stuckTimer.HasStarted() )
{
// juke and dodge to escape stuck situation
switch( RandomInt( 0, 3 ) )
{
case 0: me->PressBackwardButton(); break;
case 1: me->PressForwardButton(); break;
case 2: me->PressLeftButton(); break;
case 3: me->PressRightButton(); break;
}
}
CTFPlayer *enemy = FindNearestEnemy( me, tf_creep_aggro_range.GetFloat() );
if ( enemy )
{
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_INCOMING );
return SuspendFor( new CTFBotCreepAttack( enemy ), "Attacking nearby enemy" );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotCreepWave::OnKilled( CTFBot *me, const CTakeDamageInfo &info )
{
if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() && me->IsEnemy( info.GetAttacker() ) )
{
TheTFBots().OnCreepKilled( ToTFPlayer( info.GetAttacker() ) );
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotCreepWave::OnStuck( CTFBot *me )
{
m_stuckTimer.Start();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotCreepWave::OnUnStuck( CTFBot *me )
{
m_stuckTimer.Invalidate();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
CTFBotCreepAttack::CTFBotCreepAttack( CTFPlayer *victim )
{
m_victim = victim;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotCreepAttack::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotCreepAttack::Update( CTFBot *me, float interval )
{
if ( !me->IsAlive() && me->StateGet() != TF_STATE_DYING )
{
// remove dead creeps for now
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", me->GetUserID() ) );
}
if ( m_victim.Get() == NULL || !m_victim->IsAlive() )
{
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_JEERS );
return Done( "Killed victim" );
}
if ( me->IsRangeGreaterThan( m_victim, tf_creep_give_up_range.GetFloat() ) ||
!me->IsLineOfFireClear( m_victim ) )
{
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_NEGATIVE );
return Done( "Lost victim" );
}
CTFPlayer *newVictim = FindNearestEnemy( me, tf_creep_aggro_range.GetFloat() );
if ( newVictim )
{
float newRangeSq = me->GetRangeSquaredTo( newVictim );
float victimRangeSq = me->GetRangeSquaredTo( m_victim );
if ( newRangeSq < victimRangeSq )
{
// switch to closer target
m_victim = newVictim;
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
}
}
CBaseCombatWeapon *melee = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
if ( melee )
{
me->Weapon_Switch( melee );
}
me->GetBodyInterface()->AimHeadTowards( m_victim, IBody::CRITICAL, 0.2f, NULL, "Looking at enemy" );
// swing weapon
me->PressFireButton();
// beeline towards our victim
const float combatRange = 40.0f;
if ( me->IsRangeGreaterThan( m_victim, combatRange ) )
{
me->GetLocomotionInterface()->Approach( m_victim->GetAbsOrigin() );
}
else
{
// juke and dodge to avoid interpenetration
switch( RandomInt( 0, 3 ) )
{
case 0: me->PressBackwardButton(); break;
case 1: me->PressForwardButton(); break;
case 2: me->PressLeftButton(); break;
case 3: me->PressRightButton(); break;
}
}
return Continue();
}
#endif // TF_CREEP_MODE
@@ -0,0 +1,57 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_creep_wave.h
// Move in a "creep wave" to the next available control point to capture it
// Michael Booth, August 2010
#ifndef TF_BOT_CREEP_WAVE_H
#define TF_BOT_CREEP_WAVE_H
#ifdef TF_CREEP_MODE
#include "Path/NextBotPathFollow.h"
#include "Path/NextBotChasePath.h"
CTFBot *FindNearestEnemyCreep( CTFBot *me );
//-----------------------------------------------------------------------------
class CTFBotCreepWave : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual EventDesiredResult< CTFBot > OnKilled( CTFBot *me, const CTakeDamageInfo &info );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnUnStuck( CTFBot *me );
virtual const char *GetName( void ) const { return "CreepWave"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
IntervalTimer m_stuckTimer;
};
//-----------------------------------------------------------------------------
class CTFBotCreepAttack : public Action< CTFBot >
{
public:
CTFBotCreepAttack( CTFPlayer *victim );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual const char *GetName( void ) const { return "CreepAttack"; };
private:
CHandle< CTFPlayer > m_victim;
};
#endif // TF_CREEP_MODE
#endif // TF_BOT_CREEP_WAVE_H
@@ -0,0 +1,151 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_payload_block.cpp
// Prevent the other team from moving the cart
// Michael Booth, April 2010
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "team_control_point_master.h"
#include "team_train_watcher.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/payload/tf_bot_payload_block.h"
#include "bot/behavior/medic/tf_bot_medic_heal.h"
#include "bot/behavior/engineer/tf_bot_engineer_build.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotPayloadBlock::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_path.Invalidate();
m_giveUpTimer.Start( RandomFloat( 3.0f, 5.0f ) );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotPayloadBlock::Update( CTFBot *me, float interval )
{
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
// prepare to fight
me->EquipBestWeaponForThreat( threat );
}
if ( m_giveUpTimer.IsElapsed() )
{
return Done( "Been blocking long enough" );
}
// move toward the point, periodically repathing to account for changing situation
if ( m_repathTimer.IsElapsed() )
{
VPROF_BUDGET( "CTFBotPayloadBlock::Update( repath )", "NextBot" );
CTeamTrainWatcher *trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() );
if ( !trainWatcher )
{
return Done( "Train Watcher is missing" );
}
CBaseEntity *cart = trainWatcher->GetTrainEntity();
if ( !cart )
{
return Done( "Cart is missing" );
}
CTFBotPathCost cost( me, DEFAULT_ROUTE );
m_path.Compute( me, cart->WorldSpaceCenter(), cost );
m_repathTimer.Start( RandomFloat( 0.2f, 0.4f ) );
}
// move towards next capture point
m_path.Update( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotPayloadBlock::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
VPROF_BUDGET( "CTFBotPayloadBlock::OnResume", "NextBot" );
m_repathTimer.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnStuck( CTFBot *me )
{
VPROF_BUDGET( "CTFBotPayloadBlock::OnStuck", "NextBot" );
m_repathTimer.Invalidate();
me->GetLocomotionInterface()->ClearStuckStatus();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnMoveToSuccess( CTFBot *me, const Path *path )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
VPROF_BUDGET( "CTFBotPayloadBlock::OnMoveToFailure", "NextBot" );
m_repathTimer.Invalidate();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnTerritoryContested( CTFBot *me, int territoryID )
{
return TryToSustain( RESULT_IMPORTANT );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnTerritoryCaptured( CTFBot *me, int territoryID )
{
return TryToSustain( RESULT_IMPORTANT );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnTerritoryLost( CTFBot *me, int territoryID )
{
return TryToSustain( RESULT_IMPORTANT );
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotPayloadBlock::ShouldRetreat( const INextBot *bot ) const
{
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotPayloadBlock::ShouldHurry( const INextBot *bot ) const
{
// hurry and block the cart - don't retreat, etc
return ANSWER_YES;
}
@@ -0,0 +1,38 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_payload_block.h
// Prevent the other team from moving the cart
// Michael Booth, April 2010
#ifndef TF_BOT_PAYLOAD_BLOCK_H
#define TF_BOT_PAYLOAD_BLOCK_H
#include "Path/NextBotPathFollow.h"
class CTFBotPayloadBlock : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual EventDesiredResult< CTFBot > OnTerritoryContested( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual const char *GetName( void ) const { return "PayloadBlock"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
CountdownTimer m_giveUpTimer;
};
#endif // TF_BOT_PAYLOAD_BLOCK_H
@@ -0,0 +1,283 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_payload_guard.cpp
// Guard the payload and keep the attackers from getting near it
// Michael Booth, April 2010
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "team_control_point_master.h"
#include "team_train_watcher.h"
#include "trigger_area_capture.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/payload/tf_bot_payload_guard.h"
#include "bot/behavior/scenario/payload/tf_bot_payload_block.h"
#include "bot/behavior/medic/tf_bot_medic_heal.h"
#include "bot/behavior/engineer/tf_bot_engineer_build.h"
#include "bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h"
extern ConVar tf_bot_path_lookahead_range;
ConVar tf_bot_payload_guard_range( "tf_bot_payload_guard_range", "1000", FCVAR_CHEAT );
ConVar tf_bot_debug_payload_guard_vantage_points( "tf_bot_debug_payload_guard_vantage_points", 0, FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotPayloadGuard::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_path.Invalidate();
m_vantagePoint = me->GetAbsOrigin();
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotPayloadGuard::Update( CTFBot *me, float interval )
{
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
// prepare to fight
me->EquipBestWeaponForThreat( threat );
}
CTeamTrainWatcher *trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() );
if ( !trainWatcher )
{
return Continue();
}
CBaseEntity *cart = trainWatcher->GetTrainEntity();
if ( !cart )
{
return Continue();
}
if ( !trainWatcher->IsDisabled() && trainWatcher->GetCapturerCount() > 0 )
{
// the cart is being pushed ahead - block it
if ( !m_moveToBlockTimer.HasStarted() )
{
m_moveToBlockTimer.Start( RandomFloat( 0.5f, 3.0f ) );
}
}
if ( m_moveToBlockTimer.HasStarted() && m_moveToBlockTimer.IsElapsed() )
{
m_moveToBlockTimer.Invalidate();
if ( trainWatcher->GetCapturerCount() >= 0 )
{
// the cart is not yet blocked - move to block it!
return SuspendFor( new CTFBotPayloadBlock, "Moving to block the cart's forward motion" );
}
}
bool isMovingToVantagePoint = ( me->GetAbsOrigin() - m_vantagePoint ).AsVector2D().IsLengthGreaterThan( 25.0f );
if ( isMovingToVantagePoint )
{
// en route, don't change the point
m_vantagePointTimer.Start( RandomFloat( 3.0f, 15.0f ) );
}
if ( !me->IsLineOfFireClear( cart ) )
{
// cart is no longer visible from this area, find another one
m_vantagePointTimer.Invalidate();
}
if ( m_vantagePointTimer.IsElapsed() )
{
// find a new vantage point
m_vantagePoint = FindVantagePoint( me, cart );
m_repathTimer.Invalidate();
isMovingToVantagePoint = true;
}
if ( isMovingToVantagePoint )
{
// update our path periodically
if ( m_repathTimer.IsElapsed() )
{
CTFBotPathCost cost( me, DEFAULT_ROUTE );
m_path.Compute( me, m_vantagePoint, cost );
m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) );
}
// move towards our vantage point
m_path.Update( me );
}
else
{
// at vantage point
if ( CTFBotPrepareStickybombTrap::IsPossible( me ) )
{
return SuspendFor( new CTFBotPrepareStickybombTrap, "Laying sticky bombs!" );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotPayloadGuard::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
VPROF_BUDGET( "CTFBotPayloadGuard::OnResume", "NextBot" );
m_vantagePointTimer.Invalidate();
m_repathTimer.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnStuck( CTFBot *me )
{
VPROF_BUDGET( "CTFBotPayloadGuard::OnStuck", "NextBot" );
m_repathTimer.Invalidate();
me->GetLocomotionInterface()->ClearStuckStatus();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnMoveToSuccess( CTFBot *me, const Path *path )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
VPROF_BUDGET( "CTFBotPayloadGuard::OnMoveToFailure", "NextBot" );
m_vantagePointTimer.Invalidate();
m_repathTimer.Invalidate();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
// Invoked when cart is being pushed
EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnTerritoryContested( CTFBot *me, int territoryID )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnTerritoryCaptured( CTFBot *me, int territoryID )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
// Invoked when cart hits a checkpoint
EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnTerritoryLost( CTFBot *me, int territoryID )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotPayloadGuard::ShouldRetreat( const INextBot *bot ) const
{
CTFBot *me = ToTFBot( bot->GetEntity() );
CTeamTrainWatcher *trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() );
if ( trainWatcher && trainWatcher->IsTrainNearCheckpoint() )
{
// don't retreat if the cart is almost at the next checkpoint
return ANSWER_NO;
}
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotPayloadGuard::ShouldHurry( const INextBot *bot ) const
{
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
class CCollectPayloadGuardVantagePoints : public ISearchSurroundingAreasFunctor
{
public:
CCollectPayloadGuardVantagePoints( CTFBot *me, CBaseEntity *cart )
{
m_me = me;
m_cart = cart;
}
virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
{
CTFNavArea *area = (CTFNavArea *)baseArea;
// TODO: only use areas that are at/farther along than the payload
trace_t trace;
NextBotTraceFilterIgnoreActors filter( NULL, COLLISION_GROUP_NONE );
const int tryCount = 3;
for( int i=0; i<tryCount; ++i )
{
Vector spot = area->GetRandomPoint();
Vector eyeSpot = Vector( spot.x, spot.y, spot.z + HumanEyeHeight );
UTIL_TraceLine( eyeSpot, m_cart->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, &filter, &trace );
if ( !trace.DidHit() || trace.m_pEnt == m_cart )
{
m_vantagePointVector.AddToTail( spot );
if ( tf_bot_debug_payload_guard_vantage_points.GetBool() )
{
NDebugOverlay::Cross3D( spot, 5.0f, 255, 0, 255, true, 120.0f );
}
}
}
return true;
}
CTFBot *m_me;
CBaseEntity *m_cart;
CUtlVector< Vector > m_vantagePointVector;
};
//---------------------------------------------------------------------------------------------
//
// Find a tactically advantageous area where we can see the payload
//
Vector CTFBotPayloadGuard::FindVantagePoint( CTFBot *me, CBaseEntity *cart )
{
CTFNavArea *cartArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( cart );
CCollectPayloadGuardVantagePoints collect( me, cart );
SearchSurroundingAreas( cartArea, collect, tf_bot_payload_guard_range.GetFloat() );
if ( collect.m_vantagePointVector.Count() == 0 )
return cart->WorldSpaceCenter();
int which = RandomInt( 0, collect.m_vantagePointVector.Count()-1 );
return collect.m_vantagePointVector[ which ];
}
@@ -0,0 +1,43 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_payload_guard.h
// Guard the payload and keep the attackers from getting near it
// Michael Booth, April 2010
#ifndef TF_BOT_PAYLOAD_GUARD_H
#define TF_BOT_PAYLOAD_GUARD_H
#include "Path/NextBotPathFollow.h"
class CTFBotPayloadGuard : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual EventDesiredResult< CTFBot > OnTerritoryContested( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual const char *GetName( void ) const { return "PayloadGuard"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
Vector m_vantagePoint;
CountdownTimer m_vantagePointTimer;
Vector FindVantagePoint( CTFBot *me, CBaseEntity *cart );
CountdownTimer m_moveToBlockTimer;
};
#endif // TF_BOT_PAYLOAD_GUARD_H
@@ -0,0 +1,155 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_payload_push.cpp
// Push the cartTrigger to the goal
// Michael Booth, April 2010
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "team_control_point_master.h"
#include "team_train_watcher.h"
#include "trigger_area_capture.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/payload/tf_bot_payload_push.h"
#include "bot/behavior/medic/tf_bot_medic_heal.h"
#include "bot/behavior/engineer/tf_bot_engineer_build.h"
extern ConVar tf_bot_path_lookahead_range;
ConVar tf_bot_cart_push_radius( "tf_bot_cart_push_radius", "60", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotPayloadPush::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_path.Invalidate();
m_hideAngle = 180.0f;
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotPayloadPush::Update( CTFBot *me, float interval )
{
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
// prepare to fight
me->EquipBestWeaponForThreat( threat );
}
if ( TFGameRules()->InSetup() )
{
// wait until the gates open, then path
m_path.Invalidate();
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
return Continue();
}
CTeamTrainWatcher *trainWatcher = TFGameRules()->GetPayloadToPush( me->GetTeamNumber() );
if ( !trainWatcher )
{
return Continue();
}
CBaseEntity *cart = trainWatcher->GetTrainEntity();
if ( !cart )
{
return Continue();
}
// move toward the point, periodically repathing to account for changing situation
if ( m_repathTimer.IsElapsed() )
{
VPROF_BUDGET( "CTFBotPayloadPush::Update( repath )", "NextBot" );
Vector cartForward;
cart->GetVectors( &cartForward, NULL, NULL );
// default push position is behind cart
Vector pushPos = cart->WorldSpaceCenter() - cartForward * tf_bot_cart_push_radius.GetFloat();
// try to hide from enemies on other side of cart
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat )
{
Vector enemyToCart = cart->WorldSpaceCenter() - threat->GetLastKnownPosition();
enemyToCart.z = 0.0f;
enemyToCart.NormalizeInPlace();
pushPos = cart->WorldSpaceCenter() + tf_bot_cart_push_radius.GetFloat() * enemyToCart;
}
CTFBotPathCost cost( me, DEFAULT_ROUTE );
m_path.Compute( me, pushPos, cost );
m_repathTimer.Start( RandomFloat( 0.2f, 0.4f ) );
}
// push the cartTrigger
m_path.Update( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotPayloadPush::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
VPROF_BUDGET( "CTFBotPayloadPush::OnResume", "NextBot" );
m_repathTimer.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadPush::OnStuck( CTFBot *me )
{
VPROF_BUDGET( "CTFBotPayloadPush::OnStuck", "NextBot" );
m_repathTimer.Invalidate();
me->GetLocomotionInterface()->ClearStuckStatus();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadPush::OnMoveToSuccess( CTFBot *me, const Path *path )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotPayloadPush::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
VPROF_BUDGET( "CTFBotPayloadPush::OnMoveToFailure", "NextBot" );
m_repathTimer.Invalidate();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotPayloadPush::ShouldRetreat( const INextBot *bot ) const
{
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotPayloadPush::ShouldHurry( const INextBot *bot ) const
{
return ANSWER_UNDEFINED;
}
@@ -0,0 +1,33 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_payload_push.h
// Push the cart to the goal
// Michael Booth, April 2010
#ifndef TF_BOT_PAYLOAD_PUSH_H
#define TF_BOT_PAYLOAD_PUSH_H
#include "Path/NextBotPathFollow.h"
class CTFBotPayloadPush : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual const char *GetName( void ) const { return "PayloadPush"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
float m_hideAngle;
};
#endif // TF_BOT_PAYLOAD_PUSH_H
@@ -0,0 +1,217 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_raid_companion.cpp
// Teammate bots for Raid mode
// Michael Booth, October 2009
#include "cbase.h"
#ifdef TF_RAID_MODE
#include "team.h"
#include "bot/tf_bot.h"
#include "team_control_point_master.h"
#include "bot/behavior/medic/tf_bot_medic_heal.h"
#include "bot/behavior/scenario/raid/tf_bot_companion.h"
#include "bot/behavior/tf_bot_attack.h"
#include "bot/behavior/tf_bot_move_to_vantage_point.h"
#include "bot/behavior/engineer/tf_bot_engineer_build.h"
#include "bot/behavior/sniper/tf_bot_sniper_lurk.h"
#include "bot/map_entities/tf_bot_generator.h" // action point
ConVar tf_raid_companion_follow_range( "tf_raid_companion_follow_range", "150", FCVAR_CHEAT );
ConVar tf_raid_companion_allow_bot_leader( "tf_raid_companion_allow_bot_leader", "0", FCVAR_CHEAT );
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotCompanion::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
CTFPlayer *CTFBotCompanion::GetLeader( void )
{
CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE );
CTFPlayer *leader = NULL;
float leaderSpeed = FLT_MAX;
for( int i=0; i<raidingTeam->GetNumPlayers(); ++i )
{
CTFPlayer *player = (CTFPlayer *)raidingTeam->GetPlayer(i);
if ( player->IsBot() && !tf_raid_companion_allow_bot_leader.GetBool() )
continue;
/*
if ( player->IsPlayerClass( TF_CLASS_ENGINEER ) ||
player->IsPlayerClass( TF_CLASS_SNIPER ) ||
player->IsPlayerClass( TF_CLASS_MEDIC ) )
continue;
*/
if ( player->IsAlive() )
{
float speed = player->GetPlayerClass()->GetMaxSpeed();
if ( speed < leaderSpeed )
{
leader = player;
leaderSpeed = speed;
}
}
}
return leader;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotCompanion::Update( CTFBot *me, float interval )
{
if ( me->IsPlayerClass( TF_CLASS_MEDIC ) )
{
const CKnownEntity *patient = me->GetVisionInterface()->GetClosestKnown( me->GetTeamNumber() );
if ( patient )
{
return SuspendFor( new CTFBotMedicHeal );
}
}
CTFPlayer *leader = GetLeader();
if ( !leader )
return Continue();
CTFBotPathCost cost( me, FASTEST_ROUTE );
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( me->IsSelf( leader ) )
{
const float engageRange = 500.0f;
if ( threat && threat->IsVisibleRecently() && me->IsRangeLessThan( threat->GetEntity(), engageRange ) )
{
// stop pushing ahead and kill nearby threats
return SuspendFor( new CTFBotAttack, "Attacking nearby threats" );
}
// head toward next capture point
CTeamControlPoint *point = me->GetMyControlPoint();
if ( point )
{
m_path.Update( me, point, cost );
}
}
else
{
if ( ( !threat || threat->GetTimeSinceLastSeen() > 3.0f ) && leader->GetTimeSinceLastInjury() < 1.0f )
{
// we don't see anything, but the leader is under attack - find a better vantage point
const float nearRange = 1000.0f;
return SuspendFor( new CTFBotMoveToVantagePoint( nearRange ), "Moving to where I can see the enemy" );
}
if ( leader && me->IsDistanceBetweenGreaterThan( leader, tf_raid_companion_follow_range.GetFloat() ) )
{
m_path.Update( me, leader, cost );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotCompanion::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_path.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotGuardian::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotGuardian::Update( CTFBot *me, float interval )
{
if ( me->GetActionPoint() )
{
const float atHomeRange = 35.0f; // 25.0f;
const Vector &home = me->GetActionPoint()->GetAbsOrigin();
if ( me->IsRangeGreaterThan( home, atHomeRange ) )
{
if ( m_repathTimer.IsElapsed() && !m_path.IsValid() )
{
m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, home, cost );
}
// move home
m_path.Update( me );
return Continue();
}
}
// at home
m_path.Invalidate();
me->SetHomeArea( me->GetLastKnownArea() );
if ( me->IsPlayerClass( TF_CLASS_ENGINEER ) )
{
return SuspendFor( new CTFBotEngineerBuild );
}
if ( me->IsPlayerClass( TF_CLASS_SNIPER ) )
{
return SuspendFor( new CTFBotSniperLurk );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotGuardian::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_path.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotGuardian::OnStuck( CTFBot *me )
{
m_path.Invalidate();
return TryContinue( RESULT_IMPORTANT );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotGuardian::OnMoveToSuccess( CTFBot *me, const Path *path )
{
m_path.Invalidate();
return TryContinue( RESULT_IMPORTANT );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotGuardian::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
m_path.Invalidate();
return TryContinue( RESULT_IMPORTANT );
}
#endif // TF_RAID_MODE
@@ -0,0 +1,57 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_companion.h
// Teammate bots for Raid mode
// Michael Booth, October 2009
#ifndef TF_BOT_COMPANION_H
#define TF_BOT_COMPANION_H
#ifdef TF_RAID_MODE
#include "Path/NextBotPathFollow.h"
#include "Path/NextBotChasePath.h"
//
// Friendly teammate bots
//
class CTFBotCompanion : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual const char *GetName( void ) const { return "Companion"; };
private:
ChasePath m_path;
CTFPlayer *GetLeader( void );
};
//
// Friendly defenders of the base
//
class CTFBotGuardian : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual const char *GetName( void ) const { return "Guardian"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
};
#endif // TF_RAID_MODE
#endif // TF_BOT_COMPANION_H
@@ -0,0 +1,261 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_guard_area.cpp
// Defend an area against intruders
// Michael Booth, October 2009
#include "cbase.h"
#ifdef TF_RAID_MODE
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "team_control_point_master.h"
#include "econ_entity_creation.h"
#include "bot/behavior/tf_bot_retreat_to_cover.h"
#include "bot/behavior/sniper/tf_bot_sniper_attack.h"
#include "bot/behavior/engineer/tf_bot_engineer_build.h"
#include "bot/behavior/medic/tf_bot_medic_heal.h"
#include "bot/behavior/scenario/raid/tf_bot_wander.h"
#include "bot/behavior/scenario/raid/tf_bot_guard_area.h"
#include "bot/behavior/tf_bot_attack.h"
#include "bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h"
#include "nav_mesh.h"
extern ConVar tf_bot_path_lookahead_range;
ConVar tf_bot_guard_aggro_range( "tf_bot_guard_aggro_range", "750", FCVAR_CHEAT );
//ConVar tf_bot_guard_give_up_range( "tf_bot_guard_give_up_range", "1250", FCVAR_CHEAT );
ConVar tf_raid_special_vocalize_min_interval( "tf_raid_special_vocalize_min_interval", "10", FCVAR_CHEAT );
ConVar tf_raid_special_vocalize_max_interval( "tf_raid_special_vocalize_max_interval", "15", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotGuardArea::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_chasePath.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
/*
// give this guy a hat!
randomitemcriteria_t criteria;
criteria.iItemLevel = AE_USE_SCRIPT_VALUE;
criteria.iItemQuality = AE_USE_SCRIPT_VALUE;
criteria.vecAbsOrigin = me->GetAbsOrigin();
criteria.vecAbsAngles = vec3_angle;
switch( me->GetPlayerClass()->GetClassIndex() )
{
case TF_CLASS_SCOUT: criteria.pszItemName = "Scout Hat 1"; break;
case TF_CLASS_SNIPER: criteria.pszItemName = "Sniper Hat 1"; break;
case TF_CLASS_SOLDIER: criteria.pszItemName = "Soldier Pot Hat"; break;
case TF_CLASS_DEMOMAN: criteria.pszItemName = "Demo Top Hat"; break;
case TF_CLASS_MEDIC: criteria.pszItemName = "Medic Hat 1"; break;
case TF_CLASS_HEAVYWEAPONS: criteria.pszItemName = "Heavy Ushanka Hat"; break;
case TF_CLASS_PYRO: criteria.pszItemName = "Pyro Chicken Hat"; break;
case TF_CLASS_SPY: criteria.pszItemName = "Spy Derby Hat"; break;
case TF_CLASS_ENGINEER: criteria.pszItemName = "Engineer Hat 1"; break;
default: criteria.pszItemName = ""; break;
}
CBaseEntity *hat = ItemGeneration()->GenerateRandomItem( &criteria );
if ( hat )
{
// Fake global id
static int s_nFakeID = 1;
static_cast< CEconEntity * >( hat )->GetAttributeContainer()->GetItem()->SetItemID( s_nFakeID++ );
DispatchSpawn( hat );
static_cast< CEconEntity * >( hat )->GetAttributeContainer()->GetItem()->GenerateAttributes();
static_cast< CEconEntity * >( hat )->GiveTo( me );
}
else
{
Msg( "Failed to create hat\n" );
}
*/
return Continue();
}
//---------------------------------------------------------------------------------------------
class CFindVantagePoint : public ISearchSurroundingAreasFunctor
{
public:
CFindVantagePoint( void )
{
m_vantageArea = NULL;
}
virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar )
{
if ( travelDistanceSoFar > 2000.0f )
return false;
CTFNavArea *area = (CTFNavArea *)baseArea;
CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE );
for( int i=0; i<raidingTeam->GetNumPlayers(); ++i )
{
CTFPlayer *player = (CTFPlayer *)raidingTeam->GetPlayer(i);
if ( !player->IsAlive() || !player->GetLastKnownArea() )
continue;
CTFNavArea *playerArea = (CTFNavArea *)player->GetLastKnownArea();
if ( playerArea->IsCompletelyVisible( area ) )
{
// nearby area from which we can see the enemy team
m_vantageArea = area;
return false;
}
}
return true;
}
CTFNavArea *m_vantageArea;
};
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotGuardArea::Update( CTFBot *me, float interval )
{
// emit vocalizations to warn players we're in the area
if ( m_vocalizeTimer.IsElapsed() )
{
m_vocalizeTimer.Start( RandomFloat( tf_raid_special_vocalize_min_interval.GetFloat(), tf_raid_special_vocalize_max_interval.GetFloat() ) );
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_JEERS );
}
if ( me->IsPlayerClass( TF_CLASS_MEDIC ) )
{
return SuspendFor( new CTFBotMedicHeal );
}
if ( me->IsPlayerClass( TF_CLASS_ENGINEER ) )
{
return SuspendFor( new CTFBotEngineerBuild );
}
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
m_pathToVantageArea.Invalidate();
CTFNavArea *myArea = (CTFNavArea *)me->GetLastKnownArea();
CTFNavArea *threatArea = (CTFNavArea *)threat->GetLastKnownArea();
if ( myArea && threatArea )
{
if ( threatArea->GetIncursionDistance( TF_TEAM_BLUE ) < myArea->GetIncursionDistance( TF_TEAM_BLUE ) )
{
if ( me->IsRangeGreaterThan( threat->GetLastKnownPosition(), tf_bot_guard_aggro_range.GetFloat() ) )
{
// threat is far off and hasn't reached us yet - hide until they are closer
return SuspendFor( new CTFBotRetreatToCover, "Hiding until threat gets closer" );
}
}
}
// attack!
return SuspendFor( new CTFBotAttack, "Attacking nearby threat" );
}
else
{
// no enemy is visible
Vector moveTo = me->GetAbsOrigin();
// if point is being captured, move to it
CTeamControlPoint *point = me->GetMyControlPoint();
if ( point && point->LastContestedAt() > 0.0f && ( gpGlobals->curtime - point->LastContestedAt() ) < 5.0f )
{
// the point is, or was very recently, contested - defend it!
moveTo = point->GetAbsOrigin();
}
else if ( me->GetHomeArea() )
{
// no enemy is visible - return to our home position
moveTo = me->GetHomeArea()->GetCenter();
}
if ( !m_pathToPoint.IsValid() || m_repathTimer.IsElapsed() )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_pathToPoint.Compute( me, moveTo, cost );
m_repathTimer.Start( RandomFloat( 2.0f, 3.0f ) );
}
if ( ( me->GetAbsOrigin() - moveTo ).IsLengthGreaterThan( 25.0f ) )
{
m_pathToPoint.Update( me );
}
if ( me->GetHomeArea() == me->GetLastKnownArea() )
{
// at home
if ( CTFBotPrepareStickybombTrap::IsPossible( me ) )
{
return SuspendFor( new CTFBotPrepareStickybombTrap, "Laying sticky bombs!" );
}
}
/*
// no enemy is visible - move to where we can see them
if ( !m_pathToVantageArea.IsValid() )
{
CTFNavArea *vantageArea = me->FindVantagePoint();
if ( vantageArea )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_pathToVantageArea.Compute( me, vantageArea->GetCenter(), cost );
}
}
m_pathToVantageArea.Update( me );
*/
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotGuardArea::OnStuck( CTFBot *me )
{
m_chasePath.Invalidate();
m_pathToPoint.Invalidate();
m_pathToVantageArea.Invalidate();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotGuardArea::OnMoveToSuccess( CTFBot *me, const Path *path )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotGuardArea::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotGuardArea::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotGuardArea::OnCommandApproach( CTFBot *me, const Vector &pos, float range )
{
return TryContinue();
}
#endif // TF_RAID_MODE
@@ -0,0 +1,39 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_guard_area.h
// Defend an area against intruders
// Michael Booth, October 2009
#ifdef TF_RAID_MODE
#ifndef TF_BOT_GUARD_AREA_H
#define TF_BOT_GUARD_AREA_H
#include "Path/NextBotChasePath.h"
class CTFBotGuardArea : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual EventDesiredResult< CTFBot > OnCommandApproach( CTFBot *me, const Vector &pos, float range );
virtual const char *GetName( void ) const { return "GuardArea"; };
private:
ChasePath m_chasePath;
PathFollower m_pathToPoint;
PathFollower m_pathToVantageArea;
CountdownTimer m_vocalizeTimer;
CountdownTimer m_repathTimer;
};
#endif // TF_RAID_MODE
#endif // TF_BOT_GUARD_AREA_H
@@ -0,0 +1,164 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_mob_rush.cpp
// A member of a rushing mob of melee attackers
// Michael Booth, October 2009
#include "cbase.h"
#ifdef TF_RAID_MODE
#include "team.h"
#include "bot/tf_bot.h"
#include "bot/behavior/tf_bot_taunt.h"
#include "bot/behavior/scenario/raid/tf_bot_mob_rush.h"
ConVar tf_bot_taunt_range( "tf_bot_taunt_range", "100", FCVAR_CHEAT );
ConVar tf_raid_mob_rush_vocalize_min_interval( "tf_raid_mob_rush_vocalize_min_interval", "5", FCVAR_CHEAT );
ConVar tf_raid_mob_rush_vocalize_max_interval( "tf_raid_mob_rush_vocalize_max_interval", "8", FCVAR_CHEAT );
ConVar tf_raid_mob_avoid_range( "tf_raid_mob_avoid_range", "100", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
CTFBotMobRush::CTFBotMobRush( CTFPlayer *victim, float reactionTime )
{
m_victim = victim;
// this isn't strictly correct - we shouldn't start the timer until OnStart
m_reactionTimer.Start( reactionTime );
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMobRush::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_vocalizeTimer.Start( RandomFloat( tf_raid_mob_rush_vocalize_min_interval.GetFloat(), tf_raid_mob_rush_vocalize_max_interval.GetFloat() ) );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMobRush::Update( CTFBot *me, float interval )
{
// mobs use only their melee weapons
CBaseCombatWeapon *meleeWeapon = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
if ( meleeWeapon )
{
me->Weapon_Switch( meleeWeapon );
}
if ( m_victim == NULL )
{
return Done( "No victim" );
}
me->GetBodyInterface()->AimHeadTowards( m_victim, IBody::CRITICAL, 1.0f, NULL, "Looking at our melee target" );
if ( m_reactionTimer.HasStarted() )
{
if ( m_reactionTimer.IsElapsed() )
{
// snap out of it!
me->DoAnimationEvent( PLAYERANIMEVENT_VOICE_COMMAND_GESTURE, ACT_MP_GESTURE_VC_FINGERPOINT_PRIMARY );
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
m_reactionTimer.Invalidate();
}
else
{
// wait for reaction time to elapse
return Continue();
}
}
if ( me->IsPlayingGesture( ACT_MP_GESTURE_VC_FINGERPOINT_PRIMARY ) )
{
// wait for "wake up" anim to finish
return Continue();
}
// just keep swinging
me->PressFireButton();
// chase them down
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Update( me, m_victim, cost );
// avoid friends
CTeam *team = GetGlobalTeam( TF_TEAM_RED );
for( int t=0; t<team->GetNumPlayers(); ++t )
{
CTFPlayer *teamMember = (CTFPlayer *)team->GetPlayer(t);
if ( !teamMember->IsAlive() )
continue;
Vector toBuddy = teamMember->GetAbsOrigin() - me->GetAbsOrigin();
if ( toBuddy.IsLengthLessThan( tf_raid_mob_avoid_range.GetFloat() ) )
{
float range = toBuddy.NormalizeInPlace();
me->GetLocomotionInterface()->Approach( me->GetAbsOrigin() - 100.0f * toBuddy, 1.0f - ( range / tf_raid_mob_avoid_range.GetFloat() ) );
}
}
if ( !m_victim->IsAlive() && me->IsRangeLessThan( m_victim, tf_bot_taunt_range.GetFloat() ) )
{
// we got 'em!
return ChangeTo( new CTFBotTaunt, "Taunt their corpse" );
}
if ( m_vocalizeTimer.IsElapsed() )
{
m_vocalizeTimer.Start( RandomFloat( tf_raid_mob_rush_vocalize_min_interval.GetFloat(), tf_raid_mob_rush_vocalize_max_interval.GetFloat() ) );
if ( me->IsPlayerClass( TF_CLASS_SCOUT ) )
me->EmitSound( "Scout.MobJabber" );
else if ( me->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
me->EmitSound( "Heavy.MobJabber" );
else
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotMobRush::OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result )
{
return TryToSustain( RESULT_CRITICAL, "Ignoring contact" );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotMobRush::OnInjured( CTFBot *me, const CTakeDamageInfo &info )
{
return TryToSustain( RESULT_CRITICAL, "Ignoring injury" );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotMobRush::OnOtherKilled( CTFBot *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
{
return TryToSustain( RESULT_CRITICAL, "Ignoring friend death" );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotMobRush::OnStuck( CTFBot *me )
{
m_path.Invalidate();
return TryToSustain( RESULT_CRITICAL );
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotMobRush::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_NO;
}
#endif // TF_RAID_MODE
@@ -0,0 +1,42 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_mob_rush.h
// A member of a rushing mob of melee attackers
// Michael Booth, October 2009
#ifndef TF_BOT_MOB_RUSH_H
#define TF_BOT_MOB_RUSH_H
#ifdef TF_RAID_MODE
#include "Path/NextBotChasePath.h"
//-----------------------------------------------------------------------------
class CTFBotMobRush : public Action< CTFBot >
{
public:
CTFBotMobRush( CTFPlayer *victim, float reactionTime = 0.0f );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual EventDesiredResult< CTFBot > OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result = NULL );
virtual EventDesiredResult< CTFBot > OnInjured( CTFBot *me, const CTakeDamageInfo &info );
virtual EventDesiredResult< CTFBot > OnOtherKilled( CTFBot *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
QueryResultType ShouldRetreat( const INextBot *me ) const;
virtual const char *GetName( void ) const { return "MobRush"; };
private:
CHandle< CTFPlayer > m_victim;
CountdownTimer m_reactionTimer;
CountdownTimer m_tauntTimer;
CountdownTimer m_vocalizeTimer;
ChasePath m_path;
};
#endif // TF_RAID_MODE
#endif // TF_BOT_MOB_RUSH_H
@@ -0,0 +1,150 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_squad_attack.cpp
// Move and attack as a small, cohesive, group
// Michael Booth, October 2009
#include "cbase.h"
#ifdef TF_RAID_MODE
#include "team.h"
#include "raid/tf_raid_logic.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/raid/tf_bot_wander.h"
#include "bot/behavior/scenario/raid/tf_bot_squad_attack.h"
#include "bot/behavior/medic/tf_bot_medic_heal.h"
#include "bot/behavior/tf_bot_move_to_vantage_point.h"
ConVar tf_squad_radius( "tf_squad_radius", "200", FCVAR_CHEAT );
ConVar tf_squad_debug( "tf_squad_debug", "0", FCVAR_CHEAT );
ConVar tf_raid_squad_vocalize_min_interval( "tf_raid_squad_vocalize_min_interval", "5", FCVAR_CHEAT );
ConVar tf_raid_squad_vocalize_max_interval( "tf_raid_squad_vocalize_max_interval", "8", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSquadAttack::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_vocalizeTimer.Start( RandomFloat( tf_raid_squad_vocalize_min_interval.GetFloat(), tf_raid_squad_vocalize_max_interval.GetFloat() ) );
m_victim = NULL;
return Continue();
}
//---------------------------------------------------------------------------------------------
// the leader is the slowest member of the squad
CTFBot *CTFBotSquadAttack::GetSquadLeader( CTFBot *me ) const
{
CTFBot *leader = NULL;
float leaderSpeed = FLT_MAX;
CTFBotSquad *squad = me->GetSquad();
CTFBotSquad::Iterator it;
for( it = squad->GetFirstMember(); it != squad->InvalidIterator(); it = squad->GetNextMember( it ) )
{
CTFBot *bot = it();
float speed = bot->GetPlayerClass()->GetMaxSpeed();
if ( speed < leaderSpeed )
{
leader = bot;
leaderSpeed = speed;
}
}
return leader;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSquadAttack::Update( CTFBot *me, float interval )
{
if ( !me->IsInASquad() )
return Done( "Not in a squad" );
if ( me->IsPlayerClass( TF_CLASS_MEDIC ) )
{
return SuspendFor( new CTFBotMedicHeal );
}
CTFBot *leader = GetSquadLeader( me );
CTFBotPathCost cost( me, FASTEST_ROUTE );
if ( m_victim == NULL || m_victimConsiderTimer.IsElapsed() )
{
m_victimConsiderTimer.Start( 3.0f );
m_victim = TFGameRules()->GetRaidLogic()->SelectRaiderToAttack();
}
if ( m_victim )
{
const float engageRange = 500.0f;
if ( me->IsPlayerClass( TF_CLASS_PYRO ) ||
me->IsRangeGreaterThan( m_victim->GetAbsOrigin(), engageRange ) ||
!me->GetVisionInterface()->IsAbleToSee( m_victim, IVision::DISREGARD_FOV ) )
{
if ( me->IsSelf( leader ) || me->IsRangeLessThan( leader, tf_squad_radius.GetFloat() ) )
{
// chase down the enemy
m_chasePath.Update( me, m_victim, cost );
}
}
if ( !me->IsSelf( leader ) && me->IsRangeGreaterThan( leader, 1.25f * tf_squad_radius.GetFloat() ) )
{
// too far from leader - return to him
m_chasePath.Update( me, leader, cost );
}
if ( tf_squad_debug.GetBool() && me->IsSelf( leader ) )
{
NDebugOverlay::Circle( me->GetAbsOrigin(), 20.0f, 255, 255, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
CTFBotSquad *squad = me->GetSquad();
CTFBotSquad::Iterator it;
for( it = squad->GetFirstMember(); it != squad->InvalidIterator(); it = squad->GetNextMember( it ) )
{
CTFBot *bot = it();
if ( me->IsSelf( bot ) )
continue;
NDebugOverlay::Line( me->WorldSpaceCenter(), bot->WorldSpaceCenter(), 0, 255, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
}
}
if ( m_vocalizeTimer.IsElapsed() )
{
m_vocalizeTimer.Start( RandomFloat( tf_raid_squad_vocalize_min_interval.GetFloat(), tf_raid_squad_vocalize_max_interval.GetFloat() ) );
if ( me->IsPlayerClass( TF_CLASS_SCOUT ) )
me->EmitSound( "Scout.MobJabber" );
else if ( me->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
me->EmitSound( "Heavy.MobJabber" );
else
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSquadAttack::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_path.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotSquadAttack::OnStuck( CTFBot *me )
{
m_path.Invalidate();
return TryContinue();
}
#endif // TF_RAID_MODE
@@ -0,0 +1,47 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_squad_attack.h
// Move and attack as a small, cohesive, group
// Michael Booth, October 2009
#ifndef TF_BOT_SQUAD_ATTACK_H
#define TF_BOT_SQUAD_ATTACK_H
#ifdef TF_RAID_MODE
#include "Path/NextBotPathFollow.h"
#include "Path/NextBotChasePath.h"
//-----------------------------------------------------------------------------
class CTFBotSquadAttack : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
QueryResultType ShouldRetreat( const INextBot *me ) const;
virtual const char *GetName( void ) const { return "SquadPatrol"; };
private:
CountdownTimer m_vocalizeTimer;
PathFollower m_path;
ChasePath m_chasePath;
CHandle< CTFPlayer > m_victim;
CountdownTimer m_victimConsiderTimer;
CTFBot *GetSquadLeader( CTFBot *me ) const;
};
inline QueryResultType CTFBotSquadAttack::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_NO;
}
#endif // TF_RAID_MODE
#endif // TF_BOT_SQUAD_ATTACK_H
@@ -0,0 +1,175 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_wander.cpp
// Wanderering/idle enemies for Squad Co-op mode
// Michael Booth, October 2009
#include "cbase.h"
#ifdef TF_RAID_MODE
#include "team.h"
#include "raid/tf_raid_logic.h"
#include "bot/tf_bot.h"
#include "bot/behavior/scenario/raid/tf_bot_wander.h"
#include "bot/behavior/scenario/raid/tf_bot_mob_rush.h"
ConVar tf_raid_wanderer_aggro_range( "tf_raid_wanderer_aggro_range", "500", FCVAR_CHEAT, "If wanderers see a threat closer than this, they attack" );
ConVar tf_raid_wanderer_notice_friend_death_range( "tf_raid_wanderer_notice_friend_death_range", "1000", FCVAR_CHEAT, "If a friend dies within this radius of a wanderer, it wakes up and attacks the attacker" );
ConVar tf_raid_wanderer_reaction_factor( "tf_raid_wanderer_reaction_factor", "1", FCVAR_CHEAT );
ConVar tf_raid_wanderer_vocalize_min_interval( "tf_raid_wanderer_vocalize_min_interval", "20", FCVAR_CHEAT );
ConVar tf_raid_wanderer_vocalize_max_interval( "tf_raid_wanderer_vocalize_max_interval", "30", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
CTFBotWander::CTFBotWander( void )
{
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotWander::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_vocalizeTimer.Start( RandomFloat( tf_raid_wanderer_vocalize_min_interval.GetFloat(), tf_raid_wanderer_vocalize_max_interval.GetFloat() ) );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotWander::Update( CTFBot *me, float interval )
{
// mobs use only their melee weapons
CBaseCombatWeapon *meleeWeapon = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
if ( meleeWeapon )
{
me->Weapon_Switch( meleeWeapon );
}
CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE );
if ( me->HasAttribute( CTFBot::AGGRESSIVE ) )
{
// I'm a mob rusher - pick a random raider and attack them!
CTFPlayer *victim = TFGameRules()->GetRaidLogic()->SelectRaiderToAttack();
if ( victim )
{
return SuspendFor( new CTFBotMobRush( victim ), "Rushing a raider" );
}
}
else if ( m_visionTimer.IsElapsed() )
{
// I'm a wanderer - look for very nearby threats
m_visionTimer.Start( RandomFloat( 0.5f, 1.0f ) );
// find closest visible raider within aggro range
CTFPlayer *threat = NULL;
float closeThreatRangeSq = tf_raid_wanderer_aggro_range.GetFloat() * tf_raid_wanderer_aggro_range.GetFloat();
for( int i=0; i<raidingTeam->GetNumPlayers(); ++i )
{
CTFPlayer *player = (CTFPlayer *)raidingTeam->GetPlayer(i);
if ( !player->IsAlive() )
continue;
float rangeSq = me->GetRangeSquaredTo( player );
if ( rangeSq < closeThreatRangeSq )
{
if ( me->GetVisionInterface()->IsLineOfSightClearToEntity( player ) )
{
threat = player;
closeThreatRangeSq = rangeSq;
}
}
}
if ( threat )
{
return SuspendFor( new CTFBotMobRush( threat ), "Attacking threat!" );
}
}
if ( m_vocalizeTimer.IsElapsed() )
{
m_vocalizeTimer.Start( RandomFloat( tf_raid_wanderer_vocalize_min_interval.GetFloat(), tf_raid_wanderer_vocalize_max_interval.GetFloat() ) );
// mouth off
if ( me->IsPlayerClass( TF_CLASS_SCOUT ) )
me->EmitSound( "Scout.WanderJabber" );
else
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_JEERS );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotWander::OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result )
{
if ( other && other->IsPlayer() && me->IsEnemy( other ) )
{
return TrySuspendFor( new CTFBotMobRush( (CTFPlayer *)other ), RESULT_IMPORTANT, "Attacking threat who touched me!" );
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotWander::OnInjured( CTFBot *me, const CTakeDamageInfo &info )
{
if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() && me->IsEnemy( info.GetAttacker() ) )
{
return TrySuspendFor( new CTFBotMobRush( (CTFPlayer *)info.GetAttacker() ), RESULT_IMPORTANT, "Attacking threat who attacked me!" );
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotWander::OnOtherKilled( CTFBot *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
{
if ( victim && me->IsFriend( victim ) )
{
if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() && me->IsEnemy( info.GetAttacker() ) )
{
if ( me->IsRangeLessThan( victim, tf_raid_wanderer_notice_friend_death_range.GetFloat() ) )
{
if ( me->GetVisionInterface()->IsAbleToSee( victim, IVision::DISREGARD_FOV ) &&
me->GetVisionInterface()->IsAbleToSee( info.GetAttacker(), IVision::DISREGARD_FOV ) )
{
float rangeToAttacker = me->GetRangeTo( info.GetAttacker() );
float reactionTime;
if ( rangeToAttacker < tf_raid_wanderer_aggro_range.GetFloat() )
{
reactionTime = 0.0f;
}
else
{
reactionTime = tf_raid_wanderer_reaction_factor.GetFloat() * ( rangeToAttacker - tf_raid_wanderer_aggro_range.GetFloat() ) / tf_raid_wanderer_aggro_range.GetFloat();
}
return TrySuspendFor( new CTFBotMobRush( (CTFPlayer *)info.GetAttacker(), reactionTime ), RESULT_IMPORTANT, "Attacking my friend's attacker!" );
}
}
}
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotWander::OnCommandAttack( CTFBot *me, CBaseEntity *victim )
{
return TryContinue();
}
#endif // TF_RAID_MODE
@@ -0,0 +1,51 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_wander.h
// Wanderering/idle enemies for Squad Co-op mode
// Michael Booth, October 2009
#ifndef TF_BOT_WANDER_H
#define TF_BOT_WANDER_H
#ifdef TF_RAID_MODE
//-----------------------------------------------------------------------------
class CTFBotWander : public Action< CTFBot >
{
public:
CTFBotWander( void );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual EventDesiredResult< CTFBot > OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result = NULL );
virtual EventDesiredResult< CTFBot > OnInjured( CTFBot *me, const CTakeDamageInfo &info );
virtual EventDesiredResult< CTFBot > OnOtherKilled( CTFBot *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info );
virtual EventDesiredResult< CTFBot > OnCommandAttack( CTFBot *me, CBaseEntity *victim );
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual const char *GetName( void ) const { return "Wander"; };
private:
CountdownTimer m_visionTimer;
CountdownTimer m_vocalizeTimer;
};
inline QueryResultType CTFBotWander::ShouldHurry( const INextBot *me ) const
{
return ANSWER_YES;
}
inline QueryResultType CTFBotWander::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_NO;
}
#endif TF_RAID_MODE
#endif // TF_BOT_WANDER_H
@@ -0,0 +1,253 @@
//========= 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;
}
@@ -0,0 +1,37 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_sniper_attack.h
// Attack a threat as a Sniper
// Michael Booth, February 2009
#ifndef TF_BOT_SNIPER_ATTACK_H
#define TF_BOT_SNIPER_ATTACK_H
#include "Path/NextBotChasePath.h"
class CTFBotSniperAttack : public Action< CTFBot >
{
public:
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 );
void OnEnd( CTFBot *me, Action< CTFBot > *nextAction );
virtual ActionResult< CTFBot > OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual Vector SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const; // given a subject, return the world space position we should aim at
virtual const CKnownEntity * SelectMoreDangerousThreat( const INextBot *me,
const CBaseCombatCharacter *subject,
const CKnownEntity *threat1,
const CKnownEntity *threat2 ) const; // return the more dangerous of the two threats to 'subject', or NULL if we have no opinion
virtual const char *GetName( void ) const { return "SniperAttack"; };
private:
CountdownTimer m_lingerTimer;
bool IsImmediateThreat( const CBaseCombatCharacter *subject, const CKnownEntity *threat ) const;
};
#endif // TF_BOT_SNIPER_ATTACK_H
@@ -0,0 +1,628 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_sniper_lurk.h
// Move into position and wait for victims
// Michael Booth, October 2009
#include "cbase.h"
#include "tf_player.h"
#ifdef TF_RAID_MODE
#include "raid/tf_raid_logic.h"
#endif // TF_RAID_MODE
#include "bot/tf_bot.h"
#include "bot/behavior/sniper/tf_bot_sniper_lurk.h"
#include "bot/behavior/sniper/tf_bot_sniper_attack.h"
#include "bot/behavior/tf_bot_retreat_to_cover.h"
#include "bot/behavior/tf_bot_melee_attack.h"
#include "bot/map_entities/tf_bot_hint.h"
#include "nav_mesh.h"
extern ConVar tf_bot_path_lookahead_range;
extern ConVar tf_bot_sniper_flee_range;
extern ConVar tf_bot_sniper_melee_range;
extern ConVar tf_bot_debug_sniper;
extern float SkewedRandomValue( void );
ConVar tf_bot_sniper_patience_duration( "tf_bot_sniper_patience_duration", "10", FCVAR_CHEAT, "How long a Sniper bot will wait without seeing an enemy before picking a new spot" );
ConVar tf_bot_sniper_target_linger_duration( "tf_bot_sniper_target_linger_duration", "2", FCVAR_CHEAT, "How long a Sniper bot will keep toward at a target it just lost sight of" );
ConVar tf_bot_sniper_allow_opportunistic( "tf_bot_sniper_allow_opportunistic", "1", FCVAR_NONE, "If set, Snipers will stop on their way to their preferred lurking spot to snipe at opportunistic targets" );
ConVar tf_mvm_bot_sniper_target_by_dps( "tf_mvm_bot_sniper_target_by_dps", "1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "If set, Snipers in MvM mode target the victim that has the highest DPS" );
#ifdef STAGING_ONLY
extern ConVar tf_bot_use_items;
#endif
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSniperLurk::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_boredTimer.Start( RandomFloat( 0.9f, 1.1f ) * tf_bot_sniper_patience_duration.GetFloat() );
m_homePosition = me->GetAbsOrigin();
m_isHomePositionValid = false;
m_isAtHome = false;
m_failCount = 0;
m_isOpportunistic = tf_bot_sniper_allow_opportunistic.GetBool();
CTFBotHint *hint = NULL;
while( ( hint = (CTFBotHint *)( gEntList.FindEntityByClassname( hint, "func_tfbot_hint" ) ) ) != NULL )
{
if ( hint->IsA( CTFBotHint::HINT_SNIPER_SPOT ) )
{
m_hintVector.AddToTail( hint );
// make sure we don't yet own any of these hints
if ( me->IsSelf( hint->GetOwnerEntity() ) )
{
hint->SetOwnerEntity( NULL );
}
}
}
m_priorHint = NULL;
if ( TFGameRules()->IsMannVsMachineMode() && me->GetTeamNumber() == TF_TEAM_PVE_INVADERS )
{
// mann vs machine snipers shouldn't stop until they reach their home
//m_isOpportunistic = false;
// mann vs machine snipers should ignore the scenario and just snipe
me->SetMission( CTFBot::MISSION_SNIPER, MISSION_DOESNT_RESET_BEHAVIOR_SYSTEM );
}
#ifdef STAGING_ONLY
if ( tf_bot_use_items.GetInt() && ( RandomInt(0, 100) <= tf_bot_use_items.GetInt() ) )
{
CBaseCombatWeapon *myGun = me->Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
me->Weapon_Detach( myGun );
UTIL_Remove( myGun );
BotGenerateAndWearItem( me, "The Huntsman" );
}
#endif // STAGING_ONLY
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSniperLurk::Update( CTFBot *me, float interval )
{
#ifdef TF_RAID_MODE
if ( TFGameRules()->IsRaidMode() )
{
}
else
#endif
{
// continuously search for good sniping spots
me->AccumulateSniperSpots();
if ( !m_isHomePositionValid )
{
// just found our first sniper spot - update our home position
FindNewHome( me );
}
}
// aim at bad guys
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && !threat->GetEntity()->IsAlive() )
{
// he's dead
threat = NULL;
}
if ( threat && me->GetIntentionInterface()->ShouldAttack( me, threat ) == ANSWER_NO )
{
threat = NULL;
}
if ( threat && threat->IsVisibleInFOVNow() )
{
m_failCount = 0;
if ( me->IsDistanceBetweenLessThan( threat->GetLastKnownPosition(), tf_bot_sniper_melee_range.GetFloat() ) )
{
const float giveUpRange = 1.25f * tf_bot_sniper_melee_range.GetFloat();
return SuspendFor( new CTFBotMeleeAttack( giveUpRange ), "Melee attacking nearby threat" );
}
}
bool isSightingRifle = false;
if ( threat &&
threat->GetTimeSinceLastSeen() < tf_bot_sniper_target_linger_duration.GetFloat() &&
me->IsLineOfFireClear( threat->GetEntity() ) )
{
// we see something...
if ( m_isOpportunistic )
{
// switch to our sniper rifle
CBaseCombatWeapon *myGun = me->Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
if ( myGun )
{
me->Weapon_Switch( myGun );
}
isSightingRifle = true;
m_boredTimer.Reset();
if ( !m_isHomePositionValid )
{
// make this our opportunistic home for awhile
m_homePosition = me->GetAbsOrigin();
m_boredTimer.Start( RandomFloat( 0.9f, 1.1f ) * tf_bot_sniper_patience_duration.GetFloat() );
}
}
else
{
// switch to our SMG and fire while we run
CBaseCombatWeapon *myGun = me->Weapon_GetSlot( TF_WPN_TYPE_SECONDARY );
if ( myGun )
{
me->Weapon_Switch( myGun );
}
}
}
const float homeRange = 25.0f; // 100.0f;
m_isAtHome = ( me->GetAbsOrigin() - m_homePosition ).AsVector2D().IsLengthLessThan( homeRange );
if ( m_isAtHome )
{
isSightingRifle = true;
// once we've reached a good home spot, opportunistically attack from there
m_isOpportunistic = tf_bot_sniper_allow_opportunistic.GetBool();
if ( m_boredTimer.IsElapsed() )
{
++m_failCount;
if ( FindNewHome( me ) )
{
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_NEGATIVE );
m_boredTimer.Start( RandomFloat( 0.9f, 1.1f ) * tf_bot_sniper_patience_duration.GetFloat() );
}
else
{
// try again soon
m_boredTimer.Start( 1.0f );
}
}
}
else
{
// not yet at home - can't start to be bored
m_boredTimer.Reset();
}
if ( isSightingRifle )
{
// switch to our sniper rifle
CTFWeaponBase *myGun = (CTFWeaponBase *)me->Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
if ( myGun )
{
me->Weapon_Switch( myGun );
if ( !me->m_Shared.InCond( TF_COND_ZOOMED ) && !myGun->IsWeapon( TF_WEAPON_COMPOUND_BOW ) )
{
// zoom in and stand still
me->PressAltFireButton();
}
}
}
else
{
// move to our home position
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, m_homePosition, cost );
}
m_path.Update( me );
if ( me->m_Shared.InCond( TF_COND_ZOOMED ) )
{
me->PressAltFireButton();
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotSniperLurk::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
if ( me->m_Shared.InCond( TF_COND_ZOOMED ) )
{
// we're leaving to do something else - unzoom
me->PressAltFireButton();
}
if ( m_priorHint != NULL )
{
// release my hint
m_priorHint->SetOwnerEntity( NULL );
if ( tf_bot_debug_sniper.GetBool() )
{
DevMsg( "%3.2f: %s: Releasing hint.\n", gpGlobals->curtime, me->GetPlayerName() );
}
}
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSniperLurk::OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction )
{
if ( me->m_Shared.InCond( TF_COND_ZOOMED ) )
{
// we're leaving to do something else - unzoom
me->PressAltFireButton();
}
if ( m_priorHint != NULL )
{
// release my hint
m_priorHint->SetOwnerEntity( NULL );
if ( tf_bot_debug_sniper.GetBool() )
{
DevMsg( "%3.2f: %s: Releasing hint.\n", gpGlobals->curtime, me->GetPlayerName() );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSniperLurk::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_repathTimer.Invalidate();
m_priorHint = NULL;
// we probably just fetched some health because the enemy shot us - pick a new place to lurk
FindNewHome( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
bool CTFBotSniperLurk::FindHint( CTFBot *me )
{
// if any sniper spot hints exist, pick one of them
CUtlVector< CTFBotHint * > activeHintVector;
for( int i=0; i<m_hintVector.Count(); ++i )
{
if ( m_hintVector[i] != NULL && m_hintVector[i]->IsFor( me ) )
{
activeHintVector.AddToTail( m_hintVector[i] );
}
}
if ( activeHintVector.Count() == 0 )
{
return false;
}
if ( m_priorHint != NULL )
{
// release my hint
m_priorHint->SetOwnerEntity( NULL );
if ( tf_bot_debug_sniper.GetBool() )
{
DevMsg( "%3.2f: %s: Releasing hint.\n", gpGlobals->curtime, me->GetPlayerName() );
}
}
CTFBotHint *hint = NULL;
if ( m_priorHint != NULL && m_failCount < 2 )
{
// there used to be targets here - pick nearby hint
float nearRange = 500.0f;
CUtlVector< CTFBotHint * > nearHintVector;
for( int i=0; i<activeHintVector.Count(); ++i )
{
if ( activeHintVector[i] == m_priorHint )
continue;
if ( ( activeHintVector[i]->WorldSpaceCenter() - m_priorHint->WorldSpaceCenter() ).IsLengthGreaterThan( nearRange ) )
continue;
if ( activeHintVector[i]->GetOwnerEntity() != NULL )
continue;
nearHintVector.AddToTail( activeHintVector[i] );
}
if ( nearHintVector.Count() == 0 )
{
++m_failCount;
return false;
}
int whichHint = RandomInt( 0, nearHintVector.Count()-1 );
hint = nearHintVector[ whichHint ];
}
else
{
// picking either our first hint, or we haven't seen a victim in a long time - pick a hint that can actually see someone
CUtlVector< CTFPlayer * > victimVector;
CollectPlayers( &victimVector, GetEnemyTeam( me->GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS );
CUtlVector< CTFBotHint * > hotHintVector;
CUtlVector< CTFBotHint * > freeHintVector;
for( int i=0; i<activeHintVector.Count(); ++i )
{
if ( activeHintVector[i]->GetOwnerEntity() != NULL )
continue;
freeHintVector.AddToTail( activeHintVector[i] );
for( int p=0; p<victimVector.Count(); ++p )
{
if ( victimVector[p]->IsLineOfSightClear( activeHintVector[i]->WorldSpaceCenter(), CBaseCombatCharacter::IGNORE_ACTORS ) )
{
// at least one victim is visible from this hint
hotHintVector.AddToTail( activeHintVector[i] );
break;
}
}
}
if ( hotHintVector.Count() == 0 )
{
// no hints can see any victims - pick at random
if ( freeHintVector.Count() == 0 )
{
// all hints are owned by another sniper - double up
int whichHint = RandomInt( 0, activeHintVector.Count()-1 );
hint = activeHintVector[ whichHint ];
if ( tf_bot_debug_sniper.GetBool() )
{
DevMsg( "%3.2f: %s: No un-owned hints available! Doubling up.\n", gpGlobals->curtime, me->GetPlayerName() );
}
}
else
{
int whichHint = RandomInt( 0, freeHintVector.Count()-1 );
hint = freeHintVector[ whichHint ];
}
}
else
{
int whichHint = RandomInt( 0, hotHintVector.Count()-1 );
hint = hotHintVector[ whichHint ];
}
}
if ( hint == NULL )
{
return false;
}
Extent hintExtent;
hintExtent.Init( hint );
Vector hintSpot;
hintSpot.x = RandomFloat( hintExtent.lo.x, hintExtent.hi.x );
hintSpot.y = RandomFloat( hintExtent.lo.y, hintExtent.hi.y );
hintSpot.z = ( hintExtent.lo.z + hintExtent.hi.z ) / 2.0f;
TheNavMesh->GetSimpleGroundHeight( hintSpot, &hintSpot.z );
m_homePosition = hintSpot;
m_isHomePositionValid = true;
m_priorHint = hint;
// my hint
hint->SetOwnerEntity( me );
return true;
}
//---------------------------------------------------------------------------------------------
bool CTFBotSniperLurk::FindNewHome( CTFBot *me )
{
if ( !m_findHomeTimer.IsElapsed() )
{
return false;
}
m_findHomeTimer.Start( RandomFloat( 1.0f, 2.0f ) );
#ifdef TF_RAID_MODE
if ( TFGameRules()->IsRaidMode() )
{
// stay put for now
return true;
}
else
#endif // TF_RAID_MODE
{
// if any sniper spot hints exist, pick one of them
if ( FindHint( me ) )
{
return true;
}
// pick a sniper spot from our ongoing search
const CUtlVector< CTFBot::SniperSpotInfo > *sniperSpotVector = me->GetSniperSpots();
if ( sniperSpotVector->Count() > 0 )
{
m_homePosition = sniperSpotVector->Element( RandomInt( 0, sniperSpotVector->Count()-1 ) ).m_vantageSpot;
m_isHomePositionValid = true;
return true;
}
}
// can't find a real sniper spot - pick another goal that will get us out into the fray
m_isHomePositionValid = false;
// head toward the point
CTeamControlPoint *point = me->GetMyControlPoint();
if ( point && !point->IsLocked() )
{
const CUtlVector< CTFNavArea * > *pointAreaVector = TheTFNavMesh()->GetControlPointAreas( point->GetPointIndex() );
if ( pointAreaVector && pointAreaVector->Count() > 0 )
{
int which = RandomInt( 0, pointAreaVector->Count()-1 );
m_homePosition = pointAreaVector->Element( which )->GetRandomPoint();
return false;
}
}
// no available point at the moment - head toward the enemy spawn room and opportunistically snipe
CUtlVector< CTFNavArea * > enemySpawnThresholdVector;
TheTFNavMesh()->CollectSpawnRoomThresholdAreas( &enemySpawnThresholdVector, GetEnemyTeam( me->GetTeamNumber() ) );
if ( enemySpawnThresholdVector.Count() > 0 )
{
m_homePosition = enemySpawnThresholdVector[ RandomInt( 0, enemySpawnThresholdVector.Count()-1 ) ]->GetCenter();
}
else
{
m_homePosition = me->GetAbsOrigin();
}
return false;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSniperLurk::ShouldAttack( const INextBot *bot, const CKnownEntity *them ) const
{
CTFBot *me = (CTFBot *)bot->GetEntity();
CTFNavArea *area = me->GetLastKnownArea();
if ( TFGameRules()->IsMannVsMachineMode() && area && area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) )
{
// don't fire while in the spawn area
return ANSWER_NO;
}
// take the shot if you've got it
return ANSWER_YES;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSniperLurk::ShouldRetreat( const INextBot *me ) const
{
if ( TFGameRules()->IsMannVsMachineMode() && me->GetEntity()->GetTeamNumber() == TF_TEAM_PVE_INVADERS )
{
return ANSWER_NO;
}
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
// Return the more dangerous of the two threats to 'subject', or NULL if we have no opinion
const CKnownEntity *CTFBotSniperLurk::SelectMoreDangerousThreat( const INextBot *meBot,
const CBaseCombatCharacter *subject,
const CKnownEntity *threat1,
const CKnownEntity *threat2 ) const
{
if ( TFGameRules()->IsMannVsMachineMode() && tf_mvm_bot_sniper_target_by_dps.GetBool() )
{
CTFBot *me = ToTFBot( meBot->GetEntity() );
// If one threat is visible and the other not, always pick the visible one
if ( !threat1->IsVisibleRecently() )
{
if ( threat2->IsVisibleRecently() )
{
return threat2;
}
}
else if ( !threat2->IsVisibleRecently() )
{
return threat1;
}
// At this point, threat1 and threat2 are either both visible, or both not
CTFPlayer *playerThreat1 = ToTFPlayer( threat1->GetEntity() );
CTFPlayer *playerThreat2 = ToTFPlayer( threat2->GetEntity() );
if ( playerThreat1 && playerThreat2 )
{
float rangeSq1 = me->GetRangeSquaredTo( playerThreat1 );
float rangeSq2 = me->GetRangeSquaredTo( playerThreat2 );
if ( me->HasWeaponRestriction( CTFBot::MELEE_ONLY ) )
{
// Melee-only bots just use closest threat
if ( rangeSq1 < rangeSq2 )
{
return threat1;
}
return threat2;
}
// Very near threats are always immediately dangerous
const float nearbyRangeSq = 500.0f * 500.0f;
if ( rangeSq1 < nearbyRangeSq )
{
if ( rangeSq2 > nearbyRangeSq )
{
return threat1;
}
}
else if ( rangeSq2 < nearbyRangeSq )
{
return threat2;
}
// At this point, both threats are either both very near or both "far"
// Choose the threat that has the highest DPS
const int equalTolerance = 50;
if ( playerThreat1->GetDamagePerSecond() > playerThreat2->GetDamagePerSecond() + equalTolerance )
{
return threat1;
}
else if ( playerThreat2->GetDamagePerSecond() > playerThreat1->GetDamagePerSecond() + equalTolerance )
{
return threat2;
}
else
{
// approximately equal DPS, choose closest
if ( rangeSq1 < rangeSq2 )
{
return threat1;
}
return threat2;
}
}
}
// Use normal threat selection
return NULL;
}
@@ -0,0 +1,51 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_sniper_lurk.h
// Move into position and wait for victims
// Michael Booth, October 2009
#ifndef TF_BOT_SNIPER_LURK_H
#define TF_BOT_SNIPER_LURK_H
#include "Path/NextBotPathFollow.h"
class CTFBotHint;
class CTFBotSniperLurk : public Action< CTFBot >
{
public:
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 ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
// Snipers choose their targets a bit differently
virtual const CKnownEntity * SelectMoreDangerousThreat( const INextBot *me,
const CBaseCombatCharacter *subject,
const CKnownEntity *threat1,
const CKnownEntity *threat2 ) const; // return the more dangerous of the two threats to 'subject', or NULL if we have no opinion
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 "SniperLurk"; };
private:
CountdownTimer m_boredTimer;
CountdownTimer m_repathTimer;
PathFollower m_path;
int m_failCount;
Vector m_homePosition; // where we want to snipe from
bool m_isHomePositionValid;
bool m_isAtHome;
bool FindNewHome( CTFBot *me );
CountdownTimer m_findHomeTimer;
bool m_isOpportunistic;
CUtlVector< CHandle< CTFBotHint > > m_hintVector;
CHandle< CTFBotHint > m_priorHint;
bool FindHint( CTFBot *me );
};
#endif // TF_BOT_SNIPER_LURK_H
@@ -0,0 +1,412 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_attack.cpp
// Backstab or pistol, as appropriate
// Michael Booth, June 2010
#include "cbase.h"
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "bot/behavior/spy/tf_bot_spy_attack.h"
#include "bot/behavior/tf_bot_retreat_to_cover.h"
#include "bot/behavior/spy/tf_bot_spy_sap.h"
#include "nav_mesh.h"
extern ConVar tf_bot_path_lookahead_range;
ConVar tf_bot_spy_knife_range( "tf_bot_spy_knife_range", "300", FCVAR_CHEAT, "If threat is closer than this, prefer our knife" );
ConVar tf_bot_spy_change_target_range_threshold( "tf_bot_spy_change_target_range_threshold", "300", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
CTFBotSpyAttack::CTFBotSpyAttack( CTFPlayer *victim ) : m_path( ChasePath::LEAD_SUBJECT )
{
m_victim = victim;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyAttack::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
m_isCoverBlown = false;
if ( m_victim.Get() )
{
me->GetVisionInterface()->AddKnownEntity( m_victim );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyAttack::Update( CTFBot *me, float interval )
{
const CKnownEntity *threat = me->GetVisionInterface()->GetKnown( m_victim );
// opportunistically attack closer threat if they are much closer to us than our existing threat
const CKnownEntity *closestThreat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( !threat )
{
threat = closestThreat;
m_isCoverBlown = false;
if ( closestThreat )
{
m_victim = ToTFPlayer( closestThreat->GetEntity() );
}
}
else if ( closestThreat &&
closestThreat->GetEntity() &&
closestThreat != threat )
{
float rangeToCurrentThreat = me->GetRangeTo( threat->GetLastKnownPosition() );
float rangeToNewThreat = me->GetRangeTo( closestThreat->GetLastKnownPosition() );
if ( rangeToCurrentThreat - rangeToNewThreat > tf_bot_spy_change_target_range_threshold.GetFloat() )
{
if ( closestThreat->GetEntity()->IsPlayer() )
{
threat = closestThreat;
m_victim = ToTFPlayer( closestThreat->GetEntity() );
m_isCoverBlown = false;
}
}
}
if ( !threat || threat->IsObsolete() )
{
return Done( "No threat" );
}
CBaseObject *sapTarget = me->GetNearestKnownSappableTarget();
if ( sapTarget && me->IsEntityBetweenTargetAndSelf( sapTarget, threat->GetEntity() ) )
{
return ChangeTo( new CTFBotSpySap( sapTarget ), "Opportunistically sapping an enemy object between my victim and I" );
}
if ( me->IsAnyEnemySentryAbleToAttackMe() )
{
m_isCoverBlown = true;
CBaseCombatWeapon *myGun = me->Weapon_GetWeaponByType( TF_WPN_TYPE_PRIMARY );
me->Weapon_Switch( myGun );
return ChangeTo( new CTFBotRetreatToCover, "Escaping sentry fire!" );
}
CTFPlayer *playerThreat = ToTFPlayer( threat->GetEntity() );
if ( !playerThreat )
{
return Done( "Current 'threat' is not a player or a building?" );
}
// remember who we are attacking (in case we changed our minds)
m_victim = playerThreat;
// uncloak so we can attack
if ( me->m_Shared.IsStealthed() && m_decloakTimer.IsElapsed() )
{
me->PressAltFireButton();
m_decloakTimer.Start( 1.0f );
}
bool isKnifeFight = false;
if ( me->m_Shared.InCond( TF_COND_DISGUISED ) ||
me->m_Shared.InCond( TF_COND_DISGUISING ) ||
me->m_Shared.IsStealthed() )
{
isKnifeFight = true;
}
Vector playerThreatForward;
playerThreat->EyeVectors( &playerThreatForward );
Vector toPlayerThreat = playerThreat->GetAbsOrigin() - me->GetAbsOrigin();
float threatRange = toPlayerThreat.NormalizeInPlace();
float behindTolerance = 0.0f;
switch( me->GetDifficulty() )
{
case CTFBot::EASY: behindTolerance = 0.9f; break;
case CTFBot::NORMAL: behindTolerance = 0.7071f; break;
case CTFBot::HARD: behindTolerance = 0.2f; break;
case CTFBot::EXPERT: behindTolerance = 0.0f; break;
}
if ( TFGameRules()->IsMannVsMachineMode() )
{
behindTolerance = 0.7071f;
}
bool isBehindVictim = DotProduct( playerThreatForward, toPlayerThreat ) > behindTolerance;
// easy Spies always think they're in position to backstab
if ( me->GetDifficulty() == CTFBot::EASY )
{
isBehindVictim = true;
}
if ( threatRange < tf_bot_spy_knife_range.GetFloat() )
{
isKnifeFight = true;
}
else if ( threat->IsVisibleInFOVNow() && isBehindVictim )
{
// they are facing away from us - go for the backstab
isKnifeFight = true;
}
// does my threat know I'm a Spy?
if ( me->IsThreatAimingTowardMe( playerThreat, 0.99f ) && me->GetTimeSinceLastInjury( GetEnemyTeam( me->GetTeamNumber() ) ) < 1.0f )
{
m_isCoverBlown |= ( playerThreat->GetTimeSinceWeaponFired() < 0.25f );
}
if ( m_isCoverBlown ||
me->m_Shared.InCond( TF_COND_BURNING ) ||
me->m_Shared.InCond( TF_COND_URINE ) ||
me->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
me->m_Shared.InCond( TF_COND_BLEEDING ) )
{
isKnifeFight = false;
}
CBaseCombatWeapon *myGun = me->Weapon_GetWeaponByType( isKnifeFight ? TF_WPN_TYPE_MELEE : TF_WPN_TYPE_PRIMARY );
me->Weapon_Switch( myGun );
CTFWeaponBase *myWeapon = me->m_Shared.GetActiveTFWeapon();
bool isMovingTowardVictim = true;
if ( myWeapon && myWeapon->IsMeleeWeapon() )
{
if ( threat->IsVisibleInFOVNow() )
{
const float circleStrafeRange = 250.0f;
if ( threatRange < circleStrafeRange )
{
// we're close - aim our stab attack
me->GetBodyInterface()->AimHeadTowards( playerThreat, IBody::MANDATORY, 0.1f, NULL, "Aiming my stab!" );
if ( !isBehindVictim )
{
// circle around our victim to get behind them
Vector myForward;
me->EyeVectors( &myForward );
Vector cross;
CrossProduct( playerThreatForward, myForward, cross );
if ( cross.z < 0.0f )
{
me->PressRightButton();
}
else
{
me->PressLeftButton();
}
// don't continue to close in if we're already very close so we don't bump them and give ourselves away
if ( threatRange < 100.0f )
{
isMovingTowardVictim = false;
}
}
else if ( TFGameRules()->IsMannVsMachineMode() )
{
if ( m_chuckleTimer.IsElapsed() )
{
m_chuckleTimer.Start( 1.0f );
me->EmitSound( "Spy.MVM_Chuckle" );
}
}
}
if ( threatRange < me->GetDesiredAttackRange() )
{
// if we're still disguised, go for the backstab
if ( me->m_Shared.InCond( TF_COND_DISGUISED ) )
{
if ( isBehindVictim || m_isCoverBlown )
{
// we're behind them (or they're onto us) - backstab!
me->PressFireButton();
}
}
else
{
// we're exposed - stab! stab! stab!
me->PressFireButton();
}
}
}
}
else
{
// aim our pistol
me->GetBodyInterface()->AimHeadTowards( playerThreat, IBody::MANDATORY, 0.1f, NULL, "Aiming my pistol" );
}
if ( isMovingTowardVictim )
{
// pursue the threat. if not visible, go to the last known position
if ( !threat->IsVisibleRecently() ||
me->IsRangeGreaterThan( threat->GetEntity()->GetAbsOrigin(), me->GetDesiredAttackRange() ) ||
!me->IsLineOfFireClear( threat->GetEntity()->EyePosition() ) )
{
// if we're at the threat's last known position and he's still not visible, we lost him
if ( !threat->IsVisibleRecently() )
{
if ( me->IsRangeLessThan( threat->GetLastKnownPosition(), 20.0f ) )
{
me->GetVisionInterface()->ForgetEntity( threat->GetEntity() );
return Done( "I lost my target!" );
}
}
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Update( me, threat->GetEntity(), cost );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyAttack::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_victim = NULL;
m_path.Invalidate();
m_isCoverBlown = false;
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotSpyAttack::OnStuck( CTFBot *me )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotSpyAttack::OnInjured( CTFBot *me, const CTakeDamageInfo &info )
{
if ( me->IsEnemy( info.GetAttacker() ) )
{
if ( !me->m_Shared.InCond( TF_COND_DISGUISED ) )
{
// hurt by an enemy and exposed as a spy - flee!
m_isCoverBlown = true;
CBaseCombatWeapon *myGun = me->Weapon_GetWeaponByType( TF_WPN_TYPE_PRIMARY );
me->Weapon_Switch( myGun );
return TryChangeTo( new CTFBotRetreatToCover, RESULT_IMPORTANT, "Time to get out of here!" );
}
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotSpyAttack::OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result )
{
if ( me->IsEnemy( other ) && other->MyCombatCharacterPointer() )
{
if ( other->MyCombatCharacterPointer()->IsLookingTowards( me ) )
{
m_isCoverBlown = true;
}
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSpyAttack::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSpyAttack::ShouldHurry( const INextBot *me ) const
{
return ANSWER_YES;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSpyAttack::ShouldAttack( const INextBot *meBot, const CKnownEntity *them ) const
{
CTFBot *me = ToTFBot( meBot->GetEntity() );
if ( m_isCoverBlown ||
me->m_Shared.InCond( TF_COND_BURNING ) ||
me->m_Shared.InCond( TF_COND_URINE ) ||
me->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
me->m_Shared.InCond( TF_COND_BLEEDING ) )
{
// our cover is blown anyway
return ANSWER_YES;
}
return ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
// Use this to signal the enemy we are focusing on, so we dont avoid them
QueryResultType CTFBotSpyAttack::IsHindrance( const INextBot *me, CBaseEntity *blocker ) const
{
if ( blocker != IS_ANY_HINDRANCE_POSSIBLE )
{
if ( blocker && m_victim.Get() && blocker->entindex() == m_victim->entindex() )
{
// don't avoid this guy
return ANSWER_NO;
}
}
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
// Return the more dangerous of the two threats to 'subject', or NULL if we have no opinion
const CKnownEntity * CTFBotSpyAttack::SelectMoreDangerousThreat( const INextBot *meBot,
const CBaseCombatCharacter *subject,
const CKnownEntity *threat1,
const CKnownEntity *threat2 ) const
{
CTFBot *me = ToTFBot( meBot->GetEntity() );
if ( me->IsSelf( subject ) )
{
CTFWeaponBase *myWeapon = me->m_Shared.GetActiveTFWeapon();
if ( myWeapon && myWeapon->IsMeleeWeapon() )
{
// attack the closest victim with my knife
if ( me->GetRangeSquaredTo( threat1->GetEntity() ) < me->GetRangeSquaredTo( threat2->GetEntity() ) )
{
return threat1;
}
return threat2;
}
}
return NULL;
}
@@ -0,0 +1,49 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_attack.h
// Backstab or pistol, as appropriate
// Michael Booth, June 2010
#ifndef TF_BOT_SPY_ATTACK_H
#define TF_BOT_SPY_ATTACK_H
#include "Path/NextBotChasePath.h"
//-------------------------------------------------------------------------------
class CTFBotSpyAttack : public Action< CTFBot >
{
public:
CTFBotSpyAttack( CTFPlayer *victim );
virtual ~CTFBotSpyAttack() { }
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnInjured( CTFBot *me, const CTakeDamageInfo &info );
virtual EventDesiredResult< CTFBot > OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result = NULL );
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual QueryResultType ShouldAttack( const INextBot *meBot, const CKnownEntity *them ) const;
virtual QueryResultType IsHindrance( const INextBot *me, CBaseEntity *blocker ) const; // use this to signal the enemy we are focusing on, so we dont avoid them
virtual const CKnownEntity * SelectMoreDangerousThreat( const INextBot *me,
const CBaseCombatCharacter *subject,
const CKnownEntity *threat1,
const CKnownEntity *threat2 ) const; // return the more dangerous of the two threats to 'subject', or NULL if we have no opinion
virtual const char *GetName( void ) const { return "SpyAttack"; };
private:
CHandle< CTFPlayer > m_victim;
ChasePath m_path;
bool m_isCoverBlown;
CountdownTimer m_chuckleTimer;
CountdownTimer m_decloakTimer;
};
#endif // TF_BOT_SPY_ATTACK_H
@@ -0,0 +1,31 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_backstab.cpp
// Chase behind a victim and backstab them
// Michael Booth, June 2010
#include "cbase.h"
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "bot/behavior/spy/tf_bot_spy_backstab.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyBackstab::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyBackstab::Update( CTFBot *me, float interval )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSpyBackstab::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
return ANSWER_NO;
}
@@ -0,0 +1,24 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_backstab.h
// Chase behind a victim and backstab them
// Michael Booth, June 2010
#ifndef TF_BOT_SPY_BACKSTAB_H
#define TF_BOT_SPY_BACKSTAB_H
#include "Path/NextBotPathFollow.h"
class CTFBotSpyBackstab : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
virtual const char *GetName( void ) const { return "SpyBackstab"; };
private:
};
#endif // TF_BOT_SPY_BACKSTAB_H
@@ -0,0 +1,31 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_escape.cpp
// Flee!
// Michael Booth, June 2010
#include "cbase.h"
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "bot/behavior/spy/tf_bot_spy_escape.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyEscape::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyEscape::Update( CTFBot *me, float interval )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSpyEscape::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
return ANSWER_NO;
}
@@ -0,0 +1,24 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_escape.h
// Flee!
// Michael Booth, June 2010
#ifndef TF_BOT_SPY_ESCAPE_H
#define TF_BOT_SPY_ESCAPE_H
#include "Path/NextBotPathFollow.h"
class CTFBotSpyEscape : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
virtual const char *GetName( void ) const { return "SpyEscape"; };
private:
};
#endif // TF_BOT_SPY_ESCAPE_H
@@ -0,0 +1,236 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_hide.cpp
// Move to a hiding spot
// Michael Booth, September 2011
#include "cbase.h"
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "bot/behavior/spy/tf_bot_spy_hide.h"
#include "bot/behavior/spy/tf_bot_spy_lurk.h"
#include "bot/behavior/spy/tf_bot_spy_attack.h"
//---------------------------------------------------------------------------------------------
CTFBotSpyHide::CTFBotSpyHide( CTFPlayer *victim )
{
m_initialVictim = victim;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyHide::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_hidingSpot = NULL;
m_findTimer.Invalidate();
m_isAtGoal = false;
CTFNavArea *myArea = me->GetLastKnownArea();
int enemyTeam = GetEnemyTeam( me->GetTeamNumber() );
m_incursionThreshold = myArea ? myArea->GetIncursionDistance( enemyTeam ) : FLT_MAX;
if ( m_incursionThreshold < 0.0f )
{
m_incursionThreshold = FLT_MAX;
}
m_talkTimer.Start( RandomFloat( 5.0f, 10.0f ) );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyHide::Update( CTFBot *me, float interval )
{
if ( m_initialVictim != NULL && !me->GetVisionInterface()->IsIgnored( m_initialVictim ) )
{
return SuspendFor( new CTFBotSpyAttack( m_initialVictim ), "Going after our initial victim" );
}
// go after victims we've gotten behind
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->GetTimeSinceLastKnown() < 3.0f )
{
CTFPlayer *victim = ToTFPlayer( threat->GetEntity() );
if ( victim )
{
const float attackRange = 750.0f;
if ( me->IsRangeLessThan( victim, attackRange ) )
{
if ( !victim->IsLookingTowards( me ) || victim->IsFiringWeapon() )
{
return SuspendFor( new CTFBotSpyAttack( victim ), "Opportunistic attack or self defense!" );
}
}
}
}
if ( m_talkTimer.IsElapsed() )
{
m_talkTimer.Start( RandomFloat( 5.0f, 10.0f ) );
me->EmitSound( "Spy.TeaseVictim" );
}
if ( m_isAtGoal )
{
// Quiet everyone! We are hiding now!
CTFNavArea *myArea = me->GetLastKnownArea();
if ( myArea )
{
int enemyTeam = GetEnemyTeam( me->GetTeamNumber() );
m_incursionThreshold = myArea->GetIncursionDistance( enemyTeam );
}
return SuspendFor( new CTFBotSpyLurk, "Reached hiding spot - lurking" );
}
if ( m_hidingSpot == NULL && m_findTimer.IsElapsed() )
{
FindHidingSpot( me );
}
// move to our hiding spot
m_path.Update( me );
// path following may invalidate our hiding spot (OnMoveToFailure())
if ( m_hidingSpot == NULL )
{
return Continue();
}
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 0.3f, 0.5f ) );
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, m_hidingSpot->GetPosition(), cost );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyHide::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_hidingSpot = NULL;
m_isAtGoal = false;
m_initialVictim = NULL;
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotSpyHide::OnMoveToSuccess( CTFBot *me, const Path *path )
{
m_isAtGoal = true;
return TryContinue( RESULT_CRITICAL );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotSpyHide::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
m_hidingSpot = NULL;
m_isAtGoal = false;
return TryContinue( RESULT_IMPORTANT );
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSpyHide::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
return ANSWER_NO;
}
struct IncursionEntry_t
{
int team;
CTFNavArea *area;
};
//---------------------------------------------------------------------------------------------
class SpyHideIncursionDistanceLess
{
public:
bool Less( const IncursionEntry_t &src1, const IncursionEntry_t &src2, void *pCtx )
{
return src1.area->GetIncursionDistance( src1.team ) < src2.area->GetIncursionDistance( src2.team );
}
};
//---------------------------------------------------------------------------------------------
bool CTFBotSpyHide::FindHidingSpot( CTFBot *me )
{
CTFNavArea *myArea = me->GetLastKnownArea();
if ( !myArea )
{
return false;
}
m_hidingSpot = NULL;
// find a spot to hide
const float maxRange = 3500.0f;
CUtlVector< CNavArea * > nearbyVector;
CollectSurroundingAreas( &nearbyVector, me->GetLastKnownArea(), maxRange,
500.0f, 500.0f );
CUtlSortVector< IncursionEntry_t, SpyHideIncursionDistanceLess > hidingSpotVector;
float maxIncursion = m_incursionThreshold + 1000.0f;
int enemyTeam = GetEnemyTeam( me->GetTeamNumber() );
// if we are standing in an area the defenders can't reach, don't limit
if ( myArea->GetIncursionDistance( enemyTeam ) < 0.0f )
{
maxIncursion = 9999999;
}
for( int i=0; i<nearbyVector.Count(); ++i )
{
CTFNavArea *area = (CTFNavArea *)nearbyVector[i];
if ( area->GetHidingSpots()->Count() <= 0 )
{
continue;
}
if ( area->GetIncursionDistance( enemyTeam ) < 0 )
{
continue;
}
// keep pushing inwards towards defender's spawn
if ( area->GetIncursionDistance( enemyTeam ) > maxIncursion )
{
continue;
}
IncursionEntry_t entry = { enemyTeam, area };
hidingSpotVector.Insert( entry );
}
if ( hidingSpotVector.Count() <= 0 )
{
return false;
}
// penetrate as far as we can
int which = RandomInt( 0, hidingSpotVector.Count()/2 );
CTFNavArea *whichArea = hidingSpotVector[ which ].area;
const HidingSpotVector *hidingSpots = whichArea->GetHidingSpots();
m_hidingSpot = hidingSpots->Element( RandomInt( 0, hidingSpots->Count()-1 ) );
return true;
}
@@ -0,0 +1,45 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_hide.h
// Move to a hiding spot
// Michael Booth, September 2011
#ifndef TF_BOT_SPY_HIDE
#define TF_BOT_SPY_HIDE
#include "Path/NextBotPathFollow.h"
class CTFBotSpyHide : public Action< CTFBot >
{
public:
CTFBotSpyHide( CTFPlayer *victim = NULL );
virtual ~CTFBotSpyHide() { }
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
virtual const char *GetName( void ) const { return "SpyHide"; };
private:
CHandle< CTFPlayer > m_initialVictim;
HidingSpot *m_hidingSpot;
bool FindHidingSpot( CTFBot *me );
CountdownTimer m_findTimer;
PathFollower m_path;
CountdownTimer m_repathTimer;
bool m_isAtGoal;
float m_incursionThreshold;
CountdownTimer m_talkTimer;
};
#endif // TF_BOT_SPY_HIDE
@@ -0,0 +1,335 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_infiltrate.cpp
// Move into position behind enemy lines and wait for victims
// Michael Booth, June 2010
#include "cbase.h"
#include "tf_player.h"
#include "tf_obj_sentrygun.h"
#include "bot/tf_bot.h"
#include "bot/behavior/spy/tf_bot_spy_infiltrate.h"
#include "bot/behavior/spy/tf_bot_spy_sap.h"
#include "bot/behavior/spy/tf_bot_spy_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_debug_spy( "tf_bot_debug_spy", "0", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyInfiltrate::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_hideArea = NULL;
m_hasEnteredCombatZone = false;
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyInfiltrate::Update( CTFBot *me, float interval )
{
// switch to our pistol
CBaseCombatWeapon *myGun = me->Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
if ( myGun )
{
me->Weapon_Switch( myGun );
}
CTFNavArea *myArea = me->GetLastKnownArea();
if ( !myArea )
{
return Continue();
}
bool isInMySpawn = myArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED );
if ( myArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_EXIT ) )
{
// don't count exits so we cloak as we leave
isInMySpawn = false;
}
// cloak when we first enter an area of active combat
if ( !me->m_Shared.IsStealthed() &&
!isInMySpawn &&
myArea->IsInCombat() &&
!m_hasEnteredCombatZone )
{
m_hasEnteredCombatZone = true;
me->PressAltFireButton();
}
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->GetEntity() && threat->GetEntity()->IsBaseObject() )
{
CBaseObject *enemyObject = (CBaseObject *)threat->GetEntity();
if ( !enemyObject->HasSapper() && me->IsEnemy( enemyObject ) )
{
return SuspendFor( new CTFBotSpySap( enemyObject ), "Sapping an enemy object" );
}
}
if ( me->GetEnemySentry() && !me->GetEnemySentry()->HasSapper() )
{
return SuspendFor( new CTFBotSpySap( me->GetEnemySentry() ), "Sapping a Sentry" );
}
if ( !m_hideArea && m_findHidingSpotTimer.IsElapsed() )
{
FindHidingSpot( me );
m_findHidingSpotTimer.Start( 3.0f );
}
if ( !TFGameRules()->InSetup() )
{
// go after victims we've gotten behind
if ( threat && threat->GetTimeSinceLastKnown() < 3.0f )
{
CTFPlayer *victim = ToTFPlayer( threat->GetEntity() );
if ( victim )
{
CTFNavArea *victimArea = (CTFNavArea *)victim->GetLastKnownArea();
if ( victimArea )
{
int victimTeam = victim->GetTeamNumber();
if ( victimArea->GetIncursionDistance( victimTeam ) > myArea->GetIncursionDistance( victimTeam ) )
{
if ( me->m_Shared.IsStealthed() )
{
return SuspendFor( new CTFBotRetreatToCover( new CTFBotSpyAttack( victim ) ), "Hiding to decloak before going after a backstab victim" );
}
else
{
return SuspendFor( new CTFBotSpyAttack( victim ), "Going after a backstab victim" );
}
}
}
}
}
}
if ( m_hideArea )
{
if ( tf_bot_debug_spy.GetBool() )
{
m_hideArea->DrawFilled( 255, 255, 0, 255, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
if ( myArea == m_hideArea )
{
// stay hidden during setup time
if ( TFGameRules()->InSetup() )
{
m_waitTimer.Start( RandomFloat( 0.0f, 5.0f ) );
}
else
{
// wait in our hiding spot for a bit, then try another
if ( !m_waitTimer.HasStarted() )
{
m_waitTimer.Start( RandomFloat( 5.0f, 10.0f ) );
}
else if ( m_waitTimer.IsElapsed() )
{
// time to find a new hiding spot
m_hideArea = NULL;
}
}
}
else
{
// move to our ambush position
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
// we may not be able to path to our hiding spot, but get as close as we can
// (dropdown mid spawn in cp_gorge)
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, m_hideArea->GetCenter(), cost );
}
m_path.Update( me );
m_waitTimer.Invalidate();
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotSpyInfiltrate::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyInfiltrate::OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyInfiltrate::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_repathTimer.Invalidate();
m_hideArea = NULL;
return Continue();
}
//---------------------------------------------------------------------------------------------
bool CTFBotSpyInfiltrate::FindHidingSpot( CTFBot *me )
{
m_hideArea = NULL;
if ( me->GetAliveDuration() < 5.0f && TFGameRules()->InSetup() )
{
// wait a bit until the nav mesh has updated itself
return false;
}
int myTeam = me->GetTeamNumber();
const CUtlVector< CTFNavArea * > *enemySpawnExitVector = TheTFNavMesh()->GetSpawnRoomExitAreas( GetEnemyTeam( myTeam ) );
#ifdef TF_RAID_MODE
if ( TFGameRules()->IsRaidMode() )
{
// for now, just lurk where we are
return false;
}
#endif
if ( !enemySpawnExitVector || enemySpawnExitVector->Count() == 0 )
{
if ( tf_bot_debug_spy.GetBool() )
{
DevMsg( "%3.2f: No enemy spawn room exit areas found\n", gpGlobals->curtime );
}
return false;
}
// find nearby place to hide hear enemy spawn exit(s)
CUtlVector< CNavArea * > nearbyAreaVector;
const float nearbyHideRange = 2500.0f;
for( int x=0; x<enemySpawnExitVector->Count(); ++x )
{
CTFNavArea *enemySpawnExitArea = enemySpawnExitVector->Element( x );
CUtlVector< CNavArea * > nearbyThisExitAreaVector;
CollectSurroundingAreas( &nearbyThisExitAreaVector, enemySpawnExitArea, nearbyHideRange, me->GetLocomotionInterface()->GetStepHeight(), me->GetLocomotionInterface()->GetStepHeight() );
// concat vectors (assuming N^2 unique search would cost more than ripping through some duplicates)
nearbyAreaVector.AddVectorToTail( nearbyThisExitAreaVector );
}
// find area not visible to any enemy spawn exits
CUtlVector< CTFNavArea * > hideAreaVector;
int i;
for( i=0; i<nearbyAreaVector.Count(); ++i )
{
CTFNavArea *area = (CTFNavArea *)nearbyAreaVector[i];
if ( !me->GetLocomotionInterface()->IsAreaTraversable( area ) )
continue;
bool isHidden = true;
for( int j=0; j<enemySpawnExitVector->Count(); ++j )
{
if ( area->IsPotentiallyVisible( enemySpawnExitVector->Element(j) ) )
{
isHidden = false;
break;
}
}
if ( isHidden )
{
hideAreaVector.AddToTail( area );
}
}
if ( hideAreaVector.Count() == 0 )
{
if ( tf_bot_debug_spy.GetBool() )
{
DevMsg( "%3.2f: Can't find any non-visible hiding areas, trying for anything near the spawn exit...\n", gpGlobals->curtime );
}
for( i=0; i<nearbyAreaVector.Count(); ++i )
{
CTFNavArea *area = (CTFNavArea *)nearbyAreaVector[i];
if ( !me->GetLocomotionInterface()->IsAreaTraversable( area ) )
continue;
hideAreaVector.AddToTail( area );
}
}
if ( hideAreaVector.Count() == 0 )
{
if ( tf_bot_debug_spy.GetBool() )
{
DevMsg( "%3.2f: Can't find any areas near the enemy spawn exit - just heading to the enemy spawn and hoping...\n", gpGlobals->curtime );
}
m_hideArea = enemySpawnExitVector->Element( RandomInt( 0, enemySpawnExitVector->Count()-1 ) );
return false;
}
// pick a specific hiding spot
m_hideArea = hideAreaVector[ RandomInt( 0, hideAreaVector.Count()-1 ) ];
return true;
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotSpyInfiltrate::OnStuck( CTFBot *me )
{
m_hideArea = NULL;
m_findHidingSpotTimer.Invalidate();
return TryContinue( RESULT_TRY );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotSpyInfiltrate::OnTerritoryCaptured( CTFBot *me, int territoryID )
{
// enemy spawn likely changed - find new hiding spot after internal data has updated
m_hideArea = NULL;
m_findHidingSpotTimer.Start( 5.0f );
return TryContinue( RESULT_TRY );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotSpyInfiltrate::OnTerritoryLost( CTFBot *me, int territoryID )
{
// enemy spawn likely changed - find new hiding spot after internal data has updated
m_hideArea = NULL;
m_findHidingSpotTimer.Start( 5.0f );
return TryContinue( RESULT_TRY );
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSpyInfiltrate::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
return ANSWER_NO;
}
@@ -0,0 +1,42 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_infiltrate.h
// Move into position behind enemy lines and wait for victims
// Michael Booth, June 2010
#ifndef TF_BOT_SPY_INFILTRATE_H
#define TF_BOT_SPY_INFILTRATE_H
#include "Path/NextBotPathFollow.h"
class CTFBotSpyInfiltrate : public Action< CTFBot >
{
public:
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 ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
virtual const char *GetName( void ) const { return "SpyInfiltrate"; };
private:
CountdownTimer m_repathTimer;
PathFollower m_path;
CTFNavArea *m_hideArea;
bool FindHidingSpot( CTFBot *me );
CountdownTimer m_findHidingSpotTimer;
CountdownTimer m_waitTimer;
bool m_hasEnteredCombatZone;
};
#endif // TF_BOT_SPY_INFILTRATE_H
@@ -0,0 +1,160 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_leave_spawn_room.cpp
// Assume the enemy is watching our spawn - escape it
// Michael Booth, September 2011
#include "cbase.h"
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "bot/behavior/spy/tf_bot_spy_leave_spawn_room.h"
#include "bot/behavior/spy/tf_bot_spy_hide.h"
extern bool IsSpaceToSpawnHere( const Vector &where );
//---------------------------------------------------------------------------------------------
bool TeleportNearVictim( CTFBot *me, CTFPlayer *victim, int attempt )
{
VPROF_BUDGET( "CTFBotSpyLeaveSpawnRoom::TeleportNearVictim", "NextBot" );
if ( !victim )
{
return false;
}
CUtlVector< CTFNavArea * > ambushVector; // vector of hidden but near-to-victim areas
if ( !victim->GetLastKnownArea() )
{
return false;
}
const float maxSurroundTravelRange = 6000.0f;
float surroundTravelRange = 1500.0f + 500.0f * attempt;
if ( surroundTravelRange > maxSurroundTravelRange )
{
surroundTravelRange = maxSurroundTravelRange;
}
CUtlVector< CNavArea * > areaVector;
// collect walkable areas surrounding this victim
CollectSurroundingAreas( &areaVector, victim->GetLastKnownArea(), surroundTravelRange, StepHeight, StepHeight );
// keep subset that isn't visible to the victim's team
for( int j=0; j<areaVector.Count(); ++j )
{
CTFNavArea *area = (CTFNavArea *)areaVector[j];
if ( !area->IsValidForWanderingPopulation() )
{
continue;
}
if ( area->IsPotentiallyVisibleToTeam( victim->GetTeamNumber() ) )
{
continue;
}
ambushVector.AddToTail( area );
}
if ( ambushVector.Count() == 0 )
{
return false;
}
int maxTries = MIN( 10, ambushVector.Count() );
for( int retry=0; retry<maxTries; ++retry )
{
int which = RandomInt( 0, ambushVector.Count()-1 );
Vector where = ambushVector[ which ]->GetCenter() + Vector( 0, 0, StepHeight );
if ( IsSpaceToSpawnHere( where ) )
{
me->Teleport( &where, &vec3_angle, &vec3_origin );
return true;
}
}
return false;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyLeaveSpawnRoom::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
// disguise as enemy team
me->DisguiseAsMemberOfEnemyTeam();
// cloak
me->PressAltFireButton();
// wait a few moments to guarantee a minimum time between announcing Spies and their attack
m_waitTimer.Start( 2.0f + RandomFloat( 0.0f, 1.0f ) );
m_attempt = 0;
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyLeaveSpawnRoom::Update( CTFBot *me, float interval )
{
VPROF_BUDGET( "CTFBotSpyLeaveSpawnRoom::Update", "NextBot" );
if ( m_waitTimer.IsElapsed() )
{
CTFPlayer *victim = NULL;
CUtlVector< CTFPlayer * > enemyVector;
CollectPlayers( &enemyVector, GetEnemyTeam( me->GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS );
// randomly shuffle our enemies
CUtlVector< CTFPlayer * > shuffleVector;
shuffleVector = enemyVector;
int n = shuffleVector.Count();
while( n > 1 )
{
int k = RandomInt( 0, n-1 );
n--;
CTFPlayer *tmp = shuffleVector[n];
shuffleVector[n] = shuffleVector[k];
shuffleVector[k] = tmp;
}
for( int i=0; i<shuffleVector.Count(); ++i )
{
if ( TeleportNearVictim( me, shuffleVector[i], m_attempt ) )
{
victim = shuffleVector[i];
break;
}
}
// if we didn't find a victim, try again in a bit
if ( !victim )
{
m_waitTimer.Start( 1.0f );
++m_attempt;
return Continue();
}
return ChangeTo( new CTFBotSpyHide( victim ), "Hiding!" );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSpyLeaveSpawnRoom::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
return ANSWER_NO;
}
@@ -0,0 +1,26 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_leave_spawn_room.h
// Assume the enemy is watching our spawn - escape it
// Michael Booth, September 2011
#ifndef TF_BOT_LEAVE_SPAWN_ROOM_H
#define TF_BOT_LEAVE_SPAWN_ROOM_H
#include "Path/NextBotPathFollow.h"
class CTFBotSpyLeaveSpawnRoom : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
virtual const char *GetName( void ) const { return "SpyLeaveSpawnRoom"; };
private:
CountdownTimer m_waitTimer;
int m_attempt;
};
#endif // TF_BOT_LEAVE_SPAWN_ROOM_H
@@ -0,0 +1,91 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_lurk.cpp
// Wait for victims
// Michael Booth, September 2011
#include "cbase.h"
#include "tf_player.h"
#include "tf_obj_sentrygun.h"
#include "bot/tf_bot.h"
#include "bot/behavior/spy/tf_bot_spy_lurk.h"
#include "bot/behavior/spy/tf_bot_spy_sap.h"
#include "bot/behavior/spy/tf_bot_spy_attack.h"
#include "bot/behavior/tf_bot_retreat_to_cover.h"
#include "bot/behavior/spy/tf_bot_spy_sap.h"
#include "nav_mesh.h"
extern ConVar tf_bot_path_lookahead_range;
extern ConVar tf_bot_debug_spy;
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyLurk::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
// cloak
if ( !me->m_Shared.IsStealthed() )
{
me->PressAltFireButton();
}
// disguise as the enemy team
me->DisguiseAsMemberOfEnemyTeam();
m_lurkTimer.Start( RandomFloat( 3.0f, 5.0f ) );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpyLurk::Update( CTFBot *me, float interval )
{
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->GetEntity() )
{
CBaseObject *enemyObject = dynamic_cast< CBaseObject * >( threat->GetEntity() );
if ( enemyObject && !enemyObject->HasSapper() && me->IsEnemy( enemyObject ) )
{
return SuspendFor( new CTFBotSpySap( enemyObject ), "Sapping an enemy object" );
}
}
if ( me->GetEnemySentry() != NULL && !me->GetEnemySentry()->HasSapper() )
{
return SuspendFor( new CTFBotSpySap( me->GetEnemySentry() ), "Sapping a Sentry" );
}
if ( m_lurkTimer.IsElapsed() )
{
return Done( "Lost patience with my hiding spot" );
}
CTFNavArea *myArea = me->GetLastKnownArea();
if ( !myArea )
{
return Continue();
}
// go after victims we've gotten behind
if ( threat && threat->GetTimeSinceLastKnown() < 3.0f )
{
CTFPlayer *victim = ToTFPlayer( threat->GetEntity() );
if ( victim )
{
if ( !victim->IsLookingTowards( me ) )
{
return ChangeTo( new CTFBotSpyAttack( victim ), "Going after a backstab victim" );
}
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSpyLurk::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
return ANSWER_NO;
}
@@ -0,0 +1,26 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_lurk.h
// Wait for victims
// Michael Booth, September 2011
#ifndef TF_BOT_SPY_LURK_H
#define TF_BOT_SPY_LURK_H
#include "Path/NextBotPathFollow.h"
class CTFBotSpyLurk : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
virtual const char *GetName( void ) const { return "SpyLurk"; };
private:
CountdownTimer m_lurkTimer;
};
#endif // TF_BOT_SPY_LURK_H
@@ -0,0 +1,250 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_sap.cpp
// Sap nearby enemy buildings
// Michael Booth, June 2010
#include "cbase.h"
#include "tf_player.h"
#include "tf_obj_sentrygun.h"
#include "bot/tf_bot.h"
#include "bot/behavior/spy/tf_bot_spy_sap.h"
#include "bot/behavior/tf_bot_approach_object.h"
#include "bot/behavior/spy/tf_bot_spy_attack.h"
extern ConVar tf_bot_path_lookahead_range;
extern ConVar tf_bot_debug_spy;
//---------------------------------------------------------------------------------------------
CTFBotSpySap::CTFBotSpySap( CBaseObject *sapTarget )
{
m_sapTarget = sapTarget;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpySap::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
me->StopLookingAroundForEnemies();
// uncloak so we can sap
if ( me->m_Shared.IsStealthed() )
{
me->PressAltFireButton();
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpySap::Update( CTFBot *me, float interval )
{
CBaseObject *newSapTarget = me->GetNearestKnownSappableTarget();
if ( newSapTarget )
{
m_sapTarget = newSapTarget;
}
if ( m_sapTarget == NULL )
{
return Done( "Sap target gone" );
}
CTFPlayer *victim = NULL;
CUtlVector< CKnownEntity > knownVector;
me->GetVisionInterface()->CollectKnownEntities( &knownVector );
for( int i=0; i<knownVector.Count(); ++i )
{
CTFPlayer *playerThreat = ToTFPlayer( knownVector[i].GetEntity() );
if ( playerThreat && me->IsEnemy( playerThreat ) )
{
victim = playerThreat;
break;
}
}
// opportunistic backstab if engineer is between me and my sap target
if ( victim && victim->IsPlayerClass( TF_CLASS_ENGINEER ) )
{
const float nearbyRange = 150.0f;
if ( m_sapTarget->GetOwner() == victim && me->IsRangeLessThan( victim, nearbyRange ) )
{
if ( me->IsEntityBetweenTargetAndSelf( victim, m_sapTarget ) )
{
return SuspendFor( new CTFBotSpyAttack( victim ), "Backstabbing the engineer before I sap his buildings" );
}
}
}
const float sapRange = 40.0f;
if ( me->IsRangeLessThan( m_sapTarget, 2.0f * sapRange ) )
{
// switch to our sapper and spam it
CBaseCombatWeapon *mySapper = me->Weapon_GetWeaponByType( TF_WPN_TYPE_BUILDING );
if ( !mySapper )
{
return Done( "I have no sapper" );
}
me->Weapon_Switch( mySapper );
// uncloak
if ( me->m_Shared.IsStealthed() )
{
me->PressAltFireButton();
}
// sap our target
me->GetBodyInterface()->AimHeadTowards( m_sapTarget, IBody::MANDATORY, 0.1f, NULL, "Aiming my sapper" );
me->PressFireButton();
}
if ( me->IsRangeGreaterThan( m_sapTarget, sapRange ) )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
if ( m_path.Compute( me, m_sapTarget, cost ) == false )
{
return Done( "No path to sap target!" );
}
}
m_path.Update( me );
return Continue();
}
// if our target is sapped, look for other nearby buildings to sap
if ( m_sapTarget->HasSapper() )
{
CBaseObject *nextTarget = me->GetNearestKnownSappableTarget();
if ( nextTarget )
{
m_sapTarget = nextTarget;
}
else
{
// everything is sapped - explicitly attack nearby enemy Engineers
if ( victim && victim->IsPlayerClass( TF_CLASS_ENGINEER ) )
{
return SuspendFor( new CTFBotSpyAttack( victim ), "Attacking an engineer" );
}
return Done( "All targets sapped" );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotSpySap::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
me->StartLookingAroundForEnemies();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpySap::OnSuspend( CTFBot *me, Action< CTFBot > *interruptingAction )
{
me->StartLookingAroundForEnemies();
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotSpySap::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
me->StopLookingAroundForEnemies();
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotSpySap::OnStuck( CTFBot *me )
{
return TryDone( RESULT_CRITICAL, "I'm stuck, probably on a sapped building that hasn't exploded yet" );
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSpySap::ShouldAttack( const INextBot *meBot, const CKnownEntity *them ) const
{
CTFBot *me = ToTFBot( meBot->GetEntity() );
if ( m_sapTarget && !m_sapTarget->HasSapper() )
{
// mission not accomplished
return ANSWER_NO;
}
if ( !me->m_Shared.InCond( TF_COND_DISGUISED ) &&
!me->m_Shared.InCond( TF_COND_DISGUISING ) &&
!me->m_Shared.IsStealthed() )
{
// our cover is blown!
return ANSWER_YES;
}
// if we've sapped, attack
return AreAllDangerousSentriesSapped( me ) ? ANSWER_YES : ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
// Don't avoid enemies when we're going in for the sap
QueryResultType CTFBotSpySap::IsHindrance( const INextBot *me, CBaseEntity *blocker ) const
{
if ( m_sapTarget.Get() && me->IsRangeLessThan( m_sapTarget, 300.0f ) )
{
// we're almost to our sap target - don't avoid anyone
return ANSWER_NO;
}
// avoid everyone while we move to our sap target
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotSpySap::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
bool CTFBotSpySap::AreAllDangerousSentriesSapped( CTFBot *me ) const
{
CUtlVector< CKnownEntity > knownVector;
me->GetVisionInterface()->CollectKnownEntities( &knownVector );
for( int i=0; i<knownVector.Count(); ++i )
{
CBaseObject *enemyObject = dynamic_cast< CBaseObject * >( knownVector[i].GetEntity() );
if ( enemyObject && enemyObject->ObjectType() == OBJ_SENTRYGUN && !enemyObject->HasSapper() && me->IsEnemy( enemyObject ) )
{
// this is an active enemy sentry, are we in range and line of fire?
if ( me->IsRangeLessThan( enemyObject, SENTRY_MAX_RANGE ) && me->IsLineOfFireClear( enemyObject ) )
{
return false;
}
}
}
return true;
}
@@ -0,0 +1,42 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_spy_sap.h
// Sap nearby enemy buildings
// Michael Booth, June 2010
#ifndef TF_BOT_SPY_SAP_H
#define TF_BOT_SPY_SAP_H
#include "Path/NextBotPathFollow.h"
class CTFBotSpySap : public Action< CTFBot >
{
public:
CTFBotSpySap( CBaseObject *sapTarget );
virtual ~CTFBotSpySap() { }
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 ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
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 QueryResultType IsHindrance( const INextBot *me, CBaseEntity *blocker ) const; // use this to signal the enemy we are focusing on, so we dont avoid them
virtual const char *GetName( void ) const { return "SpySap"; };
private:
CHandle< CBaseObject > m_sapTarget;
CountdownTimer m_repathTimer;
PathFollower m_path;
CBaseObject *GetNearestKnownSappableTarget( CTFBot *me );
bool AreAllDangerousSentriesSapped( CTFBot *me ) const;
};
#endif // TF_BOT_SPY_SAP_H
@@ -0,0 +1,336 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_escort_squad_leader.cpp
// Escort the squad leader to their destination
// Michael Booth, Octoboer 2011
#include "cbase.h"
#include "bot/tf_bot.h"
#include "bot/behavior/squad/tf_bot_escort_squad_leader.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.h"
#include "bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.h"
ConVar tf_bot_squad_escort_range( "tf_bot_squad_escort_range", "500", FCVAR_CHEAT );
ConVar tf_bot_formation_debug( "tf_bot_formation_debug", "0", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
CTFBotEscortSquadLeader::CTFBotEscortSquadLeader( Action< CTFBot > *actionToDoAfterSquadDisbands ) // : m_path( ChasePath::LEAD_SUBJECT )
{
m_actionToDoAfterSquadDisbands = actionToDoAfterSquadDisbands;
m_formationPath.SetGoalTolerance( 0.0f );
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEscortSquadLeader::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_formationForward = vec3_origin;
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEscortSquadLeader::Update( CTFBot *me, float interval )
{
if ( interval <= 0.0f )
{
return Continue();
}
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
// prepare to fight
me->EquipBestWeaponForThreat( threat );
}
CTFBotSquad *squad = me->GetSquad();
if ( !squad )
{
if ( m_actionToDoAfterSquadDisbands )
{
return ChangeTo( m_actionToDoAfterSquadDisbands, "Not in a Squad" );
}
return Done( "Not in a Squad" );
}
// we need to update every tick to smoothly move in formation
me->FlagForUpdate();
CTFBot *leader = squad->GetLeader();
if ( !leader || !leader->IsAlive() )
{
me->LeaveSquad();
if ( m_actionToDoAfterSquadDisbands )
{
return ChangeTo( m_actionToDoAfterSquadDisbands, "Squad leader is dead" );
}
return Done( "Squad leader is dead" );
}
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && leader == me )
{
const char* pszNowLeader = "I'm now the squad leader! Going for the flag!";
if ( me->HasAttribute( CTFBot::AGGRESSIVE ) )
{
// push for the point first, then attack
return ChangeTo( new CTFBotPushToCapturePoint( new CTFBotFetchFlag ), pszNowLeader );
}
// capture the flag
return ChangeTo( new CTFBotFetchFlag, pszNowLeader );
}
// if we're using a melee weapon, close and attack with it while staying near the leader
CTFWeaponBase *myWeapon = me->m_Shared.GetActiveTFWeapon();
if ( myWeapon && myWeapon->IsMeleeWeapon() )
{
if ( me->IsRangeLessThan( leader, tf_bot_squad_escort_range.GetFloat() ) && me->IsLineOfSightClear( leader ) )
{
ActionResult< CTFBot > result = m_meleeAttackAction.Update( me, interval );
if ( result.IsContinue() )
{
// we have a melee target, and we're still reasonably close to the flag leader
return Continue();
}
}
}
CUtlVector< CTFBot * > rawMemberVector;
squad->CollectMembers( &rawMemberVector );
// cull out the medics - they do their own thing
CUtlVector< CTFBot * > memberVector;
for( int m=0; m<rawMemberVector.Count(); ++m )
{
if ( !rawMemberVector[m]->IsPlayerClass( TF_CLASS_MEDIC ) )
{
memberVector.AddToTail( rawMemberVector[m] );
}
}
const PathFollower *leaderPath = leader->GetCurrentPath();
if ( !leaderPath || !leaderPath->GetCurrentGoal() )
{
// no path, no formation
me->SetSquadFormationError( 0.0f );
me->SetBrokenFormation( false );
return Continue();
}
const Path::Segment *leaderSegment = leaderPath->GetCurrentGoal();
Vector leaderForward = leaderSegment->pos - leader->GetAbsOrigin();
// if the leader is very close to the goal, use the next goal to ensure
// the forward vector stays forward
const float atGoal = 25.0f;
if ( leaderForward.IsLengthLessThan( atGoal ) )
{
const Path::Segment *nextSegment = leaderPath->NextSegment( leaderSegment );
if ( nextSegment )
{
leaderForward = nextSegment->pos - leader->GetAbsOrigin();
}
}
leaderForward.NormalizeInPlace();
if ( m_formationForward.IsZero() )
{
m_formationForward = leaderForward;
}
else
{
// limit rate of change of leader forward vector to keep formation coherent
float maxRotation = 30.0f; // degrees/second
float leaderForwardYaw = UTIL_VecToYaw( leaderForward );
float formationYaw = UTIL_VecToYaw( m_formationForward );
float angleDiff = UTIL_AngleDiff( leaderForwardYaw, formationYaw );
float deltaYaw = maxRotation * interval;
if ( angleDiff < -deltaYaw )
{
formationYaw -= deltaYaw;
}
else if ( angleDiff > deltaYaw )
{
formationYaw += deltaYaw;
}
else
{
formationYaw += angleDiff;
}
FastSinCos( formationYaw * M_PI / 180.0f, &m_formationForward.y, &m_formationForward.x );
m_formationForward.z = 0.0f;
}
const float maxSeparationAngle = 30.0f * M_PI / 180.0f;
float formationRadius = 125.0f;
if ( squad->GetFormationSize() > 0.0f )
{
formationRadius = squad->GetFormationSize();
}
Vector myFormationSpot;
Vector formationForward = vec3_origin;
float s, c;
// where am I in the roster
int which;
for( which=0; which<memberVector.Count(); ++which )
{
if ( me->IsSelf( memberVector[which] ) )
{
break;
}
}
// subtract one since the leader is always first
--which;
// my formation spot is assigned via my position in the roster array
int slot = ( which + 1 ) /2;
float formationAngle = slot * maxSeparationAngle;
if ( which & 0x1 )
{
formationAngle = -formationAngle;
}
FastSinCos( formationAngle, &s, &c );
formationForward.x = m_formationForward.x * c - m_formationForward.y * s;
formationForward.y = m_formationForward.y * c + m_formationForward.x * s;
myFormationSpot = leader->GetAbsOrigin() + formationRadius * formationForward;
trace_t result;
CTraceFilterIgnoreTeammates filter( me, COLLISION_GROUP_NONE, me->GetTeamNumber() );
UTIL_TraceLine( leader->GetAbsOrigin() + Vector( 0, 0, HalfHumanHeight ), myFormationSpot + Vector( 0, 0, HalfHumanHeight ), MASK_PLAYERSOLID, &filter, &result );
if ( result.DidHitWorld() )
{
myFormationSpot = result.endpos - Vector( 0, 0, HalfHumanHeight ) + 0.6f * me->GetBodyInterface()->GetHullWidth() * result.plane.normal;
}
if ( tf_bot_formation_debug.GetBool() )
{
NDebugOverlay::Circle( myFormationSpot, 16.0f, 0, 255, 0, 255, true, 0.1f );
CFmtStr msg;
NDebugOverlay::Text( myFormationSpot, msg.sprintf( "%d", which ), false, 0.1f );
}
// match speed with leader if I'm at/near my formation position
Vector to = myFormationSpot - me->GetAbsOrigin();
float error = to.Length2D();
const float maxError = 100.0f; // 50
float normalizedError = 1.0f;
if ( error < maxError )
{
normalizedError = error / maxError;
}
// this error term is used in CTFPlayer::TeamFortress_CalculateMaxSpeed() to
// modulate our speed
// 0 = in position (no error)
// 1 = far out of position (max error)
me->SetSquadFormationError( normalizedError );
// move to my formation spot
if ( error < 50.0f )
{
// if we're ahead of where we want to be, just wait
if ( DotProduct( to, formationForward ) > 0.0f )
{
// very close - just directly approach to avoid pathing jaggies
me->GetLocomotionInterface()->Approach( myFormationSpot );
}
else
{
// we're in position
me->SetSquadFormationError( 0.0f );
}
}
else
{
if ( m_pathTimer.IsElapsed() )
{
m_pathTimer.Start( RandomFloat( 0.1f, 0.2f ) );
me->SetBrokenFormation( false );
CTFBotPathCost cost( me, FASTEST_ROUTE );
if ( m_formationPath.Compute( me, myFormationSpot, cost ) == false )
{
// no path back to formation
me->SetBrokenFormation( true );
}
// if we have a long path to get back in formation, we've broken ranks
const float tooFar = 750.0f;
if ( m_formationPath.GetLength() > tooFar )
{
me->SetBrokenFormation( true );
}
}
m_formationPath.Update( me );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotEscortSquadLeader::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotWaitForOutOfPositionSquadMember::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_waitTimer.Start( 2.0f );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotWaitForOutOfPositionSquadMember::Update( CTFBot *me, float interval )
{
if ( m_waitTimer.IsElapsed() )
{
return Done( "Timeout" );
}
if ( !me->IsInASquad() || !me->GetSquad()->IsLeader( me ) )
{
return Done( "No squad" );
}
if ( me->GetSquad()->IsInFormation() )
{
// Everyone is in position
return Done( "Everyone is in formation. Moving on." );
}
return Continue();
}
@@ -0,0 +1,55 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_escort_squad_leader.h
// Escort the squad leader to their destination
// Michael Booth, Octoboer 2011
#ifndef TF_BOT_ESCORT_SQUAD_LEADER_H
#define TF_BOT_ESCORT_SQUAD_LEADER_H
#include "Path/NextBotPathFollow.h"
#include "bot/behavior/tf_bot_melee_attack.h"
//-----------------------------------------------------------------------------
class CTFBotEscortSquadLeader : public Action< CTFBot >
{
public:
CTFBotEscortSquadLeader( Action< CTFBot > *actionToDoAfterSquadDisbands = NULL );
virtual ~CTFBotEscortSquadLeader() { }
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 const char *GetName( void ) const { return "EscortSquadLeader"; };
private:
Action< CTFBot > *m_actionToDoAfterSquadDisbands;
CTFBotMeleeAttack m_meleeAttackAction;
PathFollower m_formationPath;
CountdownTimer m_pathTimer;
const Vector &GetFormationForwardVector( CTFBot *me );
Vector m_formationForward;
};
//-----------------------------------------------------------------------------
class CTFBotWaitForOutOfPositionSquadMember : public Action< CTFBot >
{
public:
virtual ~CTFBotWaitForOutOfPositionSquadMember() { }
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual const char *GetName( void ) const { return "WaitForOutOfPositionSquadMember"; };
private:
CountdownTimer m_waitTimer;
};
#endif // TF_BOT_ESCORT_SQUAD_LEADER_H
@@ -0,0 +1,69 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_approach_object.h
// Move near/onto an object
// Michael Booth, February 2009
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "bot/tf_bot.h"
#include "bot/behavior/tf_bot_approach_object.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
CTFBotApproachObject::CTFBotApproachObject( CBaseEntity *loot, float range )
{
m_loot = loot;
m_range = range;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotApproachObject::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotApproachObject::Update( CTFBot *me, float interval )
{
if ( m_loot == NULL )
{
return Done( "Object is NULL" );
}
if ( m_loot->IsEffectActive( EF_NODRAW ) )
{
return Done( "Object is NODRAW" );
}
if ( me->GetLocomotionInterface()->GetGround() == m_loot )
{
return Done( "I'm standing on the object" );
}
if ( me->IsDistanceBetweenLessThan( m_loot->GetAbsOrigin(), m_range ) )
{
// in case we can't pick up the loot for some reason
return Done( "Reached object" );
}
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, m_loot->GetAbsOrigin(), cost );
}
// move to the loot
m_path.Update( me );
return Continue();
}
@@ -0,0 +1,29 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_approach_object.h
// Move near/onto an object
// Michael Booth, February 2009
#ifndef TF_BOT_APPROACH_OBJECT_H
#define TF_BOT_APPROACH_OBJECT_H
#include "Path/NextBotPathFollow.h"
class CTFBotApproachObject : public Action< CTFBot >
{
public:
CTFBotApproachObject( CBaseEntity *loot, float range = 10.0f );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual const char *GetName( void ) const { return "ApproachObject"; };
private:
CHandle< CBaseEntity > m_loot; // what we are collecting
float m_range; // how close should we get
PathFollower m_path; // how we get to the loot
CountdownTimer m_repathTimer;
};
#endif // TF_BOT_APPROACH_OBJECT_H
@@ -0,0 +1,157 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_attack.cpp
// Attack our threat
// Michael Booth, February 2009
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "team_control_point_master.h"
#include "bot/tf_bot.h"
#include "bot/behavior/tf_bot_attack.h"
#include "nav_mesh.h"
extern ConVar tf_bot_path_lookahead_range;
extern ConVar tf_bot_offense_must_push_time;
//---------------------------------------------------------------------------------------------
CTFBotAttack::CTFBotAttack( void ) : m_chasePath( ChasePath::LEAD_SUBJECT )
{
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotAttack::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
return Continue();
}
//---------------------------------------------------------------------------------------------
// head aiming and weapon firing is handled elsewhere - we just need to get into position to fight
ActionResult< CTFBot > CTFBotAttack::Update( CTFBot *me, float interval )
{
CTFWeaponBase *myWeapon = me->m_Shared.GetActiveTFWeapon();
bool isUsingCloseRangeWeapon = ( myWeapon && ( myWeapon->IsWeapon( TF_WEAPON_FLAMETHROWER ) || myWeapon->IsMeleeWeapon() ) );
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat == NULL || threat->IsObsolete() || !me->GetIntentionInterface()->ShouldAttack( me, threat ) )
{
return Done( "No threat" );
}
me->EquipBestWeaponForThreat( threat );
if ( isUsingCloseRangeWeapon && threat->IsVisibleRecently() && me->IsRangeLessThan( threat->GetLastKnownPosition(), 1.1f * me->GetDesiredAttackRange() ) )
{
// circle around our victim
if ( me->TransientlyConsistentRandomValue( 3.0f ) < 0.5f )
{
me->PressLeftButton();
}
else
{
me->PressRightButton();
}
}
// pursue the threat. if not visible, go to the last known position
if ( !threat->IsVisibleRecently() ||
me->IsRangeGreaterThan( threat->GetEntity()->GetAbsOrigin(), me->GetDesiredAttackRange() ) ||
!me->IsLineOfFireClear( threat->GetEntity()->EyePosition() ) )
{
if ( threat->IsVisibleRecently() )
{
if ( isUsingCloseRangeWeapon && !TFGameRules()->IsMannVsMachineMode() ) // all bots in MvM use the default route
{
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_chasePath.Update( me, threat->GetEntity(), cost );
}
else
{
CTFBotPathCost cost( me, DEFAULT_ROUTE );
m_chasePath.Update( me, threat->GetEntity(), cost );
}
}
else
{
// if we're at the threat's last known position and he's still not visible, we lost him
m_chasePath.Invalidate();
if ( me->IsRangeLessThan( threat->GetLastKnownPosition(), 20.0f ) )
{
me->GetVisionInterface()->ForgetEntity( threat->GetEntity() );
return Done( "I lost my target!" );
}
// look where we last saw him as we approach
if ( me->IsRangeLessThan( threat->GetLastKnownPosition(), me->GetMaxAttackRange() ) )
{
me->GetBodyInterface()->AimHeadTowards( threat->GetLastKnownPosition() + Vector( 0, 0, HumanEyeHeight ), IBody::IMPORTANT, 0.2f, NULL, "Looking towards where we lost sight of our victim" );
}
m_path.Update( me );
if ( m_repathTimer.IsElapsed() )
{
//m_repathTimer.Start( RandomFloat( 0.3f, 0.5f ) );
m_repathTimer.Start( RandomFloat( 3.0f, 5.0f ) );
if ( isUsingCloseRangeWeapon && !TFGameRules()->IsMannVsMachineMode() ) // all bots in MvM use the default route
{
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, threat->GetLastKnownPosition(), cost );
}
else
{
CTFBotPathCost cost( me, DEFAULT_ROUTE );
float maxPathLength = TFGameRules()->IsMannVsMachineMode() ? TFBOT_MVM_MAX_PATH_LENGTH : 0.0f;
m_path.Compute( me, threat->GetLastKnownPosition(), cost, maxPathLength );
}
}
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotAttack::OnStuck( CTFBot *me )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotAttack::OnMoveToSuccess( CTFBot *me, const Path *path )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotAttack::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotAttack::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotAttack::ShouldHurry( const INextBot *me ) const
{
return ANSWER_UNDEFINED;
}
@@ -0,0 +1,38 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_attack.h
// Attack a threat
// Michael Booth, February 2009
#ifndef TF_BOT_ATTACK_H
#define TF_BOT_ATTACK_H
#include "Path/NextBotChasePath.h"
//-------------------------------------------------------------------------------
class CTFBotAttack : public Action< CTFBot >
{
public:
CTFBotAttack( void );
virtual ~CTFBotAttack() { }
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual const char *GetName( void ) const { return "Attack"; };
private:
PathFollower m_path;
ChasePath m_chasePath;
CountdownTimer m_repathTimer;
};
#endif // TF_BOT_ATTACK_H

Some files were not shown because too many files have changed in this diff Show More