mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-08 01:39:36 +00:00
1
This commit is contained in:
@@ -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
|
||||
+123
@@ -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
|
||||
+105
@@ -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();
|
||||
}
|
||||
+27
@@ -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
|
||||
+95
@@ -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();
|
||||
}
|
||||
|
||||
+28
@@ -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
|
||||
+247
@@ -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;
|
||||
}
|
||||
+40
@@ -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
|
||||
+126
@@ -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
Reference in New Issue
Block a user