summaryrefslogtreecommitdiff
path: root/game/server/tf/bot/behavior/scenario/capture_the_flag
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/bot/behavior/scenario/capture_the_flag')
-rw-r--r--game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.cpp126
-rw-r--r--game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.h34
-rw-r--r--game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.cpp422
-rw-r--r--game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.h63
-rw-r--r--game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.cpp142
-rw-r--r--game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.h31
-rw-r--r--game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.cpp128
-rw-r--r--game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.h35
8 files changed, 981 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.cpp b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.cpp
new file mode 100644
index 0000000..95f1480
--- /dev/null
+++ b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.cpp
@@ -0,0 +1,126 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_attack_flag_defenders.cpp
+// Attack enemies that are preventing the flag from reaching its destination
+// Michael Booth, May 2011
+
+#include "cbase.h"
+
+#include "bot/tf_bot.h"
+#include "bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.h"
+#include "bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.h"
+
+ConVar tf_bot_flag_escort_range( "tf_bot_flag_escort_range", "500", FCVAR_CHEAT );
+
+extern ConVar tf_bot_flag_escort_max_count;
+
+extern int GetBotEscortCount( int team );
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotAttackFlagDefenders::CTFBotAttackFlagDefenders( float minDuration )
+{
+ if ( minDuration > 0.0f )
+ {
+ m_minDurationTimer.Start( minDuration );
+ }
+ else
+ {
+ m_minDurationTimer.Invalidate();
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotAttackFlagDefenders::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
+
+ m_chasePlayer = NULL;
+ return CTFBotAttack::OnStart( me, priorAction );
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotAttackFlagDefenders::Update( CTFBot *me, float interval )
+{
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+ if ( threat && threat->IsVisibleRecently() )
+ {
+ // prepare to fight
+ me->EquipBestWeaponForThreat( threat );
+ }
+
+ if ( m_watchFlagTimer.IsElapsed() && m_minDurationTimer.IsElapsed() )
+ {
+ m_watchFlagTimer.Start( RandomFloat( 1.0f, 3.0f ) );
+
+ CCaptureFlag *flag = me->GetFlagToFetch();
+
+ if ( !flag )
+ {
+ return Done( "No flag" );
+ }
+
+ // can't reach flag if it is at home
+ if ( !TFGameRules()->IsMannVsMachineMode() || !flag->IsHome() )
+ {
+ CTFPlayer *carrier = ToTFPlayer( flag->GetOwnerEntity() );
+ if ( !carrier )
+ {
+ return Done( "Flag was dropped" );
+ }
+
+ if ( me->IsSelf( carrier ) )
+ {
+ return Done( "I picked up the flag!" );
+ }
+
+ // escort the flag carrier, unless the carrier is in a squad
+ CTFBot *botCarrier = ToTFBot( carrier );
+ if ( !botCarrier || !botCarrier->IsInASquad() )
+ {
+ if ( me->IsRangeLessThan( carrier, tf_bot_flag_escort_range.GetFloat() ) )
+ {
+ if ( GetBotEscortCount( me->GetTeamNumber() ) < tf_bot_flag_escort_max_count.GetInt() )
+ {
+ return ChangeTo( new CTFBotEscortFlagCarrier, "Near flag carrier - escorting" );
+ }
+ }
+ }
+ }
+ }
+
+ ActionResult< CTFBot > result = CTFBotAttack::Update( me, interval );
+
+ if ( result.IsDone() )
+ {
+ // nothing to attack, move towards a random player
+
+ if ( m_chasePlayer == NULL || !m_chasePlayer->IsAlive() )
+ {
+ m_chasePlayer = me->SelectRandomReachableEnemy();
+ }
+
+ if ( m_chasePlayer == NULL )
+ {
+ // everyone is dead or hiding in the spawn room - go escort the flag
+ return ChangeTo( new CTFBotEscortFlagCarrier, "No reachable victim - escorting flag" );
+ }
+
+ // cheat and "see" our victim so we know where to go
+ me->GetVisionInterface()->AddKnownEntity( m_chasePlayer );
+
+ if ( m_repathTimer.IsElapsed() )
+ {
+ m_repathTimer.Start( RandomFloat( 1.0f, 3.0f ) );
+
+ CTFBotPathCost cost( me, DEFAULT_ROUTE );
+ float maxPathLength = TFGameRules()->IsMannVsMachineMode() ? TFBOT_MVM_MAX_PATH_LENGTH : 0.0f;
+ m_path.Compute( me, m_chasePlayer, cost, maxPathLength );
+ }
+
+ m_path.Update( me );
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.h b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.h
new file mode 100644
index 0000000..662ef8a
--- /dev/null
+++ b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.h
@@ -0,0 +1,34 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_attack_flag_defenders.h
+// Attack enemies that are preventing the flag from reaching its destination
+// Michael Booth, May 2011
+
+#ifndef TF_BOT_ATTACK_FLAG_DEFENDERS_H
+#define TF_BOT_ATTACK_FLAG_DEFENDERS_H
+
+#include "Path/NextBotPathFollow.h"
+#include "bot/behavior/tf_bot_attack.h"
+
+
+//-----------------------------------------------------------------------------
+class CTFBotAttackFlagDefenders : public CTFBotAttack
+{
+public:
+ CTFBotAttackFlagDefenders( float minDuration = -1.0f );
+ virtual ~CTFBotAttackFlagDefenders() { }
+
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+
+ virtual const char *GetName( void ) const { return "AttackFlagDefenders"; }
+
+private:
+ CountdownTimer m_minDurationTimer;
+ CountdownTimer m_watchFlagTimer;
+ CHandle< CTFPlayer > m_chasePlayer;
+ PathFollower m_path;
+ CountdownTimer m_repathTimer;
+};
+
+
+#endif // TF_BOT_ATTACK_FLAG_DEFENDERS_H
diff --git a/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.cpp b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.cpp
new file mode 100644
index 0000000..873c36e
--- /dev/null
+++ b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.cpp
@@ -0,0 +1,422 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_deliver_flag.cpp
+// Take the flag we are holding to its destination
+// Michael Booth, May 2011
+
+#include "cbase.h"
+
+#include "tf_player_shared.h"
+
+#include "bot/tf_bot.h"
+#include "bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.h"
+#include "bot/behavior/tf_bot_taunt.h"
+#include "bot/behavior/tf_bot_mvm_deploy_bomb.h"
+
+#include "tf_objective_resource.h"
+#include "player_vs_environment/tf_population_manager.h"
+#include "econ_item_system.h"
+#include "tf_gamestats.h"
+
+#include "bot/behavior/nav_entities/tf_bot_nav_ent_move_to.h"
+#include "bot/behavior/nav_entities/tf_bot_nav_ent_wait.h"
+
+#include "particle_parse.h"
+
+ConVar tf_mvm_bot_allow_flag_carrier_to_fight( "tf_mvm_bot_allow_flag_carrier_to_fight", "1", FCVAR_CHEAT );
+
+ConVar tf_mvm_bot_flag_carrier_interval_to_1st_upgrade( "tf_mvm_bot_flag_carrier_interval_to_1st_upgrade", "5", FCVAR_CHEAT );
+ConVar tf_mvm_bot_flag_carrier_interval_to_2nd_upgrade( "tf_mvm_bot_flag_carrier_interval_to_2nd_upgrade", "15", FCVAR_CHEAT );
+ConVar tf_mvm_bot_flag_carrier_interval_to_3rd_upgrade( "tf_mvm_bot_flag_carrier_interval_to_3rd_upgrade", "15", FCVAR_CHEAT );
+
+ConVar tf_mvm_bot_flag_carrier_health_regen( "tf_mvm_bot_flag_carrier_health_regen", "45.0f", FCVAR_CHEAT );
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotDeliverFlag::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ m_flTotalTravelDistance = -1.0f;
+
+ m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
+
+ if ( tf_mvm_bot_allow_flag_carrier_to_fight.GetBool() == false )
+ {
+ me->SetAttribute( CTFBot::SUPPRESS_FIRE );
+ }
+
+ // mini-bosses don't upgrade - they are already tough
+ if ( me->IsMiniBoss() )
+ {
+ m_upgradeLevel = DONT_UPGRADE;
+ if ( TFObjectiveResource() )
+ {
+ // Set threat level to max
+ TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 4 );
+ TFObjectiveResource()->SetBaseMvMBombUpgradeTime( -1 );
+ TFObjectiveResource()->SetNextMvMBombUpgradeTime( -1 );
+ }
+ }
+ else
+ {
+ m_upgradeLevel = 0;
+ m_upgradeTimer.Start( tf_mvm_bot_flag_carrier_interval_to_1st_upgrade.GetFloat() );
+ if ( TFObjectiveResource() )
+ {
+ TFObjectiveResource()->SetBaseMvMBombUpgradeTime( gpGlobals->curtime );
+ TFObjectiveResource()->SetNextMvMBombUpgradeTime( gpGlobals->curtime + m_upgradeTimer.GetRemainingTime() );
+ }
+
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+// In Mann Vs Machine, the flag carrier gets stronger the longer he carries the flag
+bool CTFBotDeliverFlag::UpgradeOverTime( CTFBot *me )
+{
+ if ( TFGameRules()->IsMannVsMachineMode() && m_upgradeLevel != DONT_UPGRADE )
+ {
+ CTFNavArea *myArea = me->GetLastKnownArea();
+ int spawnRoomFlag = me->GetTeamNumber() == TF_TEAM_RED ? TF_NAV_SPAWN_ROOM_RED : TF_NAV_SPAWN_ROOM_BLUE;
+
+ if ( myArea && myArea->HasAttributeTF( spawnRoomFlag ) )
+ {
+ // don't start counting down until we leave the spawn
+ m_upgradeTimer.Start( tf_mvm_bot_flag_carrier_interval_to_1st_upgrade.GetFloat() );
+ TFObjectiveResource()->SetBaseMvMBombUpgradeTime( gpGlobals->curtime );
+ TFObjectiveResource()->SetNextMvMBombUpgradeTime( gpGlobals->curtime + m_upgradeTimer.GetRemainingTime() );
+ }
+
+ // do defensive buff effect ourselves (since we're not a soldier)
+ if ( m_upgradeLevel > 0 && m_buffPulseTimer.IsElapsed() )
+ {
+ m_buffPulseTimer.Start( 1.0f );
+
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, me->GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );
+
+ const float buffRadius = 450.0f;
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ if ( me->IsRangeLessThan( playerVector[i], buffRadius ) )
+ {
+ playerVector[i]->m_Shared.AddCond( TF_COND_DEFENSEBUFF_NO_CRIT_BLOCK, 1.2f );
+ }
+ }
+ }
+
+ // the flag carrier gets stronger the longer he holds the flag
+ if ( m_upgradeTimer.IsElapsed() )
+ {
+ const int maxLevel = 3;
+
+ if ( m_upgradeLevel < maxLevel )
+ {
+ ++m_upgradeLevel;
+
+ TFGameRules()->BroadcastSound( 255, "MVM.Warning" );
+
+ switch( m_upgradeLevel )
+ {
+ //---------------------------------------
+ case 1:
+ m_upgradeTimer.Start( tf_mvm_bot_flag_carrier_interval_to_2nd_upgrade.GetFloat() );
+
+ // permanent buff banner effect (handled above)
+
+ // update the objective resource so clients have the information
+ if ( TFObjectiveResource() )
+ {
+ TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 1 );
+ TFObjectiveResource()->SetBaseMvMBombUpgradeTime( gpGlobals->curtime );
+ TFObjectiveResource()->SetNextMvMBombUpgradeTime( gpGlobals->curtime + m_upgradeTimer.GetRemainingTime() );
+ TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_BOMB_CARRIER_UPGRADE1, TF_TEAM_PVE_DEFENDERS );
+ DispatchParticleEffect( "mvm_levelup1", PATTACH_POINT_FOLLOW, me, "head" );
+ }
+ return true;
+
+ //---------------------------------------
+ case 2:
+ {
+ static CSchemaAttributeDefHandle pAttrDef_HealthRegen( "health regen" );
+
+ m_upgradeTimer.Start( tf_mvm_bot_flag_carrier_interval_to_3rd_upgrade.GetFloat() );
+
+ if ( !pAttrDef_HealthRegen )
+ {
+ Warning( "TFBotSpawner: Invalid attribute 'health regen'\n" );
+ }
+ else
+ {
+ CAttributeList *pAttrList = me->GetAttributeList();
+ if ( pAttrList )
+ {
+ pAttrList->SetRuntimeAttributeValue( pAttrDef_HealthRegen, tf_mvm_bot_flag_carrier_health_regen.GetFloat() );
+ }
+ }
+
+ // update the objective resource so clients have the information
+ if ( TFObjectiveResource() )
+ {
+ TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 2 );
+ TFObjectiveResource()->SetBaseMvMBombUpgradeTime( gpGlobals->curtime );
+ TFObjectiveResource()->SetNextMvMBombUpgradeTime( gpGlobals->curtime + m_upgradeTimer.GetRemainingTime() );
+ TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_BOMB_CARRIER_UPGRADE2, TF_TEAM_PVE_DEFENDERS );
+ DispatchParticleEffect( "mvm_levelup2", PATTACH_POINT_FOLLOW, me, "head" );
+ }
+ return true;
+ }
+
+ //---------------------------------------
+ case 3:
+ // add critz
+ me->m_Shared.AddCond( TF_COND_CRITBOOSTED );
+
+ // update the objective resource so clients have the information
+ if ( TFObjectiveResource() )
+ {
+ TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 3 );
+ TFObjectiveResource()->SetBaseMvMBombUpgradeTime( -1 );
+ TFObjectiveResource()->SetNextMvMBombUpgradeTime( -1 );
+ TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_BOMB_CARRIER_UPGRADE3, TF_TEAM_PVE_DEFENDERS );
+ DispatchParticleEffect( "mvm_levelup3", PATTACH_POINT_FOLLOW, me, "head" );
+ }
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotDeliverFlag::Update( CTFBot *me, float interval )
+{
+ CCaptureFlag *flag = me->GetFlagToFetch();
+
+ if ( !flag )
+ {
+ return Done( "No flag" );
+ }
+
+ CTFPlayer *carrier = ToTFPlayer( flag->GetOwnerEntity() );
+ if ( !carrier || !me->IsSelf( carrier ) )
+ {
+ return Done( "I'm no longer carrying the flag" );
+ }
+
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ // let the bomb carrier use it's buff banners/etc
+ Action< CTFBot > *result = me->OpportunisticallyUseWeaponAbilities();
+ if ( result )
+ {
+ return SuspendFor( result, "Opportunistically using buff item" );
+ }
+ }
+
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+ if ( threat && threat->IsVisibleRecently() )
+ {
+ // prepare to fight
+ me->EquipBestWeaponForThreat( threat );
+ }
+
+ // deliver the flag
+ if ( m_repathTimer.IsElapsed() )
+ {
+ CCaptureZone *zone = me->GetFlagCaptureZone();
+
+ if ( !zone )
+ {
+ return Done( "No flag capture zone exists!" );
+ }
+
+ CTFBotPathCost cost( me, FASTEST_ROUTE );
+ m_path.Compute( me, zone->WorldSpaceCenter(), cost );
+
+ float flOldTravelDistance = m_flTotalTravelDistance;
+
+ m_flTotalTravelDistance = NavAreaTravelDistance( me->GetLastKnownArea(), TheNavMesh->GetNavArea( zone->WorldSpaceCenter() ), cost );
+
+ if ( flOldTravelDistance != -1.0f && m_flTotalTravelDistance - flOldTravelDistance > 2000.0f )
+ {
+ TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Bomb_Reset" );
+
+ // Look for players that helped with the reset and send an event
+ CUtlVector<CTFPlayer *> playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_PVE_DEFENDERS );
+ FOR_EACH_VEC( playerVector, i )
+ {
+ CTFPlayer *pPlayer = playerVector[i];
+ if ( !pPlayer )
+ continue;
+
+ if ( me->m_AchievementData.IsPusherInHistory( pPlayer, 3.f ) )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "mvm_bomb_reset_by_player" );
+ if ( event )
+ {
+ event->SetInt( "player", pPlayer->entindex() );
+ gameeventmanager->FireEvent( event );
+ }
+
+ CTF_GameStats.Event_PlayerAwardBonusPoints( pPlayer, me, 100 );
+ }
+ }
+ }
+
+ m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+ }
+
+ m_path.Update( me );
+
+ if ( UpgradeOverTime( me ) )
+ {
+ return SuspendFor( new CTFBotTaunt, "Taunting for our new upgrade" );
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CTFBotDeliverFlag::OnEnd( CTFBot *me, Action< CTFBot > *nextAction )
+{
+ me->ClearAttribute( CTFBot::SUPPRESS_FIRE );
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ me->m_Shared.ResetRageBuffs();
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotDeliverFlag::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const
+{
+ if ( tf_mvm_bot_allow_flag_carrier_to_fight.GetBool() )
+ {
+ return ANSWER_UNDEFINED;
+ }
+
+ return ANSWER_NO;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// are we in a hurry?
+QueryResultType CTFBotDeliverFlag::ShouldHurry( const INextBot *me ) const
+{
+ return ANSWER_YES;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// is it time to retreat?
+QueryResultType CTFBotDeliverFlag::ShouldRetreat( const INextBot *me ) const
+{
+ return ANSWER_NO;
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotDeliverFlag::OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result )
+{
+ if ( TFGameRules()->IsMannVsMachineMode() && other && FClassnameIs( other, "func_capturezone" ) )
+ {
+ return TrySuspendFor( new CTFBotMvMDeployBomb, RESULT_CRITICAL, "Delivering the bomb!" );
+ }
+
+ return TryContinue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+CTFBotPushToCapturePoint::CTFBotPushToCapturePoint( Action< CTFBot > *nextAction )
+{
+ m_nextAction = nextAction;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotPushToCapturePoint::Update( CTFBot *me, float interval )
+{
+ // flag collection and delivery is handled by our parent behavior, ScenarioMonitor
+
+ CCaptureZone *zone = me->GetFlagCaptureZone();
+
+ if ( !zone )
+ {
+ if ( m_nextAction )
+ {
+ return ChangeTo( m_nextAction, "No flag capture zone exists!" );
+ }
+
+ return Done( "No flag capture zone exists!" );
+ }
+
+ Vector toZone = zone->WorldSpaceCenter() - me->GetAbsOrigin();
+ if ( toZone.AsVector2D().IsLengthLessThan( 50.0f ) )
+ {
+ if ( m_nextAction )
+ {
+ return ChangeTo( m_nextAction, "At destination" );
+ }
+
+ return Done( "At destination" );
+ }
+
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+ if ( threat && threat->IsVisibleRecently() )
+ {
+ // prepare to fight
+ me->EquipBestWeaponForThreat( threat );
+ }
+
+ if ( m_repathTimer.IsElapsed() )
+ {
+ CTFBotPathCost cost( me, FASTEST_ROUTE );
+ m_path.Compute( me, zone->WorldSpaceCenter(), cost );
+
+ m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+ }
+
+ m_path.Update( me );
+
+ return Continue();
+}
+
+//-----------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotPushToCapturePoint::OnNavAreaChanged( CTFBot *me, CNavArea *newArea, CNavArea *oldArea )
+{
+ // does the area we are entering have a prerequisite?
+ if ( newArea && newArea->HasPrerequisite( me ) )
+ {
+ const CUtlVector< CHandle< CFuncNavPrerequisite > > &prereqVector = newArea->GetPrerequisiteVector();
+
+ for( int i=0; i<prereqVector.Count(); ++i )
+ {
+ const CFuncNavPrerequisite *prereq = prereqVector[i];
+ if ( prereq && prereq->IsEnabled() && const_cast< CFuncNavPrerequisite * >( prereq )->PassesTriggerFilters( me ) )
+ {
+ // this prerequisite applies to me
+ if ( prereq->IsTask( CFuncNavPrerequisite::TASK_WAIT ) )
+ {
+ return TrySuspendFor( new CTFBotNavEntWait( prereq ), RESULT_IMPORTANT, "Prerequisite commands me to wait" );
+ }
+ else if ( prereq->IsTask( CFuncNavPrerequisite::TASK_MOVE_TO_ENTITY ) )
+ {
+ return TrySuspendFor( new CTFBotNavEntMoveTo( prereq ), RESULT_IMPORTANT, "Prerequisite commands me to move to an entity" );
+ }
+ }
+ }
+ }
+
+ return TryContinue();
+}
diff --git a/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.h b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.h
new file mode 100644
index 0000000..1e8dfdf
--- /dev/null
+++ b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.h
@@ -0,0 +1,63 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_deliver_flag.h
+// Take the flag we are holding to its destination
+// Michael Booth, May 2011
+
+#ifndef TF_BOT_DELIVER_FLAG_H
+#define TF_BOT_DELIVER_FLAG_H
+
+#include "Path/NextBotPathFollow.h"
+
+
+//-----------------------------------------------------------------------------
+class CTFBotDeliverFlag : public Action< CTFBot >
+{
+public:
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+ virtual void OnEnd( CTFBot *me, Action< CTFBot > *nextAction );
+
+ virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const;
+ virtual QueryResultType ShouldHurry( const INextBot *me ) const;
+ virtual QueryResultType ShouldRetreat( const INextBot *me ) const;
+
+ virtual EventDesiredResult< CTFBot > OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result = NULL );
+
+ virtual const char *GetName( void ) const { return "DeliverFlag"; };
+
+private:
+ PathFollower m_path;
+ CountdownTimer m_repathTimer;
+ float m_flTotalTravelDistance;
+
+ bool UpgradeOverTime( CTFBot *me );
+ CountdownTimer m_upgradeTimer;
+
+#define DONT_UPGRADE -1
+ int m_upgradeLevel;
+
+ CountdownTimer m_buffPulseTimer;
+};
+
+
+//-----------------------------------------------------------------------------
+class CTFBotPushToCapturePoint : public Action< CTFBot >
+{
+public:
+ CTFBotPushToCapturePoint( Action< CTFBot > *nextAction = NULL );
+ virtual ~CTFBotPushToCapturePoint() { }
+
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+ virtual EventDesiredResult< CTFBot > OnNavAreaChanged( CTFBot *me, CNavArea *newArea, CNavArea *oldArea );
+
+ virtual const char *GetName( void ) const { return "PushToCapturePoint"; };
+
+private:
+ PathFollower m_path;
+ CountdownTimer m_repathTimer;
+
+ Action< CTFBot > *m_nextAction;
+};
+
+
+#endif // TF_BOT_DELIVER_FLAG_H
diff --git a/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.cpp b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.cpp
new file mode 100644
index 0000000..ca48e19
--- /dev/null
+++ b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.cpp
@@ -0,0 +1,142 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_escort_flag_carrier.cpp
+// Escort the flag carrier to their destination
+// Michael Booth, May 2011
+
+#include "cbase.h"
+
+#include "bot/tf_bot.h"
+#include "bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.h"
+#include "bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.h"
+#include "bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.h"
+
+extern ConVar tf_bot_flag_escort_range;
+
+ConVar tf_bot_flag_escort_give_up_range( "tf_bot_flag_escort_give_up_range", "1000", FCVAR_CHEAT );
+ConVar tf_bot_flag_escort_max_count( "tf_bot_flag_escort_max_count", "4", FCVAR_CHEAT );
+
+
+//---------------------------------------------------------------------------------------------
+//
+// Count the number of TFBots currently engaged in the "EscortFlagCarrier" behavior
+//
+int GetBotEscortCount( int team )
+{
+ int count = 0;
+
+ CUtlVector< CTFPlayer * > livePlayerVector;
+ CollectPlayers( &livePlayerVector, team, COLLECT_ONLY_LIVING_PLAYERS );
+
+ int i;
+ for( i=0; i<livePlayerVector.Count(); ++i )
+ {
+ CTFBot *bot = dynamic_cast< CTFBot * >( livePlayerVector[i] );
+ if ( bot )
+ {
+ Behavior< CTFBot > *behavior = (Behavior< CTFBot > *)bot->GetIntentionInterface()->FirstContainedResponder();
+ if ( behavior )
+ {
+ Action< CTFBot > *action = (Action< CTFBot > *)behavior->FirstContainedResponder();
+
+ while( action && action->GetActiveChildAction() )
+ {
+ action = action->GetActiveChildAction();
+ }
+
+ if ( action && action->IsNamed( "EscortFlagCarrier" ) )
+ {
+ ++count;
+ }
+ }
+ }
+ }
+
+ return count;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotEscortFlagCarrier::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotEscortFlagCarrier::Update( CTFBot *me, float interval )
+{
+ CCaptureFlag *flag = me->GetFlagToFetch();
+
+ if ( !flag )
+ {
+ return Done( "No flag" );
+ }
+
+ CTFPlayer *carrier = ToTFPlayer( flag->GetOwnerEntity() );
+ if ( !carrier )
+ {
+ return Done( "Flag was dropped" );
+ }
+ else if ( me->IsSelf( carrier ) )
+ {
+ return Done( "I picked up the flag!" );
+ }
+
+ // stay near the carrier
+ if ( me->IsRangeGreaterThan( carrier, tf_bot_flag_escort_give_up_range.GetFloat() ) )
+ {
+ if ( me->SelectRandomReachableEnemy() )
+ {
+ // too far away - give up
+ return ChangeTo( new CTFBotAttackFlagDefenders, "Too far from flag carrier - attack defenders!" );
+ }
+ }
+
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+ if ( threat && threat->IsVisibleRecently() )
+ {
+ // prepare to fight
+ me->EquipBestWeaponForThreat( threat );
+ }
+
+ CTFWeaponBase *myWeapon = me->m_Shared.GetActiveTFWeapon();
+ if ( myWeapon && myWeapon->IsMeleeWeapon() )
+ {
+ if ( me->IsRangeLessThan( carrier, tf_bot_flag_escort_range.GetFloat() ) && me->IsLineOfSightClear( carrier ) )
+ {
+ ActionResult< CTFBot > result = m_meleeAttackAction.Update( me, interval );
+
+ if ( result.IsContinue() )
+ {
+ // we have a melee target, and we're still reasonably close to the flag carrier
+ return Continue();
+ }
+ }
+ }
+
+ if ( me->IsRangeGreaterThan( carrier, 0.5f * tf_bot_flag_escort_range.GetFloat() ) )
+ {
+ // move near carrier
+ if ( m_repathTimer.IsElapsed() )
+ {
+ if ( GetBotEscortCount( me->GetTeamNumber() ) > tf_bot_flag_escort_max_count.GetInt() )
+ {
+ if ( me->SelectRandomReachableEnemy() )
+ {
+ return Done( "Too many flag escorts - giving up" );
+ }
+ }
+
+ CTFBotPathCost cost( me, FASTEST_ROUTE );
+ m_path.Compute( me, carrier, cost );
+
+ m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+ }
+
+ m_path.Update( me );
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.h b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.h
new file mode 100644
index 0000000..f8e5f9d
--- /dev/null
+++ b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.h
@@ -0,0 +1,31 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_escort_flag_carrier.h
+// Escort the flag carrier to their destination
+// Michael Booth, May 2011
+
+#ifndef TF_BOT_ESCORT_FLAG_CARRIER_H
+#define TF_BOT_ESCORT_FLAG_CARRIER_H
+
+
+#include "Path/NextBotPathFollow.h"
+#include "bot/behavior/tf_bot_melee_attack.h"
+
+
+//-----------------------------------------------------------------------------
+class CTFBotEscortFlagCarrier : public Action< CTFBot >
+{
+public:
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+
+ virtual const char *GetName( void ) const { return "EscortFlagCarrier"; };
+
+private:
+ PathFollower m_path;
+ CountdownTimer m_repathTimer;
+
+ CTFBotMeleeAttack m_meleeAttackAction;
+};
+
+
+#endif // TF_BOT_ESCORT_FLAG_CARRIER_H
diff --git a/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.cpp b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.cpp
new file mode 100644
index 0000000..84e02ce
--- /dev/null
+++ b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.cpp
@@ -0,0 +1,128 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_fetch_flag.cpp
+// Go get the flag!
+// Michael Booth, May 2011
+
+#include "cbase.h"
+
+#include "bot/tf_bot.h"
+#include "bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.h"
+#include "bot/behavior/scenario/capture_the_flag/tf_bot_escort_flag_carrier.h"
+#include "bot/behavior/scenario/capture_the_flag/tf_bot_attack_flag_defenders.h"
+#include "bot/behavior/scenario/capture_the_flag/tf_bot_deliver_flag.h"
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotFetchFlag::CTFBotFetchFlag( bool isTemporary )
+{
+ m_isTemporary = isTemporary;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotFetchFlag::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotFetchFlag::Update( CTFBot *me, float interval )
+{
+ CCaptureFlag *flag = me->GetFlagToFetch();
+
+ if ( !flag )
+ {
+ if ( TFGameRules()->IsMannVsMachineMode() )
+ {
+ return SuspendFor( new CTFBotAttackFlagDefenders, "Flag flag exists - Attacking the enemy flag defenders" );
+ }
+
+ return Done( "No flag" );
+ }
+
+ // uncloak so we can attack
+ if ( me->m_Shared.IsStealthed() )
+ {
+ me->PressAltFireButton();
+ }
+
+ if ( TFGameRules()->IsMannVsMachineMode() && flag->IsHome() )
+ {
+ if ( gpGlobals->curtime - me->GetSpawnTime() < 1.0f && me->GetTeamNumber() != TEAM_SPECTATOR )
+ {
+ // we just spawned - give us the flag
+ flag->PickUp( me, true );
+ }
+ else
+ {
+ if ( m_isTemporary )
+ {
+ return Done( "Flag unreachable" );
+ }
+
+ // flag is at home and we're out in the world - can't reach it
+ return SuspendFor( new CTFBotAttackFlagDefenders, "Flag unreachable at home - Attacking the enemy flag defenders" );
+ }
+ }
+
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+ if ( threat )
+ {
+ me->EquipBestWeaponForThreat( threat );
+ }
+
+ CTFPlayer *carrier = ToTFPlayer( flag->GetOwnerEntity() );
+ if ( carrier )
+ {
+ if ( m_isTemporary )
+ {
+ return Done( "Someone else picked up the flag" );
+ }
+
+ // NOTE: if I've picked up the flag, the ScenarioMonitor will handle it
+ return SuspendFor( new CTFBotAttackFlagDefenders, "Someone has the flag - attacking the enemy defenders" );
+ }
+
+ // go pick up the flag
+ if ( m_repathTimer.IsElapsed() )
+ {
+ CTFBotPathCost cost( me, DEFAULT_ROUTE );
+ float maxPathLength = TFGameRules()->IsMannVsMachineMode() ? TFBOT_MVM_MAX_PATH_LENGTH : 0.0f;
+ if ( m_path.Compute( me, flag->WorldSpaceCenter(), cost, maxPathLength ) == false )
+ {
+ if ( flag->IsDropped() )
+ {
+ // flag is unreachable - attack for awhile and hope someone else can dislodge it
+ return SuspendFor( new CTFBotAttackFlagDefenders( RandomFloat( 5.0f, 10.0f ) ), "Flag unreachable - Attacking" );
+
+ // just give it to me
+ // flag->PickUp( me, true );
+ }
+ }
+
+ m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+ }
+
+ m_path.Update( me );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+// are we in a hurry?
+QueryResultType CTFBotFetchFlag::ShouldHurry( const INextBot *me ) const
+{
+ return ANSWER_YES;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// is it time to retreat?
+QueryResultType CTFBotFetchFlag::ShouldRetreat( const INextBot *me ) const
+{
+ return ANSWER_NO;
+}
diff --git a/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.h b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.h
new file mode 100644
index 0000000..5ca8818
--- /dev/null
+++ b/game/server/tf/bot/behavior/scenario/capture_the_flag/tf_bot_fetch_flag.h
@@ -0,0 +1,35 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_fetch_flag.h
+// Go get the flag!
+// Michael Booth, May 2011
+
+#ifndef TF_BOT_FETCH_FLAG_H
+#define TF_BOT_FETCH_FLAG_H
+
+#include "Path/NextBotPathFollow.h"
+
+
+//-----------------------------------------------------------------------------
+class CTFBotFetchFlag : public Action< CTFBot >
+{
+public:
+ #define TEMPORARY_FLAG_FETCH true
+ CTFBotFetchFlag( bool isTemporary = false );
+ virtual ~CTFBotFetchFlag() { }
+
+ virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction );
+ virtual ActionResult< CTFBot > Update( CTFBot *me, float interval );
+
+ virtual QueryResultType ShouldHurry( const INextBot *me ) const;
+ virtual QueryResultType ShouldRetreat( const INextBot *me ) const;
+
+ virtual const char *GetName( void ) const { return "FetchFlag"; };
+
+private:
+ bool m_isTemporary;
+ PathFollower m_path;
+ CountdownTimer m_repathTimer;
+};
+
+
+#endif // TF_BOT_FETCH_FLAG_H