diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/bot/behavior/scenario/capture_point | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/tf/bot/behavior/scenario/capture_point')
6 files changed, 1044 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_capture_point.cpp b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_capture_point.cpp new file mode 100644 index 0000000..6d8648b --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_capture_point.cpp @@ -0,0 +1,231 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_capture_point.cpp +// Move to and try to capture the next point +// Michael Booth, February 2009 + +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_gamerules.h" +#include "team_control_point_master.h" +#include "trigger_area_capture.h" +#include "bot/tf_bot.h" +#include "bot/behavior/scenario/capture_point/tf_bot_capture_point.h" +#include "bot/behavior/scenario/capture_point/tf_bot_defend_point.h" +#include "bot/behavior/tf_bot_seek_and_destroy.h" + + +extern ConVar tf_bot_path_lookahead_range; +ConVar tf_bot_offense_must_push_time( "tf_bot_offense_must_push_time", "120", FCVAR_CHEAT, "If timer is less than this, bots will push hard to cap" ); + +ConVar tf_bot_capture_seek_and_destroy_min_duration( "tf_bot_capture_seek_and_destroy_min_duration", "15", FCVAR_CHEAT, "If a capturing bot decides to go hunting, this is the min duration he will hunt for before reconsidering" ); +ConVar tf_bot_capture_seek_and_destroy_max_duration( "tf_bot_capture_seek_and_destroy_max_duration", "30", FCVAR_CHEAT, "If a capturing bot decides to go hunting, this is the max duration he will hunt for before reconsidering" ); + + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotCapturePoint::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + VPROF_BUDGET( "CTFBotCapturePoint::OnStart", "NextBot" ); + + m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() ); + m_path.Invalidate(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotCapturePoint::Update( CTFBot *me, float interval ) +{ + if ( TFGameRules()->InSetup() ) + { + // wait until the gates open, then path + m_path.Invalidate(); + m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + return Continue(); + } + + CTeamControlPoint *point = me->GetMyControlPoint(); + + if ( point == NULL ) + { + const float roamTime = 10.0f; + return SuspendFor( new CTFBotSeekAndDestroy( roamTime ), "Seek and destroy until a point becomes available" ); + } + + if ( point->GetTeamNumber() == me->GetTeamNumber() ) + { + return ChangeTo( new CTFBotDefendPoint, "We need to defend our point(s)" ); + } + + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( threat && threat->IsVisibleRecently() ) + { + // prepare to fight + me->EquipBestWeaponForThreat( threat ); + } + + bool isPushingToCapture = ( me->IsPointBeingCaptured( point ) && !me->IsInCombat() ) || // a friend is capturing + me->IsCapturingPoint() || // we're capturing + // me->m_Shared.InCond( TF_COND_INVULNERABLE ) || // we're ubered + TFGameRules()->InOvertime() || // the game is in overtime + me->GetTimeLeftToCapture() < tf_bot_offense_must_push_time.GetFloat() || // nearly out of tim + TFGameRules()->IsInTraining() || // teach newbies to capture + me->IsNearPoint( point ); + + + // if we see an enemy at a good combat range, stop and engage them unless we're running out of time + if ( !isPushingToCapture ) + { + if ( threat && threat->IsVisibleRecently() ) + { + return SuspendFor( new CTFBotSeekAndDestroy( RandomFloat( tf_bot_capture_seek_and_destroy_min_duration.GetFloat(), tf_bot_capture_seek_and_destroy_max_duration.GetFloat() ) ), "Too early to capture - hunting" ); + } + } + + + if ( me->IsCapturingPoint() ) + { + // move around on the point while we capture + const CUtlVector< CTFNavArea * > *controlPointAreas = TheTFNavMesh()->GetControlPointAreas( point->GetPointIndex() ); + if ( controlPointAreas ) + { + if ( controlPointAreas->Count() == 0 ) + { + Assert( controlPointAreas->Count() ); + Continue(); // this control point has no nav areas for bot to move around + } + + // move to a random spot on this control point + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) ); + + int which = RandomInt( 0, controlPointAreas->Count() - 1 ); + CTFNavArea *goalArea = controlPointAreas->Element( which ); + if ( goalArea ) + { + CTFBotPathCost cost( me, DEFAULT_ROUTE ); + m_path.Compute( me, goalArea->GetRandomPoint(), cost ); + } + } + + m_path.Update( me ); + } + } + else + { + // move toward the point, periodically repathing to account for changing situation + if ( m_repathTimer.IsElapsed() ) + { + VPROF_BUDGET( "CTFBotCapturePoint::Update( repath )", "NextBot" ); + + CTFBotPathCost cost( me, SAFEST_ROUTE ); + m_path.Compute( me, point->GetAbsOrigin(), cost ); + m_repathTimer.Start( RandomFloat( 2.0f, 3.0f ) ); + } + + if ( TFGameRules()->IsInTraining() && !me->IsAnyPointBeingCaptured() ) + { + // stop short of capturing until the human trainee starts it + if ( m_path.GetLength() < 1000.0f ) + { + // hold here and yell at player to get on the point + me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_GO ); + + return Continue(); + } + } + + // move towards next capture point + m_path.Update( me ); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotCapturePoint::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + m_repathTimer.Invalidate(); + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotCapturePoint::OnStuck( CTFBot *me ) +{ + m_repathTimer.Invalidate(); + me->GetLocomotionInterface()->ClearStuckStatus(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotCapturePoint::OnMoveToSuccess( CTFBot *me, const Path *path ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotCapturePoint::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ) +{ + m_repathTimer.Invalidate(); + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotCapturePoint::OnTerritoryContested( CTFBot *me, int territoryID ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotCapturePoint::OnTerritoryCaptured( CTFBot *me, int territoryID ) +{ + // we got it, move on + m_repathTimer.Invalidate(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotCapturePoint::OnTerritoryLost( CTFBot *me, int territoryID ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotCapturePoint::ShouldRetreat( const INextBot *bot ) const +{ + CTFBot *me = (CTFBot *)bot->GetEntity(); + + // if we're running out of time, we have to go for it + if ( me->GetTimeLeftToCapture() < tf_bot_offense_must_push_time.GetFloat() ) + return ANSWER_NO; + + return ANSWER_UNDEFINED; +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotCapturePoint::ShouldHurry( const INextBot *bot ) const +{ + CTFBot *me = (CTFBot *)bot->GetEntity(); + + // if we're running out of time, we have to go for it + if ( me->GetTimeLeftToCapture() < tf_bot_offense_must_push_time.GetFloat() ) + return ANSWER_YES; + + return ANSWER_UNDEFINED; +} + diff --git a/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_capture_point.h b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_capture_point.h new file mode 100644 index 0000000..af80217 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_capture_point.h @@ -0,0 +1,36 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_capture_point.h +// Move to and try to capture the next point +// Michael Booth, February 2009 + +#ifndef TF_BOT_CAPTURE_POINT_H +#define TF_BOT_CAPTURE_POINT_H + +#include "Path/NextBotPathFollow.h" + +class CTFBotCapturePoint : public Action< CTFBot > +{ +public: + 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 EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path ); + virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ); + + virtual EventDesiredResult< CTFBot > OnTerritoryContested( CTFBot *me, int territoryID ); + virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID ); + virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID ); + + 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 "CapturePoint"; }; + +private: + PathFollower m_path; + CountdownTimer m_repathTimer; +}; + +#endif // TF_BOT_CAPTURE_POINT_H diff --git a/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point.cpp b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point.cpp new file mode 100644 index 0000000..20c090b --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point.cpp @@ -0,0 +1,442 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_defend_point.h +// Move to and defend current point from capture +// Michael Booth, February 2009 + +#include "cbase.h" +#include "nav_mesh/tf_nav_mesh.h" +#include "tf_player.h" +#include "tf_gamerules.h" +#include "team_control_point_master.h" +#include "trigger_area_capture.h" +#include "bot/tf_bot.h" +#include "bot/behavior/scenario/capture_point/tf_bot_defend_point.h" +#include "bot/behavior/scenario/capture_point/tf_bot_capture_point.h" +#include "bot/behavior/medic/tf_bot_medic_heal.h" +#include "bot/behavior/tf_bot_attack.h" +#include "bot/behavior/tf_bot_seek_and_destroy.h" +#include "bot/behavior/engineer/tf_bot_engineer_build.h" +#include "bot/behavior/scenario/capture_point/tf_bot_defend_point_block_capture.h" +#include "bot/behavior/sniper/tf_bot_sniper_attack.h" +#include "bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h" + + +extern ConVar tf_bot_path_lookahead_range; +extern ConVar tf_bot_min_setup_gate_defend_range; +extern ConVar tf_bot_max_setup_gate_defend_range; +extern ConVar tf_bot_min_setup_gate_sniper_defend_range; +extern ConVar tf_bot_offense_must_push_time; + +ConVar tf_bot_defense_must_defend_time( "tf_bot_defense_must_defend_time", "300", FCVAR_CHEAT, "If timer is less than this, bots will stay near point and guard" ); +ConVar tf_bot_max_point_defend_range( "tf_bot_max_point_defend_range", "1250", FCVAR_CHEAT, "How far (in travel distance) from the point defending bots will take up positions" ); +ConVar tf_bot_defense_debug( "tf_bot_defense_debug", "0", FCVAR_CHEAT ); + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotDefendPoint::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() ); + + m_defenseArea = NULL; + + // higher skilled bots prefer to seek and destroy until the time is almost up + static float roamChance[ CTFBot::NUM_DIFFICULTY_LEVELS ] = { 10.0f, 50.0f, 75.0f, 90.0f }; + m_isAllowedToRoam = ( RandomFloat( 0.0f, 100.0f ) < roamChance[ (int)clamp( me->GetDifficulty(), CTFBot::EASY, CTFBot::EXPERT ) ] ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +/** + * Return true if we're in immediate danger of losing the point + */ +bool CTFBotDefendPoint::IsPointThreatened( CTFBot *me ) +{ + CTeamControlPoint *point = me->GetMyControlPoint(); + + if ( point == NULL ) + return false; + + if ( point->LastContestedAt() > 0.0f && ( gpGlobals->curtime - point->LastContestedAt() ) < 5.0f ) + { + // the point is, or was very recently, contested + return true; + } + + // if we just lost a point, we should fall back and stand on the next point to defend against a rush + if ( me->WasPointJustLost() ) + { + return true; + } + +/* + // if an enemy is closer to the point than we are, head them off + // TODO: Compare time to reach, not distance + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( threat ) + { + const float tolerance = 100.0f; + + float themRange = ( threat->GetLastKnownPosition() - point->GetAbsOrigin() ).Length(); + float myRange = ( me->GetAbsOrigin() - point->GetAbsOrigin() ).Length(); + if ( myRange + tolerance > themRange ) + return true; + } +*/ + + return false; +} + + +//--------------------------------------------------------------------------------------------- +// Are we smart enough to get on the point to block the cap +bool CTFBotDefendPoint::WillBlockCapture( CTFBot *me ) const +{ + if ( TFGameRules()->IsInTraining() ) + return false; + + if ( me->IsDifficulty( CTFBot::EASY ) ) + return false; + + if ( me->IsDifficulty( CTFBot::NORMAL ) ) + { + // 50% chance of blocking cap + return me->TransientlyConsistentRandomValue() > 0.5f; + } + + return true; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotDefendPoint::Update( CTFBot *me, float interval ) +{ + // King of the Hill logic + CTeamControlPointMaster *master = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL; + if ( master && master->GetNumPoints() == 1 ) + { + // if we don't own the only point, switch to capture behavior + CTeamControlPoint *point = master->GetControlPoint( 0 ); + if ( point && point->GetOwner() != me->GetTeamNumber() ) + { + return ChangeTo( new CTFBotCapturePoint, "We need to capture the point!" ); + } + } + + CTeamControlPoint *point = me->GetMyControlPoint(); + + if ( point == NULL ) + { + const float roamTime = 10.0f; + return SuspendFor( new CTFBotSeekAndDestroy( roamTime ), "Seek and destroy until a point becomes available" ); + } + + if ( point->GetTeamNumber() != me->GetTeamNumber() ) + { + return ChangeTo( new CTFBotCapturePoint, "We need to capture our point(s)" ); + } + + // if point in is danger - get ON the point! + // Don't do this in training to keep things easy for the new trainee + if ( IsPointThreatened( me ) && WillBlockCapture( me ) ) + { + // point is being captured - get on it! + return SuspendFor( new CTFBotDefendPointBlockCapture, "Moving to block point capture!" ); + } + + // point is safe for the moment + + // if I'm uber'd, go get 'em! + if ( me->m_Shared.InCond( TF_COND_INVULNERABLE ) ) + { + const float uberChargeTime = 6.0; + return SuspendFor( new CTFBotSeekAndDestroy( uberChargeTime ), "Attacking because I'm uber'd!" ); + } + + if ( point && point->IsLocked() ) + { + return SuspendFor( new CTFBotSeekAndDestroy, "Seek and destroy until the point unlocks" ); + } + + if ( m_isAllowedToRoam && me->GetTimeLeftToCapture() > tf_bot_defense_must_defend_time.GetFloat() ) + { + return SuspendFor( new CTFBotSeekAndDestroy( 15.0f ), "Seek and destroy - we have lots of time" ); + } + + if ( TFGameRules()->InSetup() ) + { + // don't lose patience during setup time + m_idleTimer.Reset(); + } + + // if we see an enemy as we have a melee weapon equipped, chase them down + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + + me->EquipBestWeaponForThreat( threat ); + + if ( threat && threat->IsVisibleRecently() ) + { + // we're aware of an enemy + m_idleTimer.Reset(); + + if ( me->IsPlayerClass( TF_CLASS_PYRO ) ) + { + // go get 'em + return SuspendFor( new CTFBotSeekAndDestroy( 15.0f ), "Going after an enemy" ); + } + + CTFWeaponBase *myWeapon = me->m_Shared.GetActiveTFWeapon(); + if ( myWeapon && ( myWeapon->IsMeleeWeapon() || myWeapon->IsWeapon( TF_WEAPON_FLAMETHROWER ) ) ) + { + // TODO: Check if threat is visible and if not, move to last known position + CTFBotPathCost cost( me, me->IsPlayerClass( TF_CLASS_PYRO ) ? SAFEST_ROUTE : FASTEST_ROUTE ); + m_chasePath.Update( me, threat->GetEntity(), cost ); + + return Continue(); + } + } + + // choose where we'll defend from + if ( m_defenseArea == NULL || m_idleTimer.IsElapsed() ) + { + m_defenseArea = SelectAreaToDefendFrom( me ); + } + + if ( m_defenseArea ) + { + if ( me->GetLastKnownArea() == m_defenseArea ) + { + // at our defense position + if ( CTFBotPrepareStickybombTrap::IsPossible( me ) ) + { + return SuspendFor( new CTFBotPrepareStickybombTrap, "Laying sticky bombs!" ); + } + } + else + { + // move to our desired defense position, repathing periodically to account for changing situation + VPROF_BUDGET( "CTFBotDefendPoint::Update( repath )", "NextBot" ); + + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 2.0f, 3.0f ) ); + + CTFBotPathCost cost( me, DEFAULT_ROUTE ); + m_path.Compute( me, m_defenseArea->GetCenter(), cost ); + } + + m_path.Update( me ); + + // we're not idle while we're moving to our defend position + m_idleTimer.Reset(); + } + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotDefendPoint::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + // may have lost point - recheck + me->ClearMyControlPoint(); + m_repathTimer.Invalidate(); + m_path.Invalidate(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPoint::OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPoint::OnStuck( CTFBot *me ) +{ + m_path.Invalidate(); + m_defenseArea = SelectAreaToDefendFrom( me ); + me->GetLocomotionInterface()->ClearStuckStatus(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPoint::OnMoveToSuccess( CTFBot *me, const Path *path ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPoint::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ) +{ + m_path.Invalidate(); + m_defenseArea = SelectAreaToDefendFrom( me ); + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPoint::OnTerritoryContested( CTFBot *me, int territoryID ) +{ + // handled in the Update() loop + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPoint::OnTerritoryCaptured( CTFBot *me, int territoryID ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPoint::OnTerritoryLost( CTFBot *me, int territoryID ) +{ + // we lost it, fall back to next point + me->ClearMyControlPoint(); + m_defenseArea = SelectAreaToDefendFrom( me ); + m_repathTimer.Invalidate(); + m_path.Invalidate(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +class CSelectDefenseAreaForPoint : public ISearchSurroundingAreasFunctor +{ +public: + CSelectDefenseAreaForPoint( CTFNavArea *pointArea, int myTeam, CUtlVector< CTFNavArea * > *areaVector ) + { + m_pointArea = pointArea; + m_myTeam = myTeam; + + // don't select areas that are beyond the point + m_incursionFlowLimit = pointArea->GetIncursionDistance( m_myTeam ) + 250.0f; + + m_areaVector = areaVector; + m_areaVector->RemoveAll(); + } + + virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar ) + { + CTFNavArea *area = (CTFNavArea *)baseArea; + + if ( !TFGameRules()->IsInKothMode() ) + { + // don't select areas that are beyond the point + if ( area->GetIncursionDistance( m_myTeam ) > m_incursionFlowLimit ) + return true; + } + + if ( area->IsPotentiallyVisible( m_pointArea ) ) + { + // a bit of a hack here to avoid bots choosing to defend in bottom of ravine at stage 3 of dustbowl + const float tooLow = 220.0f; + if ( m_pointArea->GetCenter().z - area->GetCenter().z < tooLow ) + { + // valid defense position + m_areaVector->AddToTail( area ); + } + } + + return true; + } + + virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar ) + { + if ( adjArea->IsBlocked( TFGameRules()->IsInKothMode() ? TEAM_ANY : m_myTeam ) ) + { + return false; + } + + if ( travelDistanceSoFar > tf_bot_max_point_defend_range.GetFloat() ) + { + // too far away + return false; + } + + const float maxHeightChange = 65.0f; + float deltaZ = currentArea->ComputeAdjacentConnectionHeightChange( adjArea ); + return ( fabs( deltaZ ) < maxHeightChange ); + } + + CTFNavArea *m_pointArea; + CUtlVector< CTFNavArea * > *m_areaVector; + float m_incursionFlowLimit; + int m_myTeam; +}; + + +//--------------------------------------------------------------------------------------------- +/** + * Select the area where we will guard the point from + */ +CTFNavArea *CTFBotDefendPoint::SelectAreaToDefendFrom( CTFBot *me ) +{ + VPROF_BUDGET( "CTFBotDefendPoint::SelectAreaToDefendFrom", "NextBot" ); + + CTeamControlPoint *point = me->GetMyControlPoint(); + if ( !point ) + { + return NULL; + } + + // decide where we will defend from + CUtlVector< CTFNavArea * > defenseAreas; + +/* + if ( !TFGameRules()->IsInKothMode() && + point->GetTeamCapPercentage( me->GetTeamNumber() ) <= 0.0f && // point is currently safe + ( ObjectiveResource()->GetPreviousPointForPoint( point->GetPointIndex(), me->GetTeamNumber(), 0 ) < 0 || // this is the first cap point + me->IsPlayerClass( TF_CLASS_PYRO ) ) ) // pyros are skirmishers + { + if ( TheTFNavMesh()->GetSetupGateDefenseAreas() ) + { + defenseAreas = *TheTFNavMesh()->GetSetupGateDefenseAreas(); + } + } +*/ + + if ( defenseAreas.Count() == 0 ) + { + CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() ); + if ( pointArea ) + { + // search outwards from the point along walkable areas (not drop downs) to make sure we can get back to the point quickly + CSelectDefenseAreaForPoint defenseScan( pointArea, me->GetTeamNumber(), &defenseAreas ); + SearchSurroundingAreas( pointArea, defenseScan ); + } + } + + // select a specific area from the potential defense set + if ( defenseAreas.Count() == 0 ) + { + return NULL; + } + + // how long will we wait if we don't see any action + m_idleTimer.Start( RandomFloat( 10.0f, 20.0f ) ); + + if ( tf_bot_defense_debug.GetBool() ) + { + for( int i=0; i<defenseAreas.Count(); ++i ) + { + defenseAreas[i]->DrawFilled( 0, 200, 200, 999.9f ); + } + } + + // select one of the defense areas + int which = RandomInt( 0, defenseAreas.Count()-1 ); + return defenseAreas[ which ]; +} + diff --git a/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point.h b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point.h new file mode 100644 index 0000000..c68a55c --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point.h @@ -0,0 +1,48 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_defend_point.h +// Move to and defend current point from capture +// Michael Booth, February 2009 + +#ifndef TF_BOT_DEFEND_POINT_H +#define TF_BOT_DEFEND_POINT_H + +#include "Path/NextBotPathFollow.h" +#include "Path/NextBotChasePath.h" + +class CTFBotDefendPoint : public Action< CTFBot > +{ +public: + 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 > OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result = NULL ); + + 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 > OnTerritoryContested( CTFBot *me, int territoryID ); + virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID ); + virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID ); + + virtual const char *GetName( void ) const { return "DefendPoint"; }; + +private: + PathFollower m_path; // for moving to a defense position + ChasePath m_chasePath; // for chasing enemies + + CountdownTimer m_repathTimer; + CountdownTimer m_lookAroundTimer; + CountdownTimer m_idleTimer; + + CTFNavArea *m_defenseArea; + CTFNavArea *SelectAreaToDefendFrom( CTFBot *me ); + + bool IsPointThreatened( CTFBot *me ); + bool WillBlockCapture( CTFBot *me ) const; + bool m_isAllowedToRoam; +}; + + +#endif // TF_BOT_DEFEND_POINT_H diff --git a/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point_block_capture.cpp b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point_block_capture.cpp new file mode 100644 index 0000000..6e5bbf7 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point_block_capture.cpp @@ -0,0 +1,247 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_defend_point_block_capture.h +// Move to and defend current point from capture +// Michael Booth, February 2009 + +#include "cbase.h" +#include "nav_mesh/tf_nav_mesh.h" +#include "tf_player.h" +#include "tf_gamerules.h" +#include "trigger_area_capture.h" +#include "bot/tf_bot.h" +#include "bot/behavior/scenario/capture_point/tf_bot_defend_point_block_capture.h" +#include "bot/behavior/medic/tf_bot_medic_heal.h" +#include "bot/behavior/tf_bot_attack.h" +#include "bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h" + + +extern ConVar tf_bot_path_lookahead_range; + +ConVar tf_bot_defend_owned_point_percent( "tf_bot_defend_owned_point_percent", "0.5", FCVAR_CHEAT, "Stay on the contested point we own until enemy cap percent falls below this" ); + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotDefendPointBlockCapture::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() ); + + m_point = me->GetMyControlPoint(); + if ( m_point == NULL ) + { + return Done( "Point is NULL" ); + } + + m_defenseArea = static_cast< CTFNavArea * >( TheTFNavMesh()->GetNearestNavArea( m_point->GetAbsOrigin() ) ); + if ( m_defenseArea == NULL ) + { + return Done( "Can't find nav area on point" ); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +bool CTFBotDefendPointBlockCapture::IsPointSafe( CTFBot *me ) +{ + // if a point was just captured, defend this point for awhile + if ( me->WasPointJustLost() ) + { + return false; + } + + if ( m_point == NULL ) + { + return true; + } + + if ( m_point->GetTeamCapPercentage( me->GetTeamNumber() ) < tf_bot_defend_owned_point_percent.GetFloat() ) + { + // we're not in complete control of this point yet + return false; + } + + // is point is being contested, or was just being contested, its not safe + if ( m_point->HasBeenContested() && ( gpGlobals->curtime - m_point->LastContestedAt() ) < 5.0f ) + { + return false; + } + + // if we still see a near threat, stay put + const CKnownEntity *knownThreat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( knownThreat ) + { + const float dangerRange = 500.0f; + if ( ( knownThreat->GetLastKnownPosition() - m_point->GetAbsOrigin() ).IsLengthLessThan( dangerRange ) ) + return false; + } + + return true; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotDefendPointBlockCapture::Update( CTFBot *me, float interval ) +{ + // if point is safe, we can move back to our defense positions + if ( IsPointSafe( me ) ) + { + return Done( "Point is safe again" ); + } + + if ( me->IsPlayerClass( TF_CLASS_MEDIC ) ) + { + // medics look ridiculous rushing to the point - they need to heal + return SuspendFor( new CTFBotMedicHeal ); + } + + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + me->EquipBestWeaponForThreat( threat ); + + Extent pointExtent; + pointExtent.Init( m_point ); + + bool isStandingOnThePoint = pointExtent.Contains( me->GetAbsOrigin() ); + + const CUtlVector< CTFNavArea * > *controlPointAreas = TheTFNavMesh()->GetControlPointAreas( m_point->GetPointIndex() ); + if ( controlPointAreas ) + { + for( int i=0; i<controlPointAreas->Count(); ++i ) + { + if ( me->GetLastKnownArea() && me->GetLastKnownArea()->GetID() == controlPointAreas->Element(i)->GetID() ) + { + isStandingOnThePoint = true; + } + } + } + + if ( isStandingOnThePoint && CTFBotPrepareStickybombTrap::IsPossible( me ) ) + { + return SuspendFor( new CTFBotPrepareStickybombTrap, "Placing stickies for defense" ); + } + + if ( controlPointAreas ) + { + // move to a random spot on this control point + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) ); + + float totalArea = 0.0f; + int i; + for( i=0; i<controlPointAreas->Count(); ++i ) + { + CTFNavArea *area = controlPointAreas->Element(i); + totalArea += area->GetSizeX() * area->GetSizeY(); + } + + float which = RandomFloat( 0.0f, totalArea - 1.0f ); + CTFNavArea *goalArea = NULL; + for( i=0; i<controlPointAreas->Count(); ++i ) + { + CTFNavArea *area = controlPointAreas->Element(i); + which -= area->GetSizeX() * area->GetSizeY(); + if ( which <= 0.0f ) + { + goalArea = area; + break; + } + } + + if ( goalArea ) + { + CTFBotPathCost cost( me, DEFAULT_ROUTE ); + m_path.Compute( me, goalArea->GetRandomPoint(), cost ); + } + } + + m_path.Update( me ); + } + else if ( !isStandingOnThePoint ) + { + // get on the point! + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) ); + + CTFBotPathCost cost( me, DEFAULT_ROUTE ); + m_path.Compute( me, ( pointExtent.lo + pointExtent.hi )/2.0f, cost ); + } + + m_path.Update( me ); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotDefendPointBlockCapture::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + m_path.Invalidate(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnStuck( CTFBot *me ) +{ + m_path.Invalidate(); + me->GetLocomotionInterface()->ClearStuckStatus(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnMoveToSuccess( CTFBot *me, const Path *path ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ) +{ + m_path.Invalidate(); + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnTerritoryContested( CTFBot *me, int territoryID ) +{ + return TryToSustain(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnTerritoryCaptured( CTFBot *me, int territoryID ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotDefendPointBlockCapture::OnTerritoryLost( CTFBot *me, int territoryID ) +{ + // we lost it, fall back + return TryDone( RESULT_CRITICAL, "Lost the point" ); +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotDefendPointBlockCapture::ShouldHurry( const INextBot *me ) const +{ + // hurry up and get on the point! + return ANSWER_YES; +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotDefendPointBlockCapture::ShouldRetreat( const INextBot *me ) const +{ + // get on the point! + return ANSWER_NO; +} diff --git a/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point_block_capture.h b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point_block_capture.h new file mode 100644 index 0000000..55ab7af --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/capture_point/tf_bot_defend_point_block_capture.h @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_defend_point_block_capture.h +// Move to and defend current point from capture +// Michael Booth, February 2009 + +#ifndef TF_BOT_DEFEND_POINT_BLOCK_CAPTURE_H +#define TF_BOT_DEFEND_POINT_BLOCK_CAPTURE_H + + +class CTFBotDefendPointBlockCapture : public Action< CTFBot > +{ +public: + 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 EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path ); + virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ); + + virtual EventDesiredResult< CTFBot > OnTerritoryContested( CTFBot *me, int territoryID ); + virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID ); + virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID ); + + virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry? + virtual QueryResultType ShouldRetreat( const INextBot *me ) const; + + virtual const char *GetName( void ) const { return "BlockCapture"; }; + +private: + PathFollower m_path; + CountdownTimer m_repathTimer; + CTeamControlPoint *m_point; + CTFNavArea *m_defenseArea; + + bool IsPointSafe( CTFBot *me ); +}; + + +#endif // TF_BOT_DEFEND_POINT_BLOCK_CAPTURE_H |