summaryrefslogtreecommitdiff
path: root/game/server/tf/bot/behavior/tf_bot_get_ammo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/bot/behavior/tf_bot_get_ammo.cpp')
-rw-r--r--game/server/tf/bot/behavior/tf_bot_get_ammo.cpp339
1 files changed, 339 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/tf_bot_get_ammo.cpp b/game/server/tf/bot/behavior/tf_bot_get_ammo.cpp
new file mode 100644
index 0000000..302663f
--- /dev/null
+++ b/game/server/tf/bot/behavior/tf_bot_get_ammo.cpp
@@ -0,0 +1,339 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// tf_bot_get_ammo.h
+// Pick up any nearby ammo
+// Michael Booth, May 2009
+
+#include "cbase.h"
+#include "tf_obj.h"
+#include "tf_gamerules.h"
+#include "bot/tf_bot.h"
+#include "bot/behavior/tf_bot_get_ammo.h"
+
+extern ConVar tf_bot_path_lookahead_range;
+
+ConVar tf_bot_ammo_search_range( "tf_bot_ammo_search_range", "5000", FCVAR_CHEAT, "How far bots will search to find ammo around them" );
+ConVar tf_bot_debug_ammo_scavenging( "tf_bot_debug_ammo_scavenging", "0", FCVAR_CHEAT );
+
+
+//---------------------------------------------------------------------------------------------
+CTFBotGetAmmo::CTFBotGetAmmo( void )
+{
+ m_path.Invalidate();
+ m_ammo = NULL;
+ m_isGoalDispenser = false;
+}
+
+
+//---------------------------------------------------------------------------------------------
+class CAmmoFilter : public INextBotFilter
+{
+public:
+ CAmmoFilter( CTFBot *me )
+ {
+ m_me = me;
+ m_ammoArea = NULL;
+ }
+
+ bool IsSelected( const CBaseEntity *constCandidate ) const
+ {
+ CBaseEntity *candidate = const_cast< CBaseEntity * >( constCandidate );
+
+ m_ammoArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( candidate->WorldSpaceCenter() );
+ if ( !m_ammoArea )
+ return false;
+
+ CClosestTFPlayer close( candidate );
+ ForEachPlayer( close );
+
+ // if the closest player to this candidate object is an enemy, don't use it
+ if ( close.m_closePlayer && !m_me->InSameTeam( close.m_closePlayer ) )
+ return false;
+
+ // resupply cabinets (not assigned a team)
+ if ( candidate->ClassMatches( "func_regenerate" ) )
+ {
+ if ( !m_ammoArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
+ {
+ // Assume any resupply cabinets not in a teamed spawn room are inaccessible.
+ // Ex: pl_upward has forward spawn rooms that neither team can use until
+ // certain checkpoints are reached.
+ return false;
+ }
+
+ if ( ( m_me->GetTeamNumber() == TF_TEAM_RED && m_ammoArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_RED ) ) ||
+ ( m_me->GetTeamNumber() == TF_TEAM_BLUE && m_ammoArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) ) )
+ {
+ // the supply cabinet is in my spawn room, or not in any spawn room
+ return true;
+ }
+ return false;
+ }
+
+ // ignore non-existent ammo to ensure we collect nearby existing ammo
+ if ( candidate->IsEffectActive( EF_NODRAW ) )
+ return false;
+
+ if ( candidate->ClassMatches( "tf_ammo_pack" ) )
+ return true;
+
+ if ( candidate->ClassMatches( "item_ammopack*" ) )
+ return true;
+
+ if ( m_me->InSameTeam( candidate ) )
+ {
+ // friendly engineer's dispenser
+ if ( candidate->ClassMatches( "obj_dispenser*" ) )
+ {
+ // for now, assume Engineers want to go fetch ammo boxes unless their dispenser is fully upgraded
+ // unless we have no sentry yet, then we need to leech off of buddy's dispenser to get started
+ if ( !m_me->IsPlayerClass( TF_CLASS_ENGINEER ) || ( (CBaseObject *)candidate )->GetUpgradeLevel() >= 3 || !m_me->GetObjectOfType( OBJ_SENTRYGUN ) )
+ {
+ CBaseObject *dispenser = (CBaseObject *)candidate;
+ if ( !dispenser->IsBuilding() && !dispenser->IsDisabled() )
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ CTFBot *m_me;
+ mutable CTFNavArea *m_ammoArea;
+};
+
+
+//---------------------------------------------------------------------------------------------
+static CTFBot *s_possibleBot = NULL;
+static CHandle< CBaseEntity > s_possibleAmmo = NULL;
+static int s_possibleFrame = 0;
+
+
+//---------------------------------------------------------------------------------------------
+/**
+ * Return true if this Action has what it needs to perform right now
+ */
+bool CTFBotGetAmmo::IsPossible( CTFBot *me )
+{
+ VPROF_BUDGET( "CTFBotGetAmmo::IsPossible", "NextBot" );
+
+ int i;
+
+ CUtlVector< CNavArea * > nearbyAreaVector;
+ CollectSurroundingAreas( &nearbyAreaVector, me->GetLastKnownArea(), tf_bot_ammo_search_range.GetFloat(), me->GetLocomotionInterface()->GetStepHeight(), me->GetLocomotionInterface()->GetDeathDropHeight() );
+
+ CAmmoFilter ammoFilter( me );
+
+ const CUtlVector< CHandle< CBaseEntity > > &staticAmmoVector = TFGameRules()->GetAmmoEntityVector();
+ CBaseEntity *closestAmmo = NULL;
+ float closestAmmoTravelDistance = FLT_MAX;
+
+ for( i=0; i<staticAmmoVector.Count(); ++i )
+ {
+ CBaseEntity *ammo = staticAmmoVector[i];
+ if ( ammo )
+ {
+ if ( ammoFilter.IsSelected( ammo ) )
+ {
+ if ( ammoFilter.m_ammoArea && ammoFilter.m_ammoArea->IsMarked() )
+ {
+ // "cost so far" was computed during the breadth first search within CollectSurroundingAreas()
+ // and is the travel distance from to this area
+ if ( ammoFilter.m_ammoArea->GetCostSoFar() < closestAmmoTravelDistance )
+ {
+ closestAmmo = ammo;
+ closestAmmoTravelDistance = ammoFilter.m_ammoArea->GetCostSoFar();
+ }
+
+ if ( tf_bot_debug_ammo_scavenging.GetBool() )
+ {
+ NDebugOverlay::Cross3D( ammo->WorldSpaceCenter(), 5.0f, 255, 255, 0, true, 999.9 );
+ }
+ }
+ }
+ }
+ }
+
+ // append nearby dropped weapons
+ CBaseEntity *ammoPack = NULL;
+ while( ( ammoPack = gEntList.FindEntityByClassname( ammoPack, "tf_ammo_pack" ) ) != NULL )
+ {
+ if ( ammoFilter.IsSelected( ammoPack ) )
+ {
+ if ( ammoFilter.m_ammoArea && ammoFilter.m_ammoArea->IsMarked() )
+ {
+ if ( ammoFilter.m_ammoArea->GetCostSoFar() < closestAmmoTravelDistance )
+ {
+ closestAmmo = ammoPack;
+ closestAmmoTravelDistance = ammoFilter.m_ammoArea->GetCostSoFar();
+ }
+
+ if ( tf_bot_debug_ammo_scavenging.GetBool() )
+ {
+ NDebugOverlay::Cross3D( ammoPack->WorldSpaceCenter(), 5.0f, 255, 100, 0, true, 999.9 );
+ }
+ }
+ }
+ }
+
+ if ( !closestAmmo )
+ {
+ if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) )
+ {
+ Warning( "%3.2f: No ammo nearby\n", gpGlobals->curtime );
+ }
+ return false;
+ }
+
+ s_possibleBot = me;
+ s_possibleAmmo = closestAmmo;
+ s_possibleFrame = gpGlobals->framecount;
+
+ return true;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotGetAmmo::OnStart( CTFBot *me, Action< CTFBot > *priorAction )
+{
+ VPROF_BUDGET( "CTFBotGetAmmo::OnStart", "NextBot" );
+
+ m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() );
+
+ // if IsPossible() has already been called, use its cached data
+ if ( s_possibleFrame != gpGlobals->framecount || s_possibleBot != me )
+ {
+ if ( !IsPossible( me ) || s_possibleAmmo == NULL )
+ {
+ return Done( "Can't get ammo" );
+ }
+ }
+
+ m_ammo = s_possibleAmmo;
+ m_isGoalDispenser = m_ammo->ClassMatches( "obj_dispenser*" );
+
+ CTFBotPathCost cost( me, FASTEST_ROUTE );
+ if ( !m_path.Compute( me, m_ammo->WorldSpaceCenter(), cost ) )
+ {
+ return Done( "No path to ammo!" );
+ }
+
+ // if I'm a spy, cloak and disguise
+ if ( me->IsPlayerClass( TF_CLASS_SPY ) )
+ {
+ if ( !me->m_Shared.IsStealthed() )
+ {
+ me->PressAltFireButton();
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFBot > CTFBotGetAmmo::Update( CTFBot *me, float interval )
+{
+ if ( me->IsAmmoFull() )
+ {
+ return Done( "My ammo is full" );
+ }
+
+ if ( m_ammo == NULL ) // || ( m_ammo->IsEffectActive( EF_NODRAW ) && !FClassnameIs( m_ammo, "func_regenerate" ) ) )
+ {
+/*
+ // engineers try to gather all the metal they can
+ if ( me->IsPlayerClass( TF_CLASS_ENGINEER ) && CTFBotGetAmmo::IsPossible( me ) )
+ {
+ // more ammo to be had
+ return ChangeTo( new CTFBotGetAmmo, "Not full yet - grabbing more ammo" );
+ }
+*/
+
+ return Done( "Ammo I was going for has been taken" );
+ }
+
+ if ( m_isGoalDispenser )
+ {
+ // we need to get near and wait, not try to run over
+ const float nearRange = 75.0f;
+ if ( ( me->GetAbsOrigin() - m_ammo->GetAbsOrigin() ).IsLengthLessThan( nearRange ) )
+ {
+ if ( me->GetVisionInterface()->IsLineOfSightClearToEntity( m_ammo ) )
+ {
+ if ( me->IsAmmoFull() )
+ {
+ return Done( "Ammo refilled by the Dispenser" );
+ }
+
+ // don't wait if I'm in combat
+ if ( !me->IsAmmoLow() && me->GetVisionInterface()->GetPrimaryKnownThreat() )
+ {
+ return Done( "No time to wait for more ammo, I must fight" );
+ }
+
+ // wait until the dispenser refills us
+ return Continue();
+ }
+ }
+ }
+
+ if ( !m_path.IsValid() )
+ {
+ return Done( "My path became invalid" );
+ }
+
+/* TODO: Rethink this. Currently creates zombie behavior loop.
+ // if the closest player to the item we're after is an enemy, give up
+ CClosestTFPlayer close( m_ammo );
+ ForEachPlayer( close );
+ if ( close.m_closePlayer && !me->InSameTeam( close.m_closePlayer ) )
+ return Done( "An enemy is closer to it" );
+*/
+
+ // may need to switch weapons due to out of ammo
+ const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat();
+ me->EquipBestWeaponForThreat( threat );
+
+ m_path.Update( me );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotGetAmmo::OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result )
+{
+ return TryContinue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotGetAmmo::OnStuck( CTFBot *me )
+{
+ return TryDone( RESULT_CRITICAL, "Stuck trying to reach ammo" );
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotGetAmmo::OnMoveToSuccess( CTFBot *me, const Path *path )
+{
+ return TryContinue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CTFBot > CTFBotGetAmmo::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason )
+{
+ return TryDone( RESULT_CRITICAL, "Failed to reach ammo" );
+}
+
+
+//---------------------------------------------------------------------------------------------
+QueryResultType CTFBotGetAmmo::ShouldHurry( const INextBot *me ) const
+{
+ // if we need ammo, we best hustle
+ return ANSWER_YES;
+}