summaryrefslogtreecommitdiff
path: root/game/server/tf/bot/behavior/engineer
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/bot/behavior/engineer')
-rw-r--r--game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.cpp123
-rw-r--r--game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h29
-rw-r--r--game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.cpp105
-rw-r--r--game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h27
-rw-r--r--game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.cpp662
-rw-r--r--game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.h55
-rw-r--r--game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.cpp95
-rw-r--r--game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h28
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.cpp118
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.h31
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.cpp212
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.h31
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.cpp194
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h44
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.cpp112
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h23
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.cpp190
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h36
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.cpp497
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.h64
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.cpp504
-rw-r--r--game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.h43
22 files changed, 3223 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.cpp b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.cpp
new file mode 100644
index 0000000..c86318f
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.cpp
@@ -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;
+ }
+}
diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h
new file mode 100644
index 0000000..199492f
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h
@@ -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
diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.cpp b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.cpp
new file mode 100644
index 0000000..024b981
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.cpp
@@ -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();
+}
diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h
new file mode 100644
index 0000000..7acb0b6
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h
@@ -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
diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.cpp b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.cpp
new file mode 100644
index 0000000..c6157dc
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.cpp
@@ -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 );
+ }
+ }
+}
diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.h b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.h
new file mode 100644
index 0000000..13ea69f
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.h
@@ -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
diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.cpp b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.cpp
new file mode 100644
index 0000000..5b439e6
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.cpp
@@ -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();
+}
+
diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h
new file mode 100644
index 0000000..cd2f32b
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h
@@ -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
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.cpp
new file mode 100644
index 0000000..e6d586e
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.cpp
@@ -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;
+}
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.h
new file mode 100644
index 0000000..2ff6265
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.h
@@ -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
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.cpp
new file mode 100644
index 0000000..eabd9ba
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.cpp
@@ -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();
+}
+
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.h
new file mode 100644
index 0000000..12e5dbf
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.h
@@ -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
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.cpp
new file mode 100644
index 0000000..b1beb35
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.cpp
@@ -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();
+}
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h
new file mode 100644
index 0000000..774b7ca
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h
@@ -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
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.cpp
new file mode 100644
index 0000000..c6924aa
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.cpp
@@ -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();
+}
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h
new file mode 100644
index 0000000..cf59418
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h
@@ -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
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.cpp
new file mode 100644
index 0000000..4c621a5
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.cpp
@@ -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();
+}
+
+
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h
new file mode 100644
index 0000000..d64f34a
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h
@@ -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
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.cpp
new file mode 100644
index 0000000..a0f84c0
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.cpp
@@ -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();
+}
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.h
new file mode 100644
index 0000000..e5928ce
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.h
@@ -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
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.cpp
new file mode 100644
index 0000000..4ef28d6
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.cpp
@@ -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();
+}
diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.h
new file mode 100644
index 0000000..42f60c4
--- /dev/null
+++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.h
@@ -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