This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions
@@ -0,0 +1,123 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h"
#include "bot/map_entities/tf_bot_hint_sentrygun.h"
#include "bot/map_entities/tf_bot_hint_teleporter_exit.h"
#include "string_t.h"
#include "tf_fx.h"
extern ConVar tf_bot_engineer_mvm_building_health_multiplier;
//---------------------------------------------------------------------------------------------
CTFBotMvMEngineerBuildSentryGun::CTFBotMvMEngineerBuildSentryGun( CTFBotHintSentrygun* pSentryHint )
{
m_sentryBuildHint = pSentryHint;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerBuildSentryGun::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
me->StartBuildingObjectOfType( OBJ_SENTRYGUN );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerBuildSentryGun::Update( CTFBot *me, float interval )
{
if ( m_sentryBuildHint == NULL )
return Done( "No hint entity" );
float rangeToBuildSpot = me->GetRangeTo( m_sentryBuildHint->GetAbsOrigin() );
if ( rangeToBuildSpot < 200.0f )
{
// crouch as we get close so we don't overshoot
me->PressCrouchButton();
me->GetBodyInterface()->AimHeadTowards( m_sentryBuildHint->GetAbsOrigin(), IBody::MANDATORY, 0.1f, NULL, "Placing sentry" );
}
// various interruptions could mean we're away from our build location - move to it
if ( rangeToBuildSpot > 25.0f )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, m_sentryBuildHint->GetAbsOrigin(), cost );
}
m_path.Update( me );
if ( !m_path.IsValid() )
{
return Done( "Path failed" );
}
return Continue();
}
if ( !m_delayBuildTime.HasStarted() )
{
m_delayBuildTime.Start( 0.1f );
TFGameRules()->PushAllPlayersAway( m_sentryBuildHint->GetAbsOrigin(), 400, 500, TF_TEAM_RED );
}
else if ( m_delayBuildTime.HasStarted() && m_delayBuildTime.IsElapsed() )
{
// destroy previous object
me->DetonateObjectOfType( OBJ_SENTRYGUN, MODE_SENTRYGUN_NORMAL, true );
// directly create a sentry gun at the precise position and orientation desired
m_sentry = (CObjectSentrygun *)CreateEntityByName( "obj_sentrygun" );
if ( m_sentry )
{
m_sentry->SetName( m_sentryBuildHint->GetEntityName() );
m_sentryBuildHint->IncrementUseCount();
m_sentry->m_nDefaultUpgradeLevel = 2;
m_sentry->SetAbsOrigin( m_sentryBuildHint->GetAbsOrigin() );
m_sentry->SetAbsAngles( QAngle( 0, m_sentryBuildHint->GetAbsAngles().y, 0 ) );
m_sentry->Spawn();
m_sentry->StartPlacement( me );
m_sentry->StartBuilding( me );
// the sentry owns this hint now
m_sentryBuildHint->SetOwnerEntity( m_sentry );
m_sentry = NULL;
}
return Done( "Built a sentry" );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotMvMEngineerBuildSentryGun::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
if ( m_sentry.Get() )
{
m_sentry->DropCarriedObject( me );
UTIL_Remove( m_sentry );
m_sentry = NULL;
}
}
@@ -0,0 +1,29 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#ifndef TF_BOT_MVM_ENGINEER_BUILD_SENTRYGUN_H
#define TF_BOT_MVM_ENGINEER_BUILD_SENTRYGUN_H
class CTFBotHintSentrygun;
class CTFBotMvMEngineerBuildSentryGun : public Action< CTFBot >
{
public:
CTFBotMvMEngineerBuildSentryGun( CTFBotHintSentrygun* pSentryHint );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual void OnEnd( CTFBot *me, Action< CTFBot > *nextAction );
virtual const char *GetName( void ) const { return "MvMEngineerBuildSentryGun"; };
private:
CHandle< CTFBotHintSentrygun > m_sentryBuildHint;
CHandle< CObjectSentrygun > m_sentry;
CountdownTimer m_delayBuildTime;
CountdownTimer m_repathTimer;
PathFollower m_path;
};
#endif // TF_BOT_MVM_ENGINEER_BUILD_SENTRYGUN_H
@@ -0,0 +1,105 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#include "cbase.h"
#include "string_t.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h"
#include "bot/map_entities/tf_bot_hint_teleporter_exit.h"
ConVar tf_bot_engineer_mvm_building_health_multiplier( "tf_bot_engineer_building_health_multiplier", "2", FCVAR_CHEAT );
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
CTFBotMvMEngineerBuildTeleportExit::CTFBotMvMEngineerBuildTeleportExit( CTFBotHintTeleporterExit *hint )
{
m_teleporterBuildHint = hint;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerBuildTeleportExit::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerBuildTeleportExit::Update( CTFBot *me, float interval )
{
if ( m_teleporterBuildHint == NULL )
return Done( "No hint entity" );
// various interruptions could mean we're away from our build location - move to it
if ( me->IsRangeGreaterThan( m_teleporterBuildHint->GetAbsOrigin(), 25.0f ) )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, m_teleporterBuildHint->GetAbsOrigin(), cost );
}
m_path.Update( me );
if ( !m_path.IsValid() )
{
return Done( "Path failed" );
}
return Continue();
}
if ( !m_delayBuildTime.HasStarted() )
{
m_delayBuildTime.Start( 0.1f );
TFGameRules()->PushAllPlayersAway( m_teleporterBuildHint->GetAbsOrigin(), 400, 500, TF_TEAM_RED );
}
else if ( m_delayBuildTime.IsElapsed() )
{
// destroy previous object
me->DetonateObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT, true );
// directly create at the precise position and orientation desired
CObjectTeleporter* myTeleporter = (CObjectTeleporter *)CreateEntityByName( "obj_teleporter" );
if ( myTeleporter )
{
myTeleporter->SetAbsOrigin( m_teleporterBuildHint->GetAbsOrigin() );
myTeleporter->SetAbsAngles( QAngle( 0, m_teleporterBuildHint->GetAbsAngles().y, 0 ) );
myTeleporter->SetObjectMode( MODE_TELEPORTER_EXIT );
myTeleporter->Spawn();
myTeleporter->SetTeleportWhere( me->GetTeleportWhere() );
if ( me->ShouldQuickBuild() )
{
myTeleporter->ForceQuickBuild();
}
myTeleporter->StartPlacement( me );
myTeleporter->StartBuilding( me );
int iHealth = myTeleporter->GetMaxHealthForCurrentLevel() * tf_bot_engineer_mvm_building_health_multiplier.GetFloat();
myTeleporter->SetMaxHealth( iHealth );
myTeleporter->SetHealth( iHealth );
m_teleporterBuildHint->SetOwnerEntity( myTeleporter );
me->EmitSound( "Engineer.MVM_AutoBuildingTeleporter02" );
return Done( "Teleport exit built" );
}
}
return Continue();
}
@@ -0,0 +1,27 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#ifndef TF_BOT_MVM_ENGINEER_BUILD_TELEPORTER_H
#define TF_BOT_MVM_ENGINEER_BUILD_TELEPORTER_H
class CTFBotHintTeleporterExit;
class CTFBotMvMEngineerBuildTeleportExit : public Action< CTFBot >
{
public:
CTFBotMvMEngineerBuildTeleportExit( CTFBotHintTeleporterExit *hint );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual const char *GetName( void ) const { return "MvMEngineerBuildTeleportExit"; };
private:
CHandle< CTFBotHintTeleporterExit > m_teleporterBuildHint;
CountdownTimer m_delayBuildTime;
CountdownTimer m_repathTimer;
PathFollower m_path;
};
#endif // TF_BOT_MVM_ENGINEER_BUILD_TELEPORTER_H
@@ -0,0 +1,662 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#include "cbase.h"
#include "nav_mesh/tf_nav_mesh.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_teleporter.h"
#include "bot/tf_bot.h"
#include "bot/map_entities/tf_bot_hint_engineer_nest.h"
#include "bot/map_entities/tf_bot_hint_sentrygun.h"
#include "bot/map_entities/tf_bot_hint_teleporter_exit.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h"
#include "bot/behavior/tf_bot_retreat_to_cover.h"
ConVar tf_bot_engineer_mvm_sentry_hint_bomb_forward_range( "tf_bot_engineer_mvm_sentry_hint_bomb_forward_range", "0", FCVAR_CHEAT );
ConVar tf_bot_engineer_mvm_sentry_hint_bomb_backward_range( "tf_bot_engineer_mvm_sentry_hint_bomb_backward_range", "3000", FCVAR_CHEAT );
ConVar tf_bot_engineer_mvm_hint_min_distance_from_bomb( "tf_bot_engineer_mvm_hint_min_distance_from_bomb", "1300", FCVAR_CHEAT );
struct BombInfo_t
{
Vector m_vPosition;
float m_flMinBattleFront;
float m_flMaxBattleFront;
};
bool GetBombInfo( BombInfo_t* pBombInfo = NULL )
{
// find the incursion distance of the current "front" (the location of the bomb)
// first find farthest bomb delivery distance of invading team since maps
// have different spawn room sizes and geometries
float battlefront = 0.0f;
for( int n=0; n<TheNavAreas.Count(); ++n )
{
CTFNavArea *area = (CTFNavArea *)TheNavAreas[n];
if ( area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
{
continue;
}
float areaDistanceToTarget = area->GetTravelDistanceToBombTarget();
if ( areaDistanceToTarget > battlefront && areaDistanceToTarget > 0.0f )
{
battlefront = areaDistanceToTarget;
}
}
// find the travel distance from the bomb to the delivery target and use it as the front
CCaptureFlag *flag = NULL;
Vector vBombSpot(0, 0, 0);
for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
{
CCaptureFlag *pTempFlag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[i] );
Vector vTempBombSpot;
CTFPlayer *carrier = ToTFPlayer( pTempFlag->GetOwnerEntity() );
if ( carrier )
{
vTempBombSpot = carrier->GetAbsOrigin();
}
else
{
vTempBombSpot = pTempFlag->WorldSpaceCenter();
}
CTFNavArea *flagArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( vTempBombSpot, false, 1000.0f );
if ( flagArea )
{
float flagDistanceToTarget = flagArea->GetTravelDistanceToBombTarget();
if ( flagDistanceToTarget < battlefront && flagDistanceToTarget >= 0.0f )
{
battlefront = flagDistanceToTarget;
flag = pTempFlag;
vBombSpot = vTempBombSpot;
}
}
}
float flMaxBattlefront = battlefront + tf_bot_engineer_mvm_sentry_hint_bomb_backward_range.GetFloat();
float flMinBattlefront = battlefront - tf_bot_engineer_mvm_sentry_hint_bomb_forward_range.GetFloat();
if ( pBombInfo )
{
pBombInfo->m_vPosition = vBombSpot;
pBombInfo->m_flMinBattleFront = flMinBattlefront;
pBombInfo->m_flMaxBattleFront = flMaxBattlefront;
}
return flag ? true : false;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerIdle::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
me->StopLookingAroundForEnemies();
m_sentryHint = NULL;
m_teleporterHint = NULL;
m_nestHint = NULL;
m_nTeleportedCount = 0;
m_bTeleportedToHint = false;
m_bTriedToDetonateStaleNest = false;
return Continue();
}
void CTFBotMvMEngineerIdle::TakeOverStaleNest( CBaseTFBotHintEntity* pHint, CTFBot *me )
{
if ( pHint != NULL && pHint->OwnerObjectHasNoOwner() )
{
CBaseObject* pObj = static_cast< CBaseObject* >( pHint->GetOwnerEntity() );
pObj->SetOwnerEntity( me );
pObj->SetBuilder( me );
me->AddObject( pObj );
}
}
bool CTFBotMvMEngineerIdle::ShouldAdvanceNestSpot( CTFBot *me )
{
if ( !m_nestHint )
{
return false;
}
if ( !m_reevaluateNestTimer.HasStarted() )
{
m_reevaluateNestTimer.Start( 5.f );
return false;
}
for ( int i=0; i<me->GetObjectCount(); ++i )
{
CBaseObject *pObj = me->GetObject( i );
if ( pObj && pObj->GetHealth() < pObj->GetMaxHealth() )
{
// if the nest is under attack, don't advance the nest
m_reevaluateNestTimer.Start( 5.f );
return false;
}
}
if ( m_reevaluateNestTimer.IsElapsed() )
{
m_reevaluateNestTimer.Invalidate();
}
BombInfo_t bombInfo;
if ( GetBombInfo( &bombInfo ) )
{
if ( m_nestHint )
{
CTFNavArea *hintArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( m_nestHint->GetAbsOrigin(), false, 1000.0f );
if ( hintArea )
{
float hintDistanceToTarget = hintArea->GetTravelDistanceToBombTarget();
bool bShouldAdvance = ( hintDistanceToTarget > bombInfo.m_flMaxBattleFront );
return bShouldAdvance;
}
}
}
return false;
}
void CTFBotMvMEngineerIdle::TryToDetonateStaleNest()
{
if ( m_bTriedToDetonateStaleNest )
return;
// wait until the engy finish building his nest
if ( ( m_sentryHint && !m_sentryHint->OwnerObjectFinishBuilding() ) ||
( m_teleporterHint && !m_teleporterHint->OwnerObjectFinishBuilding() ) )
return;
// collect all existing and active teleporter hints
CUtlVector< CTFBotHintEngineerNest* > activeEngineerNest;
for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i )
{
CBaseTFBotHintEntity *pHint = static_cast<CBaseTFBotHintEntity*>( ITFBotHintEntityAutoList::AutoList()[i] );
if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_ENGINEER_NEST ) && pHint->IsEnabled() && pHint->GetOwnerEntity() == NULL )
{
activeEngineerNest.AddToTail( static_cast< CTFBotHintEngineerNest* >( pHint ) );
}
}
// try to detonate stale nest that's out of range, when engineer finished building his nest
for ( int i=0; i<activeEngineerNest.Count(); ++i )
{
CTFBotHintEngineerNest *pNest = activeEngineerNest[i];
if ( pNest->IsStaleNest() )
{
pNest->DetonateStaleNest();
}
}
m_bTriedToDetonateStaleNest = true;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerIdle::Update( CTFBot *me, float interval )
{
if ( !me->IsAlive() )
{
// don't do anything when I'm dead
return Done();
}
// Always equip my wrench
CBaseCombatWeapon *wrench = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
if ( wrench )
{
me->Weapon_Switch( wrench );
}
if ( m_nestHint == NULL || ShouldAdvanceNestSpot( me ) )
{
if ( m_findHintTimer.HasStarted() && !m_findHintTimer.IsElapsed() )
{
// too soon
return Continue();
}
m_findHintTimer.Start( RandomFloat( 1.0f, 2.0f ) );
// figure out where to teleport into the map
bool bShouldTeleportToHint = me->HasAttribute( CTFBot::TELEPORT_TO_HINT );
bool bShouldCheckForBlockingObject = !m_bTeleportedToHint && bShouldTeleportToHint;
CHandle< CTFBotHintEngineerNest > newNest = NULL;
if ( !CTFBotMvMEngineerHintFinder::FindHint( bShouldCheckForBlockingObject, !bShouldTeleportToHint, &newNest ) )
{
// try again next time
return Continue();
}
// unown the old nest
if ( m_nestHint )
{
m_nestHint->SetOwnerEntity( NULL );
}
m_nestHint = newNest;
m_nestHint->SetOwnerEntity( me );
m_sentryHint = m_nestHint->GetSentryHint();
TakeOverStaleNest( m_sentryHint, me );
if ( me->GetTeleportWhere().Count() > 0 )
{
m_teleporterHint = m_nestHint->GetTeleporterHint();
TakeOverStaleNest( m_teleporterHint, me );
}
}
if ( !m_bTeleportedToHint && me->HasAttribute( CTFBot::TELEPORT_TO_HINT ) )
{
m_nTeleportedCount++;
bool bFirstTeleportSpawn = m_nTeleportedCount == 1;
m_bTeleportedToHint = true;
return SuspendFor( new CTFBotMvMEngineerTeleportSpawn( m_nestHint, bFirstTeleportSpawn ), "In spawn area - teleport to the teleporter hint" );
}
const float rebuildInterval = 3.0f;
CObjectSentrygun *mySentry = NULL;
if ( m_sentryHint )
{
if ( m_sentryHint->GetOwnerEntity() && m_sentryHint->GetOwnerEntity()->IsBaseObject() )
{
mySentry = assert_cast< CObjectSentrygun* >( m_sentryHint->GetOwnerEntity() );
}
if ( mySentry )
{
// force an interval between sentry being destroyed and me trying to rebuild it
m_sentryRebuildTimer.Start( rebuildInterval );
}
else
{
// check if there's a stale object on the hint
if ( m_sentryHint->GetOwnerEntity() && m_sentryHint->GetOwnerEntity()->IsBaseObject() )
{
mySentry = assert_cast< CObjectSentrygun* >( m_sentryHint->GetOwnerEntity() );
me->AddObject( mySentry );
mySentry->SetOwnerEntity( me );
}
else
{
if ( m_sentryRebuildTimer.IsElapsed() )
{
return SuspendFor( new CTFBotMvMEngineerBuildSentryGun( m_sentryHint ), "No sentry - building a new one" );
}
else
{
// run away!
return SuspendFor( new CTFBotRetreatToCover( 1.0f ), "Lost my sentry - retreat!" );
}
}
}
}
if ( mySentry && mySentry->GetHealth() < mySentry->GetMaxHealth() && !mySentry->IsBuilding() )
{
// track when sentry was last hurt
m_sentryInjuredTimer.Start( 3.0f );
}
CObjectTeleporter *myTeleporter = NULL;
if ( m_teleporterHint && m_sentryInjuredTimer.IsElapsed() )
{
if ( m_teleporterHint->GetOwnerEntity() && m_teleporterHint->GetOwnerEntity()->IsBaseObject() )
{
// force an interval between teleporter being destroyed and me trying to rebuild it
myTeleporter = assert_cast< CObjectTeleporter* >( m_teleporterHint->GetOwnerEntity() );
m_teleporterRebuildTimer.Start( rebuildInterval );
}
else if ( m_teleporterRebuildTimer.IsElapsed() )
{
return SuspendFor( new CTFBotMvMEngineerBuildTeleportExit( m_teleporterHint ), "Sentry is safe - building a teleport exit" );
}
}
// fix teleporter if sentry is not hurt
if ( myTeleporter && m_sentryInjuredTimer.IsElapsed() && myTeleporter->GetHealth() < myTeleporter->GetMaxHealth() && !myTeleporter->IsBuilding() )
{
float rangeToTeleporter = me->GetDistanceBetween( myTeleporter );
const float nearTeleporterRange = 75.0f;
if ( rangeToTeleporter < 1.2f * nearTeleporterRange )
{
// crouch as I get close
me->PressCrouchButton();
}
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
Vector toTeleporter = myTeleporter->GetAbsOrigin() - me->GetAbsOrigin();
Vector hittingTeleporterSpot = myTeleporter->GetAbsOrigin() - 50.0f * toTeleporter.Normalized();
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, hittingTeleporterSpot, cost );
}
m_path.Update( me );
if ( rangeToTeleporter < nearTeleporterRange )
{
// we are in position - hit sentry with wrench
me->GetBodyInterface()->AimHeadTowards( myTeleporter->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my Teleporter" );
me->PressFireButton();
}
}
else if ( mySentry )
{
float rangeToSentry = me->GetDistanceBetween( mySentry );
const float nearSentryRange = 75.0f;
if ( rangeToSentry < 1.2f * nearSentryRange )
{
// crouch as I get close
me->PressCrouchButton();
}
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
Vector mySentryForward;
AngleVectors( mySentry->GetTurretAngles(), &mySentryForward );
Vector behindSentrySpot = mySentry->GetAbsOrigin() - 50.0f * mySentryForward;
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, behindSentrySpot, cost );
}
m_path.Update( me );
if ( rangeToSentry < nearSentryRange )
{
// we are in position - hit sentry with wrench
me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my Sentry" );
me->PressFireButton();
}
}
TryToDetonateStaleNest();
return Continue();
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotMvMEngineerIdle::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
{
return ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotMvMEngineerIdle::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_NO;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotMvMEngineerIdle::ShouldHurry( const INextBot *me ) const
{
return ANSWER_YES;
}
CTFBotHintEngineerNest* SelectOutOfRangeNest( const CUtlVector< CTFBotHintEngineerNest* >& nestVector )
{
if ( nestVector.Count() )
{
for ( int i=0; i<nestVector.Count(); ++i )
{
if ( nestVector[i]->IsStaleNest() )
{
return nestVector[i];
}
}
int which = RandomInt( 0, nestVector.Count() - 1 );
return nestVector[which];
}
return NULL;
}
//---------------------------------------------------------------------------------------------
bool CTFBotMvMEngineerHintFinder::FindHint( bool bShouldCheckForBlockingObjects, bool bAllowOutOfRangeNest, CHandle< CTFBotHintEngineerNest >* pFoundNest /*= NULL*/ )
{
// collect all existing and active teleporter hints
CUtlVector< CTFBotHintEngineerNest* > activeEngineerNest;
for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i )
{
CBaseTFBotHintEntity *pHint = static_cast<CBaseTFBotHintEntity*>( ITFBotHintEntityAutoList::AutoList()[i] );
if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_ENGINEER_NEST ) && pHint->IsEnabled() && pHint->GetOwnerEntity() == NULL )
{
activeEngineerNest.AddToTail( static_cast< CTFBotHintEngineerNest* >( pHint ) );
}
}
if ( activeEngineerNest.Count() == 0 )
{
if ( pFoundNest )
{
*pFoundNest = NULL;
}
return false;
}
BombInfo_t bombInfo;
GetBombInfo( &bombInfo );
CUtlVector< CTFBotHintEngineerNest* > forwardOutOfRangeHintVector;
CUtlVector< CTFBotHintEngineerNest* > backwardOutOfRangeHintVector;
CUtlVector< CTFBotHintEngineerNest* > freeAtFrontHintVector;
CUtlVector< CTFBotHintEngineerNest* > staleAtFrontHintVector;
for( int i=0; i<activeEngineerNest.Count(); ++i )
{
CTFBotHintEngineerNest* pCurrentNest = activeEngineerNest[i];
const Vector& vNestPosition = pCurrentNest->GetAbsOrigin();
CTFNavArea *hintArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( vNestPosition, false, 1000.0f );
if ( !hintArea )
{
Warning( "Sentry hint has NULL nav area!\n" );
continue;
}
float hintDistanceToTarget = hintArea->GetTravelDistanceToBombTarget();
if ( hintDistanceToTarget > bombInfo.m_flMinBattleFront && hintDistanceToTarget < bombInfo.m_flMaxBattleFront )
{
if ( bShouldCheckForBlockingObjects )
{
// check for blocking players and objects
CBaseEntity *pList[256];
int count = UTIL_EntitiesInBox( pList, ARRAYSIZE( pList ), vNestPosition + VEC_HULL_MIN, vNestPosition + VEC_HULL_MAX, FL_CLIENT|FL_OBJECT );
if ( count > 0 )
{
continue;
}
}
// this hint is in range of the front
if ( pCurrentNest->IsStaleNest() )
{
// some dead engineer was here and left his object(s) behind. I should take over
staleAtFrontHintVector.AddToTail( pCurrentNest );
}
else
{
if ( VectorLength( bombInfo.m_vPosition - vNestPosition ) < tf_bot_engineer_mvm_hint_min_distance_from_bomb.GetFloat() )
{
// the hint is too close to the bomb, don't go there
continue;
}
// this hint is also unowned
freeAtFrontHintVector.AddToTail( pCurrentNest );
}
}
else if ( hintDistanceToTarget > bombInfo.m_flMaxBattleFront )
{
forwardOutOfRangeHintVector.AddToTail( pCurrentNest );
}
else
{
backwardOutOfRangeHintVector.AddToTail( pCurrentNest );
}
}
CTFBotHintEngineerNest *hint = NULL;
if ( freeAtFrontHintVector.Count() == 0 && staleAtFrontHintVector.Count() == 0 )
{
if ( bAllowOutOfRangeNest )
{
// try to advance forward before falling backward
hint = SelectOutOfRangeNest( forwardOutOfRangeHintVector );
if ( !hint )
{
hint = SelectOutOfRangeNest( backwardOutOfRangeHintVector );
}
}
// no hints are in range, or they are all in use
if ( pFoundNest )
{
*pFoundNest = hint;
}
}
else
{
// try to pick stale nest in range first
if ( staleAtFrontHintVector.Count() )
{
int whichHint = RandomInt( 0, staleAtFrontHintVector.Count()-1 );
hint = staleAtFrontHintVector[ whichHint ];
}
// if I didn't find any stale nest, try to find a free one
else if ( freeAtFrontHintVector.Count() )
{
int whichHint = RandomInt( 0, freeAtFrontHintVector.Count()-1 );
hint = freeAtFrontHintVector[ whichHint ];
}
if ( pFoundNest )
{
*pFoundNest = hint;
}
}
return hint != NULL;
}
//--------------------------------------------------------------------------------------------------------------
CON_COMMAND_F( tf_bot_mvm_show_engineer_hint_region, "Show the nav areas MvM engineer bots will consider when selecting sentry and teleporter hints", FCVAR_CHEAT )
{
if ( !UTIL_IsCommandIssuedByServerAdmin() )
return;
CBasePlayer *pPlayer = UTIL_GetCommandClient();
trace_t result;
Vector forward;
pPlayer->EyeVectors( &forward );
UTIL_TraceLine( pPlayer->EyePosition(),
pPlayer->EyePosition() + forward * 10000.0f, MASK_SOLID,
pPlayer, COLLISION_GROUP_NONE, &result );
float flDrawTime = 5.0f;
if ( result.DidHit() )
{
CTFNavArea *area = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( result.endpos );
if ( area )
{
float battlefront = area->GetTravelDistanceToBombTarget();
float maxBattlefront = battlefront + tf_bot_engineer_mvm_sentry_hint_bomb_backward_range.GetFloat();
float minBattlefront = battlefront - tf_bot_engineer_mvm_sentry_hint_bomb_forward_range.GetFloat();
CUtlVector< CTFNavArea * > battlefrontAreaVector;
TheTFNavMesh()->CollectAreaWithinBombTravelRange( &battlefrontAreaVector, minBattlefront, maxBattlefront );
CUtlVector< CTFNavArea * > hintAreaVector;
for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i )
{
CBaseTFBotHintEntity *pHint = static_cast< CBaseTFBotHintEntity* >( ITFBotHintEntityAutoList::AutoList()[i] );
hintAreaVector.AddToTail( (CTFNavArea*)TheNavMesh->GetNearestNavArea( pHint ) );
}
for( int i=0; i<battlefrontAreaVector.Count(); ++i )
{
CTFNavArea *fillArea = battlefrontAreaVector[i];
if ( fillArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE || TF_NAV_SPAWN_ROOM_RED ) )
{
continue;
}
fillArea->DrawFilled( 255, 100, 0, 0, flDrawTime );
for ( int j=0; j<hintAreaVector.Count(); ++j )
{
if ( fillArea == hintAreaVector[j] )
{
CBaseTFBotHintEntity *pHint = static_cast< CBaseTFBotHintEntity* >( ITFBotHintEntityAutoList::AutoList()[j] );
Color color;
if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_SENTRYGUN ) )
{
color = Color( 0, 255, 0 );
}
else if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_TELEPORTER_EXIT ) )
{
color = Color( 0, 0, 255 );
}
else
{
bool bTooCloseToBomb = VectorLength( result.endpos - pHint->GetAbsOrigin() ) < tf_bot_engineer_mvm_hint_min_distance_from_bomb.GetFloat();
color = bTooCloseToBomb ? Color( 255, 0, 0 ) : Color( 255, 255, 0 );
}
NDebugOverlay::Sphere( pHint->GetAbsOrigin(), 50, color.r(), color.g(), color.b(), true, flDrawTime );
}
}
}
NDebugOverlay::Sphere( result.endpos, tf_bot_engineer_mvm_hint_min_distance_from_bomb.GetFloat(), 255, 255, 0, false, flDrawTime );
}
}
}
@@ -0,0 +1,55 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// Michael Booth, September 2012
#ifndef TF_BOT_MVM_ENGINEER_IDLE_H
#define TF_BOT_MVM_ENGINEER_IDLE_H
#include "Path/NextBotPathFollow.h"
class CBaseTFBotHintEntity;
class CTFBotHintSentrygun;
class CTFBotHintTeleporterExit;
class CTFBotHintEngineerNest;
class CTFBotMvMEngineerIdle : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const;
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual const char *GetName( void ) const { return "MvMEngineerIdle"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
CountdownTimer m_sentryInjuredTimer;
CountdownTimer m_sentryRebuildTimer;
CountdownTimer m_teleporterRebuildTimer;
CountdownTimer m_findHintTimer;
CountdownTimer m_reevaluateNestTimer;
int m_nTeleportedCount;
bool m_bTeleportedToHint;
CHandle< CTFBotHintTeleporterExit > m_teleporterHint;
CHandle< CTFBotHintSentrygun > m_sentryHint;
CHandle< CTFBotHintEngineerNest > m_nestHint;
void TakeOverStaleNest( CBaseTFBotHintEntity* pHint, CTFBot *me );
bool ShouldAdvanceNestSpot( CTFBot *me );
void TryToDetonateStaleNest();
bool m_bTriedToDetonateStaleNest;
};
class CTFBotMvMEngineerHintFinder
{
public:
static bool FindHint( bool bShouldCheckForBlockingObjects, bool bAllowOutOfRangeNest, CHandle< CTFBotHintEngineerNest >* pFoundNest = NULL );
};
#endif // TF_BOT_MVM_ENGINEER_IDLE_H
@@ -0,0 +1,95 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_gamerules.h"
#include "bot/tf_bot.h"
#include "tf_obj_sentrygun.h"
#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h"
#include "bot/map_entities/tf_bot_hint_entity.h"
#include "string_t.h"
#include "tf_fx.h"
#include "player_vs_environment/tf_population_manager.h"
//---------------------------------------------------------------------------------------------
CTFBotMvMEngineerTeleportSpawn::CTFBotMvMEngineerTeleportSpawn( CBaseTFBotHintEntity* pHint, bool bFirstTeleportSpawn )
{
m_hintEntity = pHint;
m_bFirstTeleportSpawn = bFirstTeleportSpawn;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerTeleportSpawn::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
if ( !me->HasAttribute( CTFBot::TELEPORT_TO_HINT ) )
{
return Done( "Cannot teleport to hint with out Attributes TeleportToHint" );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotMvMEngineerTeleportSpawn::Update( CTFBot *me, float interval )
{
if ( !m_teleportDelay.HasStarted() )
{
m_teleportDelay.Start( 0.1f );
if ( m_hintEntity )
TFGameRules()->PushAllPlayersAway( m_hintEntity->GetAbsOrigin(), 400, 500, TF_TEAM_RED );
}
else if ( m_teleportDelay.IsElapsed() )
{
if ( !m_hintEntity )
return Done( "Cannot teleport to hint as m_hintEntity is NULL" );
// teleport the engineer to the sentry spawn point
QAngle angles = m_hintEntity->GetAbsAngles();
Vector origin = m_hintEntity->GetAbsOrigin();
origin.z += 10.f; // move up off the around a little bit to prevent the engineer from getting stuck in the ground
me->Teleport( &origin, &angles, NULL );
CPVSFilter filter( origin );
TE_TFParticleEffect( filter, 0.0, "teleported_blue", origin, vec3_angle );
TE_TFParticleEffect( filter, 0.0, "player_sparkles_blue", origin, vec3_angle );
if ( m_bFirstTeleportSpawn )
{
// notify players that engineer's teleported into the map
TE_TFParticleEffect( filter, 0.0, "teleported_mvm_bot", origin, vec3_angle );
me->EmitSound( "Engineer.MVM_BattleCry07" );
m_hintEntity->EmitSound( "MVM.Robot_Engineer_Spawn" );
if ( g_pPopulationManager )
{
CWave *pWave = g_pPopulationManager->GetCurrentWave();
if ( pWave )
{
if ( pWave->NumEngineersTeleportSpawned() == 0 )
{
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_First_Engineer_Teleport_Spawned" );
}
else
{
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Another_Engineer_Teleport_Spawned" );
}
pWave->IncrementEngineerTeleportSpawned();
}
}
}
return Done( "Teleported" );
}
return Continue();
}
@@ -0,0 +1,28 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#ifndef TF_BOT_MVM_ENGINEER_TELEPORT_SPAWN_H
#define TF_BOT_MVM_ENGINEER_TELEPORT_SPAWN_H
class CBaseTFBotHintEntity;
class CTFBotMvMEngineerTeleportSpawn : public Action< CTFBot >
{
public:
CTFBotMvMEngineerTeleportSpawn( CBaseTFBotHintEntity* pHint, bool bFirstTeleportSpawn );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual const char *GetName( void ) const { return "MvMEngineerTeleportSpawn"; };
private:
CountdownTimer m_teleportDelay;
CHandle< CBaseTFBotHintEntity > m_hintEntity;
bool m_bFirstTeleportSpawn;
};
#endif // TF_BOT_MVM_ENGINEER_TELEPORT_SPAWN_H
@@ -0,0 +1,118 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build.cpp
// Engineer building his buildings
// Michael Booth, February 2009
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_build.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "raid/tf_raid_logic.h"
// this was useful when engineers build at their normal (slow) rate to make sure initial sentries get built in time
ConVar tf_raid_engineer_infinte_metal( "tf_raid_engineer_infinte_metal", "1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
//---------------------------------------------------------------------------------------------
Action< CTFBot > *CTFBotEngineerBuild::InitialContainedAction( CTFBot *me )
{
if ( TFGameRules()->IsPVEModeActive() )
{
return new CTFBotEngineerMoveToBuild;
}
return new CTFBotEngineerBuildTeleportEntrance;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuild::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuild::Update( CTFBot *me, float interval )
{
if ( TFGameRules()->IsPVEModeActive() && tf_raid_engineer_infinte_metal.GetBool() )
{
// infinite ammo
me->GiveAmmo( 1000, TF_AMMO_METAL, true );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuild::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerBuild::OnTerritoryLost( CTFBot *me, int territoryID )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
// Hack to disable ammo/health gathering elsewhere
QueryResultType CTFBotEngineerBuild::ShouldHurry( const INextBot *meBot ) const
{
CTFBot *me = (CTFBot *)meBot->GetEntity();
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER );
if ( mySentry && myDispenser && !mySentry->IsBuilding() && !myDispenser->IsBuilding() && me->GetActiveTFWeapon() && me->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_WRENCH )
{
if ( me->IsAmmoLow() && myDispenser->GetAvailableMetal() <= 0 )
{
// we're totally out of metal - collect some nearby
return ANSWER_NO;
}
// by being in a "hurry" we wont collect health and ammo
return ANSWER_YES;
}
return ANSWER_UNDEFINED;
}
//---------------------------------------------------------------------------------------------
QueryResultType CTFBotEngineerBuild::ShouldAttack( const INextBot *meBot, const CKnownEntity *them ) const
{
CTFBot *me = (CTFBot *)meBot->GetEntity();
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
CTFPlayer *themPlayer = ToTFPlayer( them->GetEntity() );
if ( themPlayer && themPlayer->IsPlayerClass( TF_CLASS_SPY ) )
{
// Engineers hate Spies
return ANSWER_YES;
}
if ( mySentry && me->IsRangeLessThan( mySentry, 100.0f ) )
{
// focus on keeping our sentry alive
return ANSWER_NO;
}
return ANSWER_UNDEFINED;
}
@@ -0,0 +1,31 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build.h
// Engineer building his buildings
// Michael Booth, February 2009
#ifndef TF_BOT_ENGINEER_BUILD_H
#define TF_BOT_ENGINEER_BUILD_H
class CTFBotHintTeleporterExit;
class CTFBotEngineerBuild : public Action< CTFBot >
{
public:
virtual Action< CTFBot > *InitialContainedAction( CTFBot *me );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"?
virtual const char *GetName( void ) const { return "EngineerBuild"; };
};
#endif // TF_BOT_ENGINEER_BUILD_H
@@ -0,0 +1,212 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_dispenser.cpp
// Engineer building his Dispenser near his Sentry
// Michael Booth, May 2010
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_dispenser.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "bot/behavior/tf_bot_get_ammo.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildDispenser::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_placementTriesLeft = 3;
return Continue();
}
//---------------------------------------------------------------------------------------------
class PressFireButtonIfValidBuildPositionReply : public INextBotReply
{
public:
PressFireButtonIfValidBuildPositionReply( void )
{
m_builder = NULL;
}
void SetBuilder( CTFWeaponBuilder *builder )
{
m_builder = builder;
}
// invoked when process completed successfully
virtual void OnSuccess( INextBot *bot )
{
if ( m_builder != NULL && m_builder->IsValidPlacement() )
{
INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
if ( playerInput )
{
playerInput->PressFireButton();
}
}
}
CTFWeaponBuilder *m_builder;
};
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildDispenser::Update( CTFBot *me, float interval )
{
if ( me->GetTimeSinceLastInjury() < 1.0f )
{
return Done( "Ouch! I'm under attack" );
}
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
if ( !mySentry )
{
return Done( "No Sentry" );
}
if ( mySentry->GetTimeSinceLastInjury() < 1.0f || mySentry->GetHealth() < mySentry->GetMaxHealth() )
{
return Done( "Need to repair my Sentry" );
}
CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER );
if ( myDispenser )
{
return Done( "Dispenser built" );
}
if ( m_placementTriesLeft <= 0 )
{
return Done( "Can't find a place to build a Dispenser" );
}
if ( me->CanBuild( OBJ_DISPENSER ) == CB_NEED_RESOURCES )
{
if ( m_getAmmoTimer.IsElapsed() && CTFBotGetAmmo::IsPossible( me ) )
{
// need more metal - get some
m_getAmmoTimer.Start( 1.0f );
return SuspendFor( new CTFBotGetAmmo, "Need more metal to build" );
}
/*
else
{
// work on my sentry while I wait for ammo to show up
me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on sentry while I wait for ammo to show up" );
me->PressFireButton();
return Continue();
}
*/
}
/*
// if my sentry is under attack, forgo building a dispenser - focus on keeping the sentry alive
if ( mySentry->GetTimeSinceLastInjury() < 1.0f )
{
CBaseCombatWeapon *wrench = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
if ( wrench )
{
me->Weapon_Switch( wrench );
}
me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Focusing on keeping my besieged sentry alive" );
me->PressFireButton();
return Continue();
}
*/
// move behind the Sentry (our chosen build location)
Vector buildSpot = mySentry->GetAbsOrigin() - 75.0f * mySentry->BodyDirection2D();
// the ground might be steeply sloped (ie: stairs), so find the actual ground
buildSpot.z += HumanHeight;
TheNavMesh->GetSimpleGroundHeight( buildSpot, &buildSpot.z );
if ( me->IsDistanceBetweenLessThan( buildSpot, 100.0f ) )
{
// crouch as we get close so we slow down and hit our mark
me->PressCrouchButton();
}
// if too far away from our build location, move closer
if ( me->IsDistanceBetweenGreaterThan( buildSpot, 25.0f ) )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, buildSpot, cost );
}
m_path.Update( me );
return Continue();
}
// we're at our build spot behind our sentry now - build a Dispenser
CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( me->GetActiveTFWeapon() );
if ( !builder || builder->GetType() != OBJ_DISPENSER || builder->m_hObjectBeingBuilt == NULL )
{
// at home position, build the object
me->StartBuildingObjectOfType( OBJ_DISPENSER );
}
else if ( m_searchTimer.IsElapsed() )
{
// rotate to find valid spot
Vector toSentry = mySentry->GetAbsOrigin() - me->GetAbsOrigin();
toSentry.NormalizeInPlace();
Vector forward = -toSentry;
float angle = RandomFloat( -M_PI/2.0f, M_PI/2.0f );
float s, c;
FastSinCos( angle, &s, &c );
forward.x = toSentry.x * c - toSentry.y * s;
forward.y = toSentry.x * s + toSentry.y * c;
forward.z = 0.0f;
static PressFireButtonIfValidBuildPositionReply buildReply;
buildReply.SetBuilder( builder );
me->GetBodyInterface()->AimHeadTowards( me->EyePosition() - 100.0f * forward, IBody::CRITICAL, 1.0f, &buildReply, "Trying to place my dispenser" );
m_searchTimer.Start( 1.0f );
--m_placementTriesLeft;
}
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotEngineerBuildDispenser::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
me->GetBodyInterface()->ClearPendingAimReply();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildDispenser::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_path.Invalidate();
m_repathTimer.Invalidate();
me->GetBodyInterface()->ClearPendingAimReply();
return Continue();
}
@@ -0,0 +1,31 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_dispenser.h
// Engineer building his Dispenser near his Sentry
// Michael Booth, May 2010
#ifndef TF_BOT_ENGINEER_BUILD_DISPENSER_H
#define TF_BOT_ENGINEER_BUILD_DISPENSER_H
class CTFBotEngineerBuildDispenser : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual void OnEnd( CTFBot *me, Action< CTFBot > *nextAction );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual const char *GetName( void ) const { return "EngineerBuildDispenser"; };
private:
CountdownTimer m_searchTimer;
CountdownTimer m_getAmmoTimer;
CountdownTimer m_repathTimer;
int m_placementTriesLeft;
PathFollower m_path;
};
#endif // TF_BOT_ENGINEER_BUILD_DISPENSER_H
@@ -0,0 +1,194 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_sentrygun.cpp
// Engineer building his Sentry gun
// Michael Booth, May 2010
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h"
#include "bot/behavior/tf_bot_get_ammo.h"
#include "bot/map_entities/tf_bot_hint_sentrygun.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuildSentryGun::CTFBotEngineerBuildSentryGun( void )
{
m_sentryBuildHint = NULL;
}
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuildSentryGun::CTFBotEngineerBuildSentryGun( CTFBotHintSentrygun *sentryBuildHint )
{
m_sentryBuildHint = sentryBuildHint;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildSentryGun::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_sentryTriesLeft = 5;
m_giveUpTimer.Invalidate();
m_searchTimer.Invalidate();
m_wanderWay = 1;
m_needToAimSentry = true;
m_sentryBuildLocation = ( m_sentryBuildHint == NULL ) ? me->GetAbsOrigin() : m_sentryBuildHint->GetAbsOrigin();
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildSentryGun::Update( CTFBot *me, float interval )
{
if ( me->GetTimeSinceLastInjury() < 1.0f )
{
return Done( "Ouch! I'm under attack" );
}
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
if ( mySentry )
{
return Done( "Sentry built" );
}
// collect metal as we move to our build location
if ( me->CanBuild( OBJ_SENTRYGUN ) == CB_NEED_RESOURCES )
{
if ( m_getAmmoTimer.IsElapsed() && CTFBotGetAmmo::IsPossible( me ) )
{
// need more metal - get some
m_getAmmoTimer.Start( 1.0f );
return SuspendFor( new CTFBotGetAmmo, "Need more metal to build my Sentry" );
}
}
// various interruptions could mean we're away from our build location - move to it
if ( me->IsRangeGreaterThan( m_sentryBuildLocation, 25.0f ) )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, m_sentryBuildLocation, cost );
}
m_path.Update( me );
if ( !m_path.IsValid() )
{
return Done( "Path failed" );
}
return Continue();
}
// we are at our build location
if ( m_sentryTriesLeft <= 0 )
{
// couldn't build here
return Done( "Couldn't find a place to build" );
}
// attempt to build a Sentry here
if ( m_sentryBuildHint != NULL )
{
// directly create a sentry gun at the precise position and orientation desired
CObjectSentrygun *mySentry = (CObjectSentrygun *)CreateEntityByName( "obj_sentrygun" );
if ( mySentry )
{
m_sentryBuildHint->IncrementUseCount();
mySentry->SetAbsOrigin( m_sentryBuildHint->GetAbsOrigin() );
mySentry->SetAbsAngles( QAngle( 0, m_sentryBuildHint->GetAbsAngles().y, 0 ) );
mySentry->Spawn();
mySentry->StartPlacement( me );
mySentry->StartBuilding( me );
}
}
else
{
// no precise build location - go through the normal build process
CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( me->GetActiveTFWeapon() );
if ( !builder || builder->GetType() != OBJ_SENTRYGUN || builder->m_hObjectBeingBuilt == NULL )
{
// at home position, build a sentry (switch to the sentry builder "gun")
me->StartBuildingObjectOfType( OBJ_SENTRYGUN );
return Continue();
}
// orient sentry towards where enemies enter this region
if ( m_needToAimSentry )
{
CTFNavArea *myArea = (CTFNavArea *)me->GetLastKnownArea();
if ( myArea )
{
CUtlVector< CTFNavArea * > invasionVector;
myArea->GetEnemyInvasionAreaVector( me->GetTeamNumber() );
if ( invasionVector.Count() > 0 )
{
// orient sentry towards where enemies enter this region
int which = RandomInt( 0, invasionVector.Count()-1 );
me->GetBodyInterface()->AimHeadTowards( invasionVector[ which ]->GetCenter(), IBody::CRITICAL, 1.0f, NULL, "Sentry build orientation" );
m_needToAimSentry = false;
}
}
}
if ( me->GetBodyInterface()->IsHeadSteady() )
{
if ( builder->IsValidPlacement() )
{
// build the sentry
me->PressFireButton();
}
else
{
// move around a bit to find valid spot
if ( m_searchTimer.IsElapsed() )
{
m_wanderWay = RandomInt( 0, 3 );
m_needToAimSentry = true;
m_searchTimer.Start( RandomFloat( 0.1f, 0.25f ) );
--m_sentryTriesLeft;
}
switch( m_wanderWay )
{
case 0: me->PressForwardButton(); break;
case 1: me->PressBackwardButton(); break;
case 2: me->PressRightButton(); break;
case 3: me->PressLeftButton(); break;
}
}
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildSentryGun::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_path.Invalidate();
m_repathTimer.Invalidate();
return Continue();
}
@@ -0,0 +1,44 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_sentrygun.h
// Engineer building his Sentry gun
// Michael Booth, May 2010
#ifndef TF_BOT_ENGINEER_BUILD_SENTRYGUN_H
#define TF_BOT_ENGINEER_BUILD_SENTRYGUN_H
class CTFBotHintSentrygun;
class CTFBotEngineerBuildSentryGun : public Action< CTFBot >
{
public:
CTFBotEngineerBuildSentryGun( void );
CTFBotEngineerBuildSentryGun( CTFBotHintSentrygun *sentryBuildHint );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual const char *GetName( void ) const { return "EngineerBuildSentryGun"; };
private:
CountdownTimer m_searchTimer;
CountdownTimer m_giveUpTimer;
CountdownTimer m_getAmmoTimer;
CountdownTimer m_repathTimer;
CountdownTimer m_buildTeleporterExitTimer;
int m_sentryTriesLeft;
PathFollower m_path;
CTFBotHintSentrygun *m_sentryBuildHint;
Vector m_sentryBuildLocation;
int m_wanderWay;
bool m_needToAimSentry;
Vector m_sentryBuildAimTarget;
};
#endif // TF_BOT_ENGINEER_BUILD_SENTRYGUN_H
@@ -0,0 +1,112 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_teleport_entrance.cpp
// Engineer building a teleport entrance right outside of the spawn room
// Michael Booth, May 2009
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "bot/behavior/tf_bot_get_ammo.h"
extern ConVar tf_bot_path_lookahead_range;
ConVar tf_bot_max_teleport_entrance_travel( "tf_bot_max_teleport_entrance_travel", "1500", FCVAR_CHEAT, "Don't plant teleport entrances farther than this travel distance from our spawn room" );
ConVar tf_bot_teleport_build_surface_normal_limit( "tf_bot_teleport_build_surface_normal_limit", "0.99", FCVAR_CHEAT, "If the ground normal Z component is less that this value, Engineer bots won't place their entrance teleporter" );
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildTeleportEntrance::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildTeleportEntrance::Update( CTFBot *me, float interval )
{
CTeamControlPoint *point = me->GetMyControlPoint();
if ( !point )
{
// wait until a control point becomes available
return Continue();
}
CTFNavArea *myArea = me->GetLastKnownArea();
if ( !myArea )
{
return Done( "No nav mesh!" );
}
if ( myArea->GetIncursionDistance( me->GetTeamNumber() ) > tf_bot_max_teleport_entrance_travel.GetFloat() )
{
return ChangeTo( new CTFBotEngineerMoveToBuild, "Too far from our spawn room to build teleporter entrance" );
}
// make sure we go back to our resupply cabinet after planting the teleporter entrance before we move on
if ( !me->IsAmmoFull() && CTFBotGetAmmo::IsPossible( me ) )
{
return SuspendFor( new CTFBotGetAmmo, "Refilling ammo" );
}
CBaseObject *myTeleportEntrance = me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE );
if ( myTeleportEntrance )
{
// successfully built
return ChangeTo( new CTFBotEngineerMoveToBuild, "Teleport entrance built" );
}
// head towards the control point and build as soon as we can
if ( !m_path.IsValid() )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, point->GetAbsOrigin(), cost );
}
m_path.Update( me );
// build
CTFWeaponBase *myGun = me->GetActiveTFWeapon();
if ( myGun )
{
CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( myGun );
if ( builder )
{
// don't build on slopes - causes player blockages
Vector forward;
me->EyeVectors( &forward );
const float placementRange = 30.0f;
forward *= placementRange;
trace_t result;
UTIL_TraceLine( me->WorldSpaceCenter() + Vector( forward.x, forward.y, 0.0f ), me->WorldSpaceCenter() + Vector( forward.x, forward.y, -200.0f ), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
if ( builder->IsValidPlacement() && result.DidHit() && result.plane.normal.z > tf_bot_teleport_build_surface_normal_limit.GetFloat() )
{
// place it down
me->PressFireButton();
}
}
else
{
// switch to teleporter builder
me->StartBuildingObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerBuildTeleportEntrance::OnStuck( CTFBot *me )
{
m_path.Invalidate();
return TryContinue();
}
@@ -0,0 +1,23 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_teleport_entrance.h
// Engineer building a teleport entrance right outside of the spawn room
// Michael Booth, May 2009
#ifndef TF_BOT_ENGINEER_BUILD_TELEPORT_ENTRANCE_H
#define TF_BOT_ENGINEER_BUILD_TELEPORT_ENTRANCE_H
class CTFBotEngineerBuildTeleportEntrance : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual const char *GetName( void ) const { return "EngineerBuildTeleportEntrance"; };
private:
PathFollower m_path;
};
#endif // TF_BOT_ENGINEER_BUILD_TELEPORT_ENTRANCE_H
@@ -0,0 +1,190 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_teleport_exit.cpp
// Engineer building a teleport exit
// Michael Booth, May 2010
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_weapon_builder.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h"
#include "bot/behavior/tf_bot_get_ammo.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuildTeleportExit::CTFBotEngineerBuildTeleportExit( void )
{
m_hasPreciseBuildLocation = false;
}
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuildTeleportExit::CTFBotEngineerBuildTeleportExit( const Vector &buildLocation, float buildAngle )
{
m_hasPreciseBuildLocation = true;
m_buildLocation = buildLocation;
m_buildAngle = buildAngle;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildTeleportExit::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
if ( !m_hasPreciseBuildLocation )
{
// if no specific build location given, just build right where we are
m_buildLocation = me->GetAbsOrigin();
}
m_giveUpTimer.Start( 3.1f );
m_path.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildTeleportExit::Update( CTFBot *me, float interval )
{
if ( me->GetTimeSinceLastInjury() < 1.0f )
{
return Done( "Ouch! I'm under attack" );
}
CBaseObject *myTeleportEntrance = me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT );
if ( myTeleportEntrance )
{
// successfully built
return Done( "Teleport exit built" );
}
// collect metal as we move to our build location
if ( me->CanBuild( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT ) == CB_NEED_RESOURCES )
{
if ( m_getAmmoTimer.IsElapsed() && CTFBotGetAmmo::IsPossible( me ) )
{
// need more metal - get some
m_getAmmoTimer.Start( 1.0f );
return SuspendFor( new CTFBotGetAmmo, "Need more metal to build my Teleporter Exit" );
}
}
// move near our build position
const float buildRange = 50.0f;
if ( me->IsRangeGreaterThan( m_buildLocation, buildRange ) )
{
// move into position
if ( !m_path.IsValid() || m_repathTimer.IsElapsed() )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, m_buildLocation, cost );
m_repathTimer.Start( RandomFloat( 2.0f, 3.0f ) );
}
m_path.Update( me );
// don't give up until we've reached our build location
m_giveUpTimer.Reset();
return Continue();
}
// in position to build
if ( m_giveUpTimer.IsElapsed() )
{
return Done( "Taking too long - giving up" );
}
if ( m_hasPreciseBuildLocation )
{
me->GetBodyInterface()->AimHeadTowards( m_buildLocation, IBody::CRITICAL, 1.0f, NULL, "Looking toward my precise build location" );
// directly create a teleporter exit at the precise position and orientation desired
CObjectTeleporter *myTeleporterExit = (CObjectTeleporter *)CreateEntityByName( "obj_teleporter" );
if ( myTeleporterExit )
{
myTeleporterExit->SetObjectMode( MODE_TELEPORTER_EXIT );
myTeleporterExit->SetAbsOrigin( m_buildLocation );
myTeleporterExit->SetAbsAngles( QAngle( 0, m_buildAngle, 0 ) );
myTeleporterExit->Spawn();
myTeleporterExit->StartPlacement( me );
myTeleporterExit->StartBuilding( me );
myTeleporterExit->SetBuilder( me );
// teleporter exits are solid blockers - put engineer on top of exit or he'll be stuck
Vector myNewOrigin = me->GetAbsOrigin();
myNewOrigin.z += me->GetLocomotionInterface()->GetStepHeight();
me->SetAbsOrigin( myNewOrigin );
return Done( "Teleport exit built at precise location" );
}
return Continue();
}
// build exit roughly at this spot
CTFWeaponBase *myGun = me->GetActiveTFWeapon();
if ( myGun )
{
CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( myGun );
if ( builder )
{
if ( builder->IsValidPlacement() )
{
// place it down
me->PressFireButton();
}
else if ( m_searchTimer.IsElapsed() )
{
// rotate to find valid spot
Vector forward;
float angle = RandomFloat( -M_PI, M_PI );
FastSinCos( angle, &forward.y, &forward.x );
forward.z = 0.0f;
me->GetBodyInterface()->AimHeadTowards( me->EyePosition() - 100.0f * forward, IBody::CRITICAL, 1.0f, NULL, "Trying to place my teleport exit" );
m_searchTimer.Start( 1.0f );
}
}
else
{
// switch to teleporter builder
me->StartBuildingObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuildTeleportExit::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
m_giveUpTimer.Reset();
m_path.Invalidate();
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerBuildTeleportExit::OnStuck( CTFBot *me )
{
m_path.Invalidate();
return TryContinue();
}
@@ -0,0 +1,36 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_build_teleport_exit.h
// Engineer building a teleport exit
// Michael Booth, May 2010
#ifndef TF_BOT_ENGINEER_BUILD_TELEPORT_EXIT_H
#define TF_BOT_ENGINEER_BUILD_TELEPORT_EXIT_H
class CTFBotEngineerBuildTeleportExit : public Action< CTFBot >
{
public:
CTFBotEngineerBuildTeleportExit( void );
CTFBotEngineerBuildTeleportExit( const Vector &buildLocation, float buildAngle );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual const char *GetName( void ) const { return "EngineerBuildTeleportExit"; };
private:
PathFollower m_path;
bool m_hasPreciseBuildLocation;
Vector m_buildLocation;
float m_buildAngle;
CountdownTimer m_giveUpTimer;
CountdownTimer m_repathTimer;
CountdownTimer m_getAmmoTimer;
CountdownTimer m_searchTimer;
};
#endif // TF_BOT_ENGINEER_BUILD_TELEPORT_EXIT_H
@@ -0,0 +1,497 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_building.cpp
// At building location, constructing buildings
// Michael Booth, May 2010
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dispenser.h"
#include "tf_gamerules.h"
#include "tf_weapon_builder.h"
#include "team_train_watcher.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_building.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_dispenser.h"
#include "bot/behavior/tf_bot_attack.h"
#include "bot/behavior/tf_bot_get_ammo.h"
#include "bot/map_entities/tf_bot_hint_teleporter_exit.h"
#include "bot/map_entities/tf_bot_hint_sentrygun.h"
#include "NextBotUtil.h"
ConVar tf_bot_engineer_retaliate_range( "tf_bot_engineer_retaliate_range", "750", FCVAR_CHEAT, "If attacker who destroyed sentry is closer than this, attack. Otherwise, retreat" );
ConVar tf_bot_engineer_exit_near_sentry_range( "tf_bot_engineer_exit_near_sentry_range", "2500", FCVAR_CHEAT, "Maximum travel distance between a bot's Sentry gun and its Teleporter Exit" );
ConVar tf_bot_engineer_max_sentry_travel_distance_to_point( "tf_bot_engineer_max_sentry_travel_distance_to_point", "2500", FCVAR_CHEAT, "Maximum travel distance between a bot's Sentry gun and the currently contested point" );
extern ConVar tf_bot_path_lookahead_range;
const int MaxPlacementAttempts = 5;
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuilding::CTFBotEngineerBuilding( void )
{
m_sentryBuildHint = NULL;
}
//---------------------------------------------------------------------------------------------
CTFBotEngineerBuilding::CTFBotEngineerBuilding( CTFBotHintSentrygun *sentryBuildHint )
{
m_sentryBuildHint = sentryBuildHint;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuilding::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_sentryTriesLeft = MaxPlacementAttempts;
m_territoryRangeTimer.Invalidate();
m_hasBuiltSentry = false;
m_isSentryOutOfPosition = false;
m_nearbyMetalStatus = NEARBY_METAL_UNKNOWN;
return Continue();
}
//---------------------------------------------------------------------------------------------
// Everything is built, upgrade/maintain it
// TODO: Upgrade/maintain nearby friendly buildings, too.
void CTFBotEngineerBuilding::UpgradeAndMaintainBuildings( CTFBot *me )
{
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER );
if ( !mySentry )
{
return;
}
CBaseCombatWeapon *wrench = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
if ( wrench )
{
me->Weapon_Switch( wrench );
}
const float tooFarRange = 75.0f;
if ( !myDispenser )
{
// just work on our sentry
float rangeToSentry = me->GetDistanceBetween( mySentry );
if ( rangeToSentry < 1.2f * tooFarRange )
{
// crouch both for cover behind our buildings, but also to slow us down so we hit our move goal more accurately
me->PressCrouchButton();
}
if ( rangeToSentry > tooFarRange )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, mySentry->GetAbsOrigin(), cost );
}
m_path.Update( me );
}
else
{
// we are in position - work on our buildings
me->StopLookingAroundForEnemies();
me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my Sentry" );
me->PressFireButton();
}
return;
}
// sit near both buildings
Vector betweenMyBuildings = ( mySentry->GetAbsOrigin() + myDispenser->GetAbsOrigin() ) / 2.0f;
// try to equalize distance between both
float rangeToSentry = me->GetDistanceBetween( mySentry );
float rangeToDispenser = me->GetDistanceBetween( myDispenser );
const float equalTolerance = 25.0f;
if ( rangeToSentry < 1.2f * tooFarRange && rangeToDispenser < 1.2f * tooFarRange )
{
// crouch both for cover behind our buildings, but also to slow us down so we hit our move goal more accurately
me->PressCrouchButton();
}
if ( fabs( rangeToDispenser - rangeToSentry ) > equalTolerance || rangeToSentry > tooFarRange || rangeToDispenser > tooFarRange )
{
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, FASTEST_ROUTE );
m_path.Compute( me, betweenMyBuildings, cost );
}
m_path.Update( me );
}
if ( rangeToSentry < tooFarRange || rangeToDispenser < tooFarRange )
{
// we are (nearly) in position - work on our buildings
m_searchTimer.Invalidate();
CBaseObject *workTarget = mySentry;
if ( mySentry->HasSapper() || mySentry->IsPlasmaDisabled() )
workTarget = mySentry;
else if ( myDispenser->HasSapper() || myDispenser->IsPlasmaDisabled() )
workTarget = myDispenser;
else if ( mySentry->GetTimeSinceLastInjury() < 1.0f || mySentry->GetHealth() < mySentry->GetMaxHealth() )
workTarget = mySentry;
else if ( mySentry->IsBuilding() )
workTarget = mySentry;
else if ( myDispenser->IsBuilding() )
workTarget = myDispenser;
else if ( mySentry->GetUpgradeLevel() < 3 )
workTarget = mySentry;
else if ( myDispenser->GetHealth() < myDispenser->GetMaxHealth() )
workTarget = myDispenser;
else if ( myDispenser->GetUpgradeLevel() < mySentry->GetUpgradeLevel() )
workTarget = myDispenser;
me->StopLookingAroundForEnemies();
me->GetBodyInterface()->AimHeadTowards( workTarget->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my buildings" );
me->PressFireButton();
}
}
//---------------------------------------------------------------------------------------------
bool CTFBotEngineerBuilding::IsMetalSourceNearby( CTFBot *me ) const
{
CUtlVector< CNavArea * > nearbyVector;
CollectSurroundingAreas( &nearbyVector, me->GetLastKnownArea(), 2000.0f, me->GetLocomotionInterface()->GetStepHeight(), me->GetLocomotionInterface()->GetStepHeight() );
for( int i=0; i<nearbyVector.Count(); ++i )
{
CTFNavArea *area = (CTFNavArea *)nearbyVector[i];
if ( area->HasAttributeTF( TF_NAV_HAS_AMMO ) )
{
return true;
}
// this assumes all spawn rooms have resupply cabinets
if ( me->GetTeamNumber() == TF_TEAM_RED && area->HasAttributeTF( TF_NAV_SPAWN_ROOM_RED ) )
{
return true;
}
if ( me->GetTeamNumber() == TF_TEAM_BLUE && area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) )
{
return true;
}
}
return false;
}
//---------------------------------------------------------------------------------------------
bool CTFBotEngineerBuilding::CheckIfSentryIsOutOfPosition( CTFBot *me ) const
{
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
if ( !mySentry )
{
return false;
}
// payload
if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT )
{
CTeamTrainWatcher *trainWatcher;
if ( me->GetTeamNumber() == TF_TEAM_BLUE )
{
trainWatcher = TFGameRules()->GetPayloadToPush( me->GetTeamNumber() );
}
else
{
trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() );
}
if ( trainWatcher )
{
float sentryDistanceAlongPath;
trainWatcher->ProjectPointOntoPath( mySentry->GetAbsOrigin(), NULL, &sentryDistanceAlongPath );
const float behindTrainTolerance = SENTRY_MAX_RANGE;
return ( trainWatcher->GetTrainDistanceAlongTrack() > sentryDistanceAlongPath + behindTrainTolerance );
}
}
// control points
mySentry->UpdateLastKnownArea();
CNavArea *sentryArea = mySentry->GetLastKnownArea();
CTeamControlPoint *point = me->GetMyControlPoint();
if ( point )
{
CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() );
if ( sentryArea && pointArea )
{
CTFBotPathCost cost( me, FASTEST_ROUTE );
if ( NavAreaTravelDistance( sentryArea, pointArea, cost, tf_bot_engineer_max_sentry_travel_distance_to_point.GetFloat() ) < 0 &&
NavAreaTravelDistance( pointArea, sentryArea, cost, tf_bot_engineer_max_sentry_travel_distance_to_point.GetFloat() ) < 0 )
{
return true;
}
}
}
return false;
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuilding::Update( CTFBot *me, float interval )
{
CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN );
CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER );
CObjectTeleporter *myTeleportEntrance = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE );
CObjectTeleporter *myTeleportExit = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT );
bool isUnderAttack = ( me->GetTimeSinceLastInjury() < 1.0f );
isUnderAttack |= ( mySentry && ( mySentry->HasSapper() || mySentry->IsPlasmaDisabled() ) );
isUnderAttack |= ( myDispenser && ( myDispenser->HasSapper() || myDispenser->IsPlasmaDisabled() ) );
me->StartLookingAroundForEnemies();
// try to build a Sentry
if ( !mySentry )
{
m_nearbyMetalStatus = NEARBY_METAL_UNKNOWN;
// react to nearby threats if our sentry is down
const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
if ( threat && threat->IsVisibleRecently() )
{
me->EquipBestWeaponForThreat( threat );
}
if ( !m_hasBuiltSentry && m_sentryTriesLeft > 0 )
{
--m_sentryTriesLeft;
if ( m_sentryBuildHint )
{
return SuspendFor( new CTFBotEngineerBuildSentryGun( m_sentryBuildHint ), "Building a Sentry at a hint location" );
}
return SuspendFor( new CTFBotEngineerBuildSentryGun, "Building a Sentry" );
}
else
{
// can't build a Sentry here - pick a new place
return ChangeTo( new CTFBotEngineerMoveToBuild, "Couldn't find a place to build" );
}
}
// I have a Sentry
m_hasBuiltSentry = true;
if ( m_sentryBuildHint != NULL && !m_sentryBuildHint->IsEnabled() )
{
// our hint has been disabled and no longer has influence on our behavior
m_sentryBuildHint = NULL;
}
// periodically check that our Sentry is still near the contested point
if ( m_sentryBuildHint == NULL || !m_sentryBuildHint->IsSticky() )
{
if ( !m_isSentryOutOfPosition && m_territoryRangeTimer.IsElapsed() )
{
m_territoryRangeTimer.Start( RandomFloat( 3.0f, 5.0f ) );
m_isSentryOutOfPosition = CheckIfSentryIsOutOfPosition( me );
}
m_isSentryOutOfPosition = false;
if ( m_isSentryOutOfPosition )
{
// the point has moved, only keep sentry as long as it keeps attacking
if ( mySentry->GetTimeSinceLastFired() > 10.0f )
{
mySentry->DetonateObject();
// if we built here because of a hint, disable that hint so we don't use it and rebuild here again
if ( m_sentryBuildHint != NULL )
{
inputdata_t dummy;
m_sentryBuildHint->InputDisable( dummy );
m_sentryBuildHint = NULL;
}
if ( myDispenser )
{
myDispenser->DetonateObject();
}
if ( myTeleportExit )
{
myTeleportExit->DetonateObject();
}
me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_MOVEUP );
return ChangeTo( new CTFBotEngineerMoveToBuild, "Need to move my gear closer to the point!" );
}
}
}
// if my dispenser is too far away from my sentry, destroy and rebuild it next update
// @TODO: Flag hint-built entities for a larger range
const float maxSeparation = 500.0f;
if ( myDispenser )
{
if ( ( mySentry->GetAbsOrigin() - myDispenser->GetAbsOrigin() ).IsLengthGreaterThan( maxSeparation ) )
{
myDispenser->DestroyObject();
myDispenser = NULL;
}
}
// build up the sentry all the way if there is a metal source nearby
if ( mySentry->GetUpgradeLevel() < 3 )
{
if ( m_nearbyMetalStatus == NEARBY_METAL_UNKNOWN )
{
m_nearbyMetalStatus = IsMetalSourceNearby( me ) ? NEARBY_METAL_EXISTS : NEARBY_METAL_NONE;
}
if ( m_nearbyMetalStatus == NEARBY_METAL_EXISTS )
{
UpgradeAndMaintainBuildings( me );
return Continue();
}
}
/*
if ( myTeleportExit )
{
// if my teleporter exit is too far away from my sentry, destroy and rebuild it next update
if ( ( mySentry->GetAbsOrigin() - myTeleportExit->GetAbsOrigin() ).IsLengthGreaterThan( maxSeparation ) )
{
myTeleportExit->DestroyObject();
myTeleportExit = NULL;
}
}
*/
// try to build a Dispenser (build after tele exit in training)
if ( !TFGameRules()->IsInTraining() || myTeleportExit )
{
const float dispenserRebuildInterval = 10.0f;
if ( myDispenser )
{
// don't rebuild immediately after building is destroyed
m_dispenserRetryTimer.Start( dispenserRebuildInterval );
}
else if ( m_dispenserRetryTimer.IsElapsed() && !isUnderAttack )
{
m_dispenserRetryTimer.Start( dispenserRebuildInterval );
return SuspendFor( new CTFBotEngineerBuildDispenser, "Building a Dispenser" );
}
}
// try to build a Teleporter Exit
const float exitRebuildInterval = TFGameRules()->IsInTraining() ? 5.0f : 30.0f;
if ( myTeleportExit )
{
// don't rebuild immediately after building is destroyed
m_teleportExitRetryTimer.Start( exitRebuildInterval );
}
else if ( m_teleportExitRetryTimer.IsElapsed() && myTeleportEntrance && !isUnderAttack )
{
m_teleportExitRetryTimer.Start( exitRebuildInterval );
// we need to build a teleporter exit yet
if ( m_sentryBuildHint != NULL )
{
// if there are teleporter exit hints, find the closest one to our sentry and use it
CUtlVector< CBaseEntity * > hintVector;
CTFBotHintTeleporterExit *hint = NULL;
while( ( hint = (CTFBotHintTeleporterExit *)gEntList.FindEntityByClassname( hint, "bot_hint_teleporter_exit" ) ) != NULL )
{
if ( hint->IsEnabled() && hint->InSameTeam( me ) )
{
hintVector.AddToTail( hint );
}
}
if ( hintVector.Count() > 0 )
{
mySentry->UpdateLastKnownArea();
CBaseEntity *closeHint = SelectClosestEntityByTravelDistance( me, hintVector, mySentry->GetLastKnownArea(), tf_bot_engineer_exit_near_sentry_range.GetFloat() );
if ( closeHint )
{
return SuspendFor( new CTFBotEngineerBuildTeleportExit( closeHint->GetAbsOrigin(), closeHint->GetAbsAngles().y ), "Building teleporter exit at nearby hint" );
}
}
}
else if ( me->IsRangeLessThan( mySentry, 300.0f ) )
{
// drop a teleporter exit near our sentry
return SuspendFor( new CTFBotEngineerBuildTeleportExit(), "Building teleporter exit" );
}
}
// everything is built - maintain them
UpgradeAndMaintainBuildings( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
void CTFBotEngineerBuilding::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
{
me->StartLookingAroundForEnemies();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerBuilding::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerBuilding::OnTerritoryLost( CTFBot *me, int territoryID )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerBuilding::OnTerritoryCaptured( CTFBot *me, int territoryID )
{
return TryContinue();
}
@@ -0,0 +1,64 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_building.h
// At building location, constructing buildings
// Michael Booth, May 2010
#ifndef TF_BOT_ENGINEER_BUILDING_H
#define TF_BOT_ENGINEER_BUILDING_H
class CTFBotHintSentrygun;
class CTFBotEngineerBuilding : public Action< CTFBot >
{
public:
CTFBotEngineerBuilding( void );
CTFBotEngineerBuilding( CTFBotHintSentrygun *sentryBuildHint );
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual void OnEnd( CTFBot *me, Action< CTFBot > *nextAction );
virtual ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID );
virtual const char *GetName( void ) const { return "EngineerBuilding"; };
private:
CountdownTimer m_searchTimer;
CountdownTimer m_getAmmoTimer;
CountdownTimer m_repathTimer;
CountdownTimer m_buildTeleporterExitTimer;
int m_sentryTriesLeft;
CountdownTimer m_dispenserRetryTimer;
CountdownTimer m_teleportExitRetryTimer;
PathFollower m_path;
CHandle< CTFBotHintSentrygun > m_sentryBuildHint;
bool m_hasBuiltSentry;
enum NearbyMetalType
{
NEARBY_METAL_UNKNOWN,
NEARBY_METAL_NONE,
NEARBY_METAL_EXISTS
};
NearbyMetalType m_nearbyMetalStatus;
CountdownTimer m_territoryRangeTimer;
bool m_isSentryOutOfPosition;
bool CheckIfSentryIsOutOfPosition( CTFBot *me ) const;
void UpgradeAndMaintainBuildings( CTFBot *me );
bool IsMetalSourceNearby( CTFBot *me ) const;
};
#endif // TF_BOT_ENGINEER_BUILDING_H
@@ -0,0 +1,504 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_move_to_build.cpp
// Engineer moving into position to build
// Michael Booth, February 2009
#include "cbase.h"
#include "nav_mesh/tf_nav_mesh.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_obj_sentrygun.h"
#include "tf_weapon_builder.h"
#include "team_train_watcher.h"
#include "bot/tf_bot.h"
#include "bot/behavior/engineer/tf_bot_engineer_build.h"
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h"
#include "bot/behavior/engineer/tf_bot_engineer_building.h"
#include "bot/map_entities/tf_bot_hint_sentrygun.h"
#include "bot/behavior/tf_bot_get_ammo.h"
#include "bot/behavior/tf_bot_retreat_to_cover.h"
#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h"
#include "trigger_area_capture.h"
#include "raid/tf_raid_logic.h"
extern ConVar tf_bot_path_lookahead_range;
ConVar tf_bot_debug_sentry_placement( "tf_bot_debug_sentry_placement", "0", FCVAR_CHEAT );
ConVar tf_bot_max_teleport_exit_travel_to_point( "tf_bot_max_teleport_exit_travel_to_point", "2500", FCVAR_CHEAT, "In an offensive engineer bot's tele exit is farther from the point than this, destroy it" );
ConVar tf_bot_min_teleport_travel( "tf_bot_min_teleport_travel", "3000", FCVAR_CHEAT, "Minimum travel distance between teleporter entrance and exit before engineer bot will build one" );
//--------------------------------------------------------------------------------------------------------
static Vector s_pointCentroid;
int CompareRangeToPoint( CTFNavArea * const *area1, CTFNavArea * const *area2 )
{
float d1 = ( (*area1)->GetCenter() - s_pointCentroid ).LengthSqr();
float d2 = ( (*area2)->GetCenter() - s_pointCentroid ).LengthSqr();
// reversed so farthest is sorted first in the vector
if ( d1 < d2 )
return 1;
if ( d1 > d2 )
return -1;
return 0;
}
//---------------------------------------------------------------------------------------------
void CTFBotEngineerMoveToBuild::CollectBuildAreas( CTFBot *me )
{
// if we have a predesignated build area, we're done
if ( me->GetHomeArea() )
return;
m_sentryAreaVector.RemoveAll();
CUtlVector< CTFNavArea * > pointAreaVector;
Vector pointCentroid = vec3_origin;
float pointEnemyIncursion = 0.0f;
int i;
int myTeam = me->GetTeamNumber();
int enemyTeam = ( myTeam == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE;
CCaptureZone *zone = me->GetFlagCaptureZone();
if ( zone )
{
// NOTE: Not strictly the right thing - should defend location of our team's flag
CTFNavArea *zoneArea = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( zone->WorldSpaceCenter(), false, 500.0f, true );
if ( zoneArea )
{
pointAreaVector.AddToTail( zoneArea );
pointCentroid += zoneArea->GetCenter();
pointEnemyIncursion += zoneArea->GetIncursionDistance( enemyTeam );
}
}
else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT )
{
CTeamTrainWatcher *trainWatcher;
if ( myTeam == TF_TEAM_BLUE )
{
trainWatcher = TFGameRules()->GetPayloadToPush( me->GetTeamNumber() );
}
else
{
trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() );
}
if ( trainWatcher )
{
Vector checkpointPos = trainWatcher->GetNextCheckpointPosition();
CTFNavArea *checkpointArea = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( checkpointPos, false, 500.0f, true );
if ( checkpointArea )
{
pointAreaVector.AddToTail( checkpointArea );
pointCentroid += checkpointArea->GetCenter();
pointEnemyIncursion += checkpointArea->GetIncursionDistance( enemyTeam );
}
}
}
else
{
// collect all areas overlapping the point
CTeamControlPoint *ctrlPoint = me->GetMyControlPoint();
if ( !ctrlPoint )
return;
const CUtlVector< CTFNavArea * > *ctrlPointAreaVector = TheTFNavMesh()->GetControlPointAreas( ctrlPoint->GetPointIndex() );
if ( ctrlPointAreaVector )
{
for( i=0; i<ctrlPointAreaVector->Count(); ++i )
{
CTFNavArea *area = ctrlPointAreaVector->Element(i);
pointAreaVector.AddToTail( area );
pointCentroid += area->GetCenter();
pointEnemyIncursion += area->GetIncursionDistance( enemyTeam );
}
}
}
if ( pointAreaVector.Count() == 0 )
return;
pointCentroid /= pointAreaVector.Count();
pointEnemyIncursion /= pointAreaVector.Count();
// collect all areas that can see the point
CUtlVector< CTFNavArea * > exposedAreaVector;
for( i=0; i<pointAreaVector.Count(); ++i )
{
CTFAreaCollector collect;
pointAreaVector[i]->ForAllPotentiallyVisibleAreas( collect );
for( int j=0; j<collect.m_vector.Count(); ++j )
{
CTFNavArea *visibleArea = collect.m_vector[j];
if ( visibleArea->GetIncursionDistance( myTeam ) < 0 || visibleArea->GetIncursionDistance( enemyTeam ) < 0 )
continue;
if ( TFGameRules()->IsInKothMode() )
{
// ignore areas the enemy can reach first
if ( visibleArea->GetIncursionDistance( myTeam ) >= visibleArea->GetIncursionDistance( enemyTeam ) )
continue;
}
// incursion flow is badly behaved at cap #1, stage #2 in dustbowl
// else
// {
// if ( pointEnemyIncursion > visibleArea->GetIncursionDistance( enemyTeam ) )
// continue;
// }
if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP )
{
// don't build directly on the point
if ( visibleArea->HasAttributeTF( TF_NAV_CONTROL_POINT ) )
continue;
// ignore areas below the point
const float tooFarBelow = 150.0f;
if ( visibleArea->GetCenter().z < pointCentroid.z - tooFarBelow )
continue;
// ignore areas too far from the point for the sentry gun to reach
const float tolerance = 1.1f;
if ( ( visibleArea->GetCenter() - pointCentroid ).IsLengthGreaterThan( SENTRY_MAX_RANGE * tolerance ) )
continue;
}
// ignore areas that don't have clear line of FIRE (not sight)
const float sentryEyeHeight = 60.0f;
const float pointFlagHeight = 70.0f; // 100.0f;
if ( !me->IsLineOfFireClear( visibleArea->GetCenter() + Vector( 0, 0, sentryEyeHeight ), pointCentroid + Vector( 0, 0, pointFlagHeight ) ) )
continue;
if ( !exposedAreaVector.HasElement( visibleArea ) )
exposedAreaVector.AddToTail( visibleArea );
}
}
// keep the farthest away areas
const float keepRatio = 1.0f; // 0.5f;
s_pointCentroid = pointCentroid;
exposedAreaVector.Sort( CompareRangeToPoint );
for( i=0; i<exposedAreaVector.Count() * keepRatio; ++i )
{
CTFNavArea *usableArea = exposedAreaVector[i];
m_sentryAreaVector.AddToTail( usableArea );
}
// calculate total surface area
m_totalSurfaceArea = 0.0f;
FOR_EACH_VEC( m_sentryAreaVector, it )
{
CTFNavArea *area = m_sentryAreaVector[ it ];
m_totalSurfaceArea += area->GetSizeX() * area->GetSizeY();
if ( tf_bot_debug_sentry_placement.GetBool() )
{
TheNavMesh->AddToSelectedSet( area );
}
}
}
//---------------------------------------------------------------------------------------------
/**
* Doesn't recompute the potential areas, just reselected from the list
*/
void CTFBotEngineerMoveToBuild::SelectBuildLocation( CTFBot *me )
{
m_path.Invalidate();
m_sentryBuildHint = NULL;
m_sentryBuildLocation = vec3_origin;
// if we have a build spot, use it
if ( me->GetHomeArea() )
{
m_sentryBuildLocation = me->GetHomeArea()->GetCenter();
return;
}
// if we have a set of specific build locations, pick one of them
CUtlVector< CTFBotHintSentrygun * > sentryHintVector;
CTFBotHintSentrygun *sentryHint;
for( sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( NULL, "bot_hint_sentrygun" ) );
sentryHint;
sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( sentryHint, "bot_hint_sentrygun" ) ) )
{
// clear the previous owner if it is us
if ( sentryHint->GetPlayerOwner() == me )
{
sentryHint->SetPlayerOwner( NULL );
}
if ( sentryHint->IsAvailableForSelection( me ) )
{
sentryHintVector.AddToTail( sentryHint );
}
}
if ( sentryHintVector.Count() > 0 )
{
int which = RandomInt( 0, sentryHintVector.Count()-1 );
m_sentryBuildHint = sentryHintVector[ which ];
m_sentryBuildHint->SetPlayerOwner( me );
m_sentryBuildLocation = m_sentryBuildHint->GetAbsOrigin();
return;
}
// collect nav area candidates
CollectBuildAreas( me );
// choose based on surface area to avoid biasing finely subdivided areas of the mesh
float which = RandomFloat( 0.0f, m_totalSurfaceArea - 1.0f );
float soFar = 0.0f;
FOR_EACH_VEC( m_sentryAreaVector, sit )
{
CTFNavArea *area = m_sentryAreaVector[ sit ];
soFar += area->GetSizeX() * area->GetSizeY();
if ( which < soFar )
{
m_sentryBuildLocation = area->GetRandomPoint();
return;
}
}
if ( !HushAsserts() )
{
Assert( !"Failed to find a build location" );
}
m_sentryBuildLocation = me->GetAbsOrigin();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerMoveToBuild::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
{
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
#ifdef TF_RAID_MODE
if ( TFGameRules()->IsRaidMode() )
{
if ( me->GetHomeArea() && TFGameRules()->GetRaidLogic() )
{
// try to pick a new area
CTFNavArea *sentryArea = TFGameRules()->GetRaidLogic()->SelectRaidSentryArea();
if ( sentryArea )
{
me->SetHomeArea( sentryArea );
}
}
}
#endif // TF_RAID_MODE
SelectBuildLocation( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CTFBot > CTFBotEngineerMoveToBuild::Update( CTFBot *me, float interval )
{
if ( me->WasPointJustLost() )
{
if ( m_fallBackTimer.HasStarted() )
{
if ( m_fallBackTimer.IsElapsed() )
{
SelectBuildLocation( me );
m_fallBackTimer.Invalidate();
}
else
{
// wait a moment while we decide where to build near fallback point
return Continue();
}
}
}
CBaseObject *mySentry = me->GetObjectOfType( OBJ_SENTRYGUN );
if ( mySentry )
{
// we already have a sentry from a previous life - continue what we were doing
// if we used a sentry hint last time, reuse it
CTFBotHintSentrygun *sentryHint;
for( sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( NULL, "bot_hint_sentrygun" ) );
sentryHint;
sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( sentryHint, "bot_hint_sentrygun" ) ) )
{
if ( sentryHint->GetPlayerOwner() == me )
{
return ChangeTo( new CTFBotEngineerBuilding( sentryHint ), "Going back to my existing sentry nest and reusing a sentry hint" );
}
}
return ChangeTo( new CTFBotEngineerBuilding, "Going back to my existing sentry nest" );
}
// offensive engineers need to place a forward teleporter
if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP && !TFGameRules()->IsInKothMode() && me->GetTeamNumber() == TF_TEAM_BLUE )
{
CObjectTeleporter *myTeleportExit = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT );
int myTeam = me->GetTeamNumber();
if ( myTeleportExit )
{
// if exit is too far from the point, destroy it and try again
CTeamControlPoint *point = me->GetMyControlPoint();
if ( point )
{
CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() );
myTeleportExit->UpdateLastKnownArea();
CTFNavArea *exitArea = (CTFNavArea *)myTeleportExit->GetLastKnownArea();
if ( pointArea && exitArea )
{
float travelToPoint = fabs( exitArea->GetIncursionDistance( myTeam ) - pointArea->GetIncursionDistance( myTeam ) );
if ( travelToPoint > tf_bot_max_teleport_exit_travel_to_point.GetFloat() )
{
// too far, destroy it
myTeleportExit->DestroyObject();
myTeleportExit = NULL;
}
}
}
}
else
{
CObjectTeleporter *myTeleportEntrance = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE );
CTFNavArea *myArea = me->GetLastKnownArea();
bool shouldBuildExit = true;
// if we have a teleporter entrance, don't place the exit too close to it
if ( myTeleportEntrance && myArea )
{
myTeleportEntrance->UpdateLastKnownArea();
CTFNavArea *enterArea = (CTFNavArea *)myTeleportEntrance->GetLastKnownArea();
if ( enterArea )
{
float travelBetween = fabs( enterArea->GetIncursionDistance( myTeam ) - myArea->GetIncursionDistance( myTeam ) );
if ( travelBetween < tf_bot_min_teleport_travel.GetFloat() )
{
shouldBuildExit = false;
}
}
}
if ( shouldBuildExit )
{
// no exit yet - need to place one
// when we see the enemy, retreat to cover and build the exit there
if ( me->GetVisionInterface()->GetPrimaryKnownThreat( true ) )
{
if ( !me->m_Shared.InCond( TF_COND_INVULNERABLE ) && ShouldRetreat( me ) != ANSWER_NO )
{
Action< CTFBot > *nextActionWhenInCover = new CTFBotEngineerBuildTeleportExit;
return SuspendFor( new CTFBotRetreatToCover( nextActionWhenInCover ), "Retreating to a safe place to build my teleporter exit" );
}
}
}
}
}
// move to build position
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CTFBotPathCost cost( me, SAFEST_ROUTE );
m_path.Compute( me, m_sentryBuildLocation, cost );
}
Vector forward;
me->EyeVectors( &forward );
forward.z = 0.0f;
forward.NormalizeInPlace();
Vector myBlueprintPosition = me->GetAbsOrigin() + 50.0f * forward;
const float closeToHome = 25.0f;
Vector toBuild = m_sentryBuildLocation - myBlueprintPosition;
Vector toMe = m_sentryBuildLocation - me->GetAbsOrigin();
if ( me->GetLocomotionInterface()->IsOnGround() )
{
// we need to wait until we're on the ground since the Build action assumes our position OnStart is where we are going to build
if ( toMe.AsVector2D().IsLengthLessThan( closeToHome ) || toBuild.AsVector2D().IsLengthLessThan( closeToHome ) )
{
if ( m_sentryBuildHint != NULL )
{
return ChangeTo( new CTFBotEngineerBuilding( m_sentryBuildHint ), "Reached my precise build location" );
}
return ChangeTo( new CTFBotEngineerBuilding, "Reached my build location" );
}
m_path.Update( me );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnStuck( CTFBot *me )
{
// SelectBuildLocation( me );
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnMoveToSuccess( CTFBot *me, const Path *path )
{
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
{
SelectBuildLocation( me );
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnTerritoryLost( CTFBot *me, int territoryID )
{
// we have to wait a moment until contested point changes to select a new build spot
m_fallBackTimer.Start( 0.2f );
return TryContinue();
}
@@ -0,0 +1,43 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// tf_bot_engineer_move_to_build.h
// Engineer moving into position to build
// Michael Booth, February 2009
#ifndef TF_BOT_ENGINEER_MOVE_TO_BUILD_H
#define TF_BOT_ENGINEER_MOVE_TO_BUILD_H
#include "Path/NextBotPathFollow.h"
class CTFBotHintSentrygun;
class CTFBotEngineerMoveToBuild : public Action< CTFBot >
{
public:
virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me );
virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path );
virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason );
virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID );
virtual const char *GetName( void ) const { return "EngineerMoveToBuild"; };
private:
CHandle< CTFBotHintSentrygun > m_sentryBuildHint;
Vector m_sentryBuildLocation;
PathFollower m_path;
CountdownTimer m_repathTimer;
CUtlVector< CTFNavArea * > m_sentryAreaVector;
float m_totalSurfaceArea;
void CollectBuildAreas( CTFBot *me );
void SelectBuildLocation( CTFBot *me );
CountdownTimer m_fallBackTimer;
};
#endif // TF_BOT_ENGINEER_MOVE_TO_BUILD_H