summaryrefslogtreecommitdiff
path: root/game/server/tf/bot/behavior/engineer/mvm_engineer
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/bot/behavior/engineer/mvm_engineer
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/tf/bot/behavior/engineer/mvm_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
8 files changed, 1124 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