diff options
Diffstat (limited to 'game/server/tf/bot/behavior/scenario')
32 files changed, 4228 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 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 diff --git a/game/server/tf/bot/behavior/scenario/creep_wave/tf_bot_creep_wave.cpp b/game/server/tf/bot/behavior/scenario/creep_wave/tf_bot_creep_wave.cpp new file mode 100644 index 0000000..fb93882 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/creep_wave/tf_bot_creep_wave.cpp @@ -0,0 +1,240 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_creep_wave.cpp +// Move in a "creep wave" to the next available control point to capture it +// Michael Booth, August 2010 + +#include "cbase.h" + +#ifdef TF_CREEP_MODE + +#include "team.h" +#include "team_control_point_master.h" +#include "bot/tf_bot_manager.h" +#include "bot/tf_bot.h" +#include "bot/behavior/scenario/creep_wave/tf_bot_creep_wave.h" + +ConVar tf_creep_aggro_range( "tf_creep_aggro_range", "250" ); +ConVar tf_creep_give_up_range( "tf_creep_give_up_range", "300" ); + + +CTFPlayer *FindNearestEnemy( CTFBot *me, float maxRange ) +{ + CBasePlayer *closest = NULL; + float closeRangeSq = maxRange * maxRange; + + for( int i=1; i<=gpGlobals->maxClients; ++i ) + { + CBasePlayer *player = static_cast< CBasePlayer * >( UTIL_PlayerByIndex( i ) ); + + if ( !player ) + continue; + + if ( FNullEnt( player->edict() ) ) + continue; + + if ( me->IsFriend( player ) ) + continue; + + if ( !player->IsAlive() ) + continue; + + float rangeSq = me->GetRangeSquaredTo( player ); + if ( rangeSq < closeRangeSq ) + { + if ( me->IsLineOfFireClear( player ) ) + { + closeRangeSq = rangeSq; + closest = player; + } + } + } + + return (CTFPlayer *)closest; +} + + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotCreepWave::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + me->StopLookingAroundForEnemies(); + m_stuckTimer.Invalidate(); + + me->GetPlayerClass()->SetCustomModel( "models/bots/bot_heavy.mdl", USE_CLASS_ANIMATIONS ); + me->UpdateModel(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotCreepWave::Update( CTFBot *me, float interval ) +{ + if ( !me->IsAlive() && me->StateGet() != TF_STATE_DYING ) + { + // remove dead creeps for now + engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", me->GetUserID() ) ); + } + + CBaseCombatWeapon *melee = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE ); + if ( melee ) + { + me->Weapon_Switch( melee ); + } + + CUtlVector< CTeamControlPoint * > captureVector; + TFGameRules()->CollectCapturePoints( me, &captureVector ); + + if ( captureVector.Count() == 0 ) + { + return Continue(); + } + + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_path.Compute( me, captureVector[0]->WorldSpaceCenter(), cost ); + } + + m_path.Update( me ); + + if ( m_stuckTimer.HasStarted() ) + { + // juke and dodge to escape stuck situation + switch( RandomInt( 0, 3 ) ) + { + case 0: me->PressBackwardButton(); break; + case 1: me->PressForwardButton(); break; + case 2: me->PressLeftButton(); break; + case 3: me->PressRightButton(); break; + } + } + + CTFPlayer *enemy = FindNearestEnemy( me, tf_creep_aggro_range.GetFloat() ); + if ( enemy ) + { + me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_INCOMING ); + return SuspendFor( new CTFBotCreepAttack( enemy ), "Attacking nearby enemy" ); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotCreepWave::OnKilled( CTFBot *me, const CTakeDamageInfo &info ) +{ + if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() && me->IsEnemy( info.GetAttacker() ) ) + { + TheTFBots().OnCreepKilled( ToTFPlayer( info.GetAttacker() ) ); + } + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotCreepWave::OnStuck( CTFBot *me ) +{ + m_stuckTimer.Start(); + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotCreepWave::OnUnStuck( CTFBot *me ) +{ + m_stuckTimer.Invalidate(); + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +CTFBotCreepAttack::CTFBotCreepAttack( CTFPlayer *victim ) +{ + m_victim = victim; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotCreepAttack::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotCreepAttack::Update( CTFBot *me, float interval ) +{ + if ( !me->IsAlive() && me->StateGet() != TF_STATE_DYING ) + { + // remove dead creeps for now + engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", me->GetUserID() ) ); + } + + if ( m_victim.Get() == NULL || !m_victim->IsAlive() ) + { + me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_JEERS ); + return Done( "Killed victim" ); + } + + if ( me->IsRangeGreaterThan( m_victim, tf_creep_give_up_range.GetFloat() ) || + !me->IsLineOfFireClear( m_victim ) ) + { + me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_NEGATIVE ); + return Done( "Lost victim" ); + } + + CTFPlayer *newVictim = FindNearestEnemy( me, tf_creep_aggro_range.GetFloat() ); + if ( newVictim ) + { + float newRangeSq = me->GetRangeSquaredTo( newVictim ); + float victimRangeSq = me->GetRangeSquaredTo( m_victim ); + + if ( newRangeSq < victimRangeSq ) + { + // switch to closer target + m_victim = newVictim; + me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY ); + } + } + + CBaseCombatWeapon *melee = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE ); + if ( melee ) + { + me->Weapon_Switch( melee ); + } + + me->GetBodyInterface()->AimHeadTowards( m_victim, IBody::CRITICAL, 0.2f, NULL, "Looking at enemy" ); + + // swing weapon + me->PressFireButton(); + + // beeline towards our victim + const float combatRange = 40.0f; + if ( me->IsRangeGreaterThan( m_victim, combatRange ) ) + { + me->GetLocomotionInterface()->Approach( m_victim->GetAbsOrigin() ); + } + else + { + // juke and dodge to avoid interpenetration + switch( RandomInt( 0, 3 ) ) + { + case 0: me->PressBackwardButton(); break; + case 1: me->PressForwardButton(); break; + case 2: me->PressLeftButton(); break; + case 3: me->PressRightButton(); break; + } + } + + return Continue(); +} + + + + +#endif // TF_CREEP_MODE
\ No newline at end of file diff --git a/game/server/tf/bot/behavior/scenario/creep_wave/tf_bot_creep_wave.h b/game/server/tf/bot/behavior/scenario/creep_wave/tf_bot_creep_wave.h new file mode 100644 index 0000000..6dedafe --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/creep_wave/tf_bot_creep_wave.h @@ -0,0 +1,57 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_creep_wave.h +// Move in a "creep wave" to the next available control point to capture it +// Michael Booth, August 2010 + +#ifndef TF_BOT_CREEP_WAVE_H +#define TF_BOT_CREEP_WAVE_H + +#ifdef TF_CREEP_MODE + +#include "Path/NextBotPathFollow.h" +#include "Path/NextBotChasePath.h" + + +CTFBot *FindNearestEnemyCreep( CTFBot *me ); + + +//----------------------------------------------------------------------------- +class CTFBotCreepWave : public Action< CTFBot > +{ +public: + virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction ); + virtual ActionResult< CTFBot > Update( CTFBot *me, float interval ); + + virtual EventDesiredResult< CTFBot > OnKilled( CTFBot *me, const CTakeDamageInfo &info ); + virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me ); + virtual EventDesiredResult< CTFBot > OnUnStuck( CTFBot *me ); + + virtual const char *GetName( void ) const { return "CreepWave"; }; + +private: + PathFollower m_path; + CountdownTimer m_repathTimer; + IntervalTimer m_stuckTimer; +}; + + + +//----------------------------------------------------------------------------- +class CTFBotCreepAttack : public Action< CTFBot > +{ +public: + CTFBotCreepAttack( CTFPlayer *victim ); + + virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction ); + virtual ActionResult< CTFBot > Update( CTFBot *me, float interval ); + + virtual const char *GetName( void ) const { return "CreepAttack"; }; + +private: + CHandle< CTFPlayer > m_victim; +}; + + +#endif // TF_CREEP_MODE + +#endif // TF_BOT_CREEP_WAVE_H diff --git a/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_block.cpp b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_block.cpp new file mode 100644 index 0000000..3f34071 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_block.cpp @@ -0,0 +1,151 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_payload_block.cpp +// Prevent the other team from moving the cart +// Michael Booth, April 2010 + +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_gamerules.h" +#include "team_control_point_master.h" +#include "team_train_watcher.h" +#include "bot/tf_bot.h" +#include "bot/behavior/scenario/payload/tf_bot_payload_block.h" +#include "bot/behavior/medic/tf_bot_medic_heal.h" +#include "bot/behavior/engineer/tf_bot_engineer_build.h" + + +extern ConVar tf_bot_path_lookahead_range; + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotPayloadBlock::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() ); + m_path.Invalidate(); + + m_giveUpTimer.Start( RandomFloat( 3.0f, 5.0f ) ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotPayloadBlock::Update( CTFBot *me, float interval ) +{ + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( threat && threat->IsVisibleRecently() ) + { + // prepare to fight + me->EquipBestWeaponForThreat( threat ); + } + + if ( m_giveUpTimer.IsElapsed() ) + { + return Done( "Been blocking long enough" ); + } + + // move toward the point, periodically repathing to account for changing situation + if ( m_repathTimer.IsElapsed() ) + { + VPROF_BUDGET( "CTFBotPayloadBlock::Update( repath )", "NextBot" ); + + CTeamTrainWatcher *trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() ); + if ( !trainWatcher ) + { + return Done( "Train Watcher is missing" ); + } + + CBaseEntity *cart = trainWatcher->GetTrainEntity(); + if ( !cart ) + { + return Done( "Cart is missing" ); + } + + CTFBotPathCost cost( me, DEFAULT_ROUTE ); + m_path.Compute( me, cart->WorldSpaceCenter(), cost ); + m_repathTimer.Start( RandomFloat( 0.2f, 0.4f ) ); + } + + // move towards next capture point + m_path.Update( me ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotPayloadBlock::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + VPROF_BUDGET( "CTFBotPayloadBlock::OnResume", "NextBot" ); + + m_repathTimer.Invalidate(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnStuck( CTFBot *me ) +{ + VPROF_BUDGET( "CTFBotPayloadBlock::OnStuck", "NextBot" ); + + m_repathTimer.Invalidate(); + me->GetLocomotionInterface()->ClearStuckStatus(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnMoveToSuccess( CTFBot *me, const Path *path ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ) +{ + VPROF_BUDGET( "CTFBotPayloadBlock::OnMoveToFailure", "NextBot" ); + + m_repathTimer.Invalidate(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnTerritoryContested( CTFBot *me, int territoryID ) +{ + return TryToSustain( RESULT_IMPORTANT ); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnTerritoryCaptured( CTFBot *me, int territoryID ) +{ + return TryToSustain( RESULT_IMPORTANT ); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadBlock::OnTerritoryLost( CTFBot *me, int territoryID ) +{ + return TryToSustain( RESULT_IMPORTANT ); +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotPayloadBlock::ShouldRetreat( const INextBot *bot ) const +{ + return ANSWER_UNDEFINED; +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotPayloadBlock::ShouldHurry( const INextBot *bot ) const +{ + // hurry and block the cart - don't retreat, etc + return ANSWER_YES; +} + diff --git a/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_block.h b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_block.h new file mode 100644 index 0000000..9b934f2 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_block.h @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_payload_block.h +// Prevent the other team from moving the cart +// Michael Booth, April 2010 + +#ifndef TF_BOT_PAYLOAD_BLOCK_H +#define TF_BOT_PAYLOAD_BLOCK_H + +#include "Path/NextBotPathFollow.h" + +class CTFBotPayloadBlock : 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 "PayloadBlock"; }; + +private: + PathFollower m_path; + CountdownTimer m_repathTimer; + + CountdownTimer m_giveUpTimer; +}; + +#endif // TF_BOT_PAYLOAD_BLOCK_H diff --git a/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_guard.cpp b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_guard.cpp new file mode 100644 index 0000000..a09d186 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_guard.cpp @@ -0,0 +1,283 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_payload_guard.cpp +// Guard the payload and keep the attackers from getting near it +// Michael Booth, April 2010 + +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_gamerules.h" +#include "team_control_point_master.h" +#include "team_train_watcher.h" +#include "trigger_area_capture.h" +#include "bot/tf_bot.h" +#include "bot/behavior/scenario/payload/tf_bot_payload_guard.h" +#include "bot/behavior/scenario/payload/tf_bot_payload_block.h" +#include "bot/behavior/medic/tf_bot_medic_heal.h" +#include "bot/behavior/engineer/tf_bot_engineer_build.h" +#include "bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h" + + +extern ConVar tf_bot_path_lookahead_range; + +ConVar tf_bot_payload_guard_range( "tf_bot_payload_guard_range", "1000", FCVAR_CHEAT ); +ConVar tf_bot_debug_payload_guard_vantage_points( "tf_bot_debug_payload_guard_vantage_points", 0, FCVAR_CHEAT ); + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotPayloadGuard::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() ); + m_path.Invalidate(); + + m_vantagePoint = me->GetAbsOrigin(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotPayloadGuard::Update( CTFBot *me, float interval ) +{ + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( threat && threat->IsVisibleRecently() ) + { + // prepare to fight + me->EquipBestWeaponForThreat( threat ); + } + + CTeamTrainWatcher *trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() ); + if ( !trainWatcher ) + { + return Continue(); + } + + CBaseEntity *cart = trainWatcher->GetTrainEntity(); + if ( !cart ) + { + return Continue(); + } + + if ( !trainWatcher->IsDisabled() && trainWatcher->GetCapturerCount() > 0 ) + { + // the cart is being pushed ahead - block it + if ( !m_moveToBlockTimer.HasStarted() ) + { + m_moveToBlockTimer.Start( RandomFloat( 0.5f, 3.0f ) ); + } + } + + if ( m_moveToBlockTimer.HasStarted() && m_moveToBlockTimer.IsElapsed() ) + { + m_moveToBlockTimer.Invalidate(); + + if ( trainWatcher->GetCapturerCount() >= 0 ) + { + // the cart is not yet blocked - move to block it! + return SuspendFor( new CTFBotPayloadBlock, "Moving to block the cart's forward motion" ); + } + } + + bool isMovingToVantagePoint = ( me->GetAbsOrigin() - m_vantagePoint ).AsVector2D().IsLengthGreaterThan( 25.0f ); + + if ( isMovingToVantagePoint ) + { + // en route, don't change the point + m_vantagePointTimer.Start( RandomFloat( 3.0f, 15.0f ) ); + } + + if ( !me->IsLineOfFireClear( cart ) ) + { + // cart is no longer visible from this area, find another one + m_vantagePointTimer.Invalidate(); + } + + if ( m_vantagePointTimer.IsElapsed() ) + { + // find a new vantage point + m_vantagePoint = FindVantagePoint( me, cart ); + m_repathTimer.Invalidate(); + isMovingToVantagePoint = true; + } + + if ( isMovingToVantagePoint ) + { + // update our path periodically + if ( m_repathTimer.IsElapsed() ) + { + CTFBotPathCost cost( me, DEFAULT_ROUTE ); + m_path.Compute( me, m_vantagePoint, cost ); + m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) ); + } + + // move towards our vantage point + m_path.Update( me ); + } + else + { + // at vantage point + if ( CTFBotPrepareStickybombTrap::IsPossible( me ) ) + { + return SuspendFor( new CTFBotPrepareStickybombTrap, "Laying sticky bombs!" ); + } + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotPayloadGuard::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + VPROF_BUDGET( "CTFBotPayloadGuard::OnResume", "NextBot" ); + + m_vantagePointTimer.Invalidate(); + m_repathTimer.Invalidate(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnStuck( CTFBot *me ) +{ + VPROF_BUDGET( "CTFBotPayloadGuard::OnStuck", "NextBot" ); + + m_repathTimer.Invalidate(); + me->GetLocomotionInterface()->ClearStuckStatus(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnMoveToSuccess( CTFBot *me, const Path *path ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ) +{ + VPROF_BUDGET( "CTFBotPayloadGuard::OnMoveToFailure", "NextBot" ); + + m_vantagePointTimer.Invalidate(); + m_repathTimer.Invalidate(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +// Invoked when cart is being pushed +EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnTerritoryContested( CTFBot *me, int territoryID ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnTerritoryCaptured( CTFBot *me, int territoryID ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +// Invoked when cart hits a checkpoint +EventDesiredResult< CTFBot > CTFBotPayloadGuard::OnTerritoryLost( CTFBot *me, int territoryID ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotPayloadGuard::ShouldRetreat( const INextBot *bot ) const +{ + CTFBot *me = ToTFBot( bot->GetEntity() ); + + CTeamTrainWatcher *trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() ); + if ( trainWatcher && trainWatcher->IsTrainNearCheckpoint() ) + { + // don't retreat if the cart is almost at the next checkpoint + return ANSWER_NO; + } + + return ANSWER_UNDEFINED; +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotPayloadGuard::ShouldHurry( const INextBot *bot ) const +{ + return ANSWER_UNDEFINED; +} + + +//--------------------------------------------------------------------------------------------- +class CCollectPayloadGuardVantagePoints : public ISearchSurroundingAreasFunctor +{ +public: + CCollectPayloadGuardVantagePoints( CTFBot *me, CBaseEntity *cart ) + { + m_me = me; + m_cart = cart; + } + + virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar ) + { + CTFNavArea *area = (CTFNavArea *)baseArea; + + // TODO: only use areas that are at/farther along than the payload + + trace_t trace; + NextBotTraceFilterIgnoreActors filter( NULL, COLLISION_GROUP_NONE ); + + const int tryCount = 3; + + for( int i=0; i<tryCount; ++i ) + { + Vector spot = area->GetRandomPoint(); + Vector eyeSpot = Vector( spot.x, spot.y, spot.z + HumanEyeHeight ); + + UTIL_TraceLine( eyeSpot, m_cart->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, &filter, &trace ); + + if ( !trace.DidHit() || trace.m_pEnt == m_cart ) + { + m_vantagePointVector.AddToTail( spot ); + + if ( tf_bot_debug_payload_guard_vantage_points.GetBool() ) + { + NDebugOverlay::Cross3D( spot, 5.0f, 255, 0, 255, true, 120.0f ); + } + } + } + + return true; + } + + CTFBot *m_me; + CBaseEntity *m_cart; + CUtlVector< Vector > m_vantagePointVector; +}; + + +//--------------------------------------------------------------------------------------------- +// +// Find a tactically advantageous area where we can see the payload +// +Vector CTFBotPayloadGuard::FindVantagePoint( CTFBot *me, CBaseEntity *cart ) +{ + CTFNavArea *cartArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( cart ); + + CCollectPayloadGuardVantagePoints collect( me, cart ); + SearchSurroundingAreas( cartArea, collect, tf_bot_payload_guard_range.GetFloat() ); + + if ( collect.m_vantagePointVector.Count() == 0 ) + return cart->WorldSpaceCenter(); + + int which = RandomInt( 0, collect.m_vantagePointVector.Count()-1 ); + return collect.m_vantagePointVector[ which ]; +} + diff --git a/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_guard.h b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_guard.h new file mode 100644 index 0000000..2d1b03d --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_guard.h @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_payload_guard.h +// Guard the payload and keep the attackers from getting near it +// Michael Booth, April 2010 + +#ifndef TF_BOT_PAYLOAD_GUARD_H +#define TF_BOT_PAYLOAD_GUARD_H + +#include "Path/NextBotPathFollow.h" + +class CTFBotPayloadGuard : 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 "PayloadGuard"; }; + +private: + PathFollower m_path; + CountdownTimer m_repathTimer; + + Vector m_vantagePoint; + CountdownTimer m_vantagePointTimer; + Vector FindVantagePoint( CTFBot *me, CBaseEntity *cart ); + + CountdownTimer m_moveToBlockTimer; + +}; + +#endif // TF_BOT_PAYLOAD_GUARD_H diff --git a/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_push.cpp b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_push.cpp new file mode 100644 index 0000000..79fe667 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_push.cpp @@ -0,0 +1,155 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_payload_push.cpp +// Push the cartTrigger to the goal +// Michael Booth, April 2010 + +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_gamerules.h" +#include "team_control_point_master.h" +#include "team_train_watcher.h" +#include "trigger_area_capture.h" +#include "bot/tf_bot.h" +#include "bot/behavior/scenario/payload/tf_bot_payload_push.h" +#include "bot/behavior/medic/tf_bot_medic_heal.h" +#include "bot/behavior/engineer/tf_bot_engineer_build.h" + + +extern ConVar tf_bot_path_lookahead_range; +ConVar tf_bot_cart_push_radius( "tf_bot_cart_push_radius", "60", FCVAR_CHEAT ); + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotPayloadPush::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() ); + m_path.Invalidate(); + + m_hideAngle = 180.0f; + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotPayloadPush::Update( CTFBot *me, float interval ) +{ + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( threat && threat->IsVisibleRecently() ) + { + // prepare to fight + me->EquipBestWeaponForThreat( threat ); + } + + if ( TFGameRules()->InSetup() ) + { + // wait until the gates open, then path + m_path.Invalidate(); + m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + return Continue(); + } + + CTeamTrainWatcher *trainWatcher = TFGameRules()->GetPayloadToPush( me->GetTeamNumber() ); + if ( !trainWatcher ) + { + return Continue(); + } + + CBaseEntity *cart = trainWatcher->GetTrainEntity(); + if ( !cart ) + { + return Continue(); + } + + + // move toward the point, periodically repathing to account for changing situation + if ( m_repathTimer.IsElapsed() ) + { + VPROF_BUDGET( "CTFBotPayloadPush::Update( repath )", "NextBot" ); + + Vector cartForward; + cart->GetVectors( &cartForward, NULL, NULL ); + + // default push position is behind cart + Vector pushPos = cart->WorldSpaceCenter() - cartForward * tf_bot_cart_push_radius.GetFloat(); + + // try to hide from enemies on other side of cart + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( threat ) + { + Vector enemyToCart = cart->WorldSpaceCenter() - threat->GetLastKnownPosition(); + enemyToCart.z = 0.0f; + enemyToCart.NormalizeInPlace(); + + pushPos = cart->WorldSpaceCenter() + tf_bot_cart_push_radius.GetFloat() * enemyToCart; + } + + CTFBotPathCost cost( me, DEFAULT_ROUTE ); + m_path.Compute( me, pushPos, cost ); + + m_repathTimer.Start( RandomFloat( 0.2f, 0.4f ) ); + } + + // push the cartTrigger + m_path.Update( me ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotPayloadPush::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + VPROF_BUDGET( "CTFBotPayloadPush::OnResume", "NextBot" ); + + m_repathTimer.Invalidate(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadPush::OnStuck( CTFBot *me ) +{ + VPROF_BUDGET( "CTFBotPayloadPush::OnStuck", "NextBot" ); + + m_repathTimer.Invalidate(); + me->GetLocomotionInterface()->ClearStuckStatus(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadPush::OnMoveToSuccess( CTFBot *me, const Path *path ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotPayloadPush::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ) +{ + VPROF_BUDGET( "CTFBotPayloadPush::OnMoveToFailure", "NextBot" ); + + m_repathTimer.Invalidate(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotPayloadPush::ShouldRetreat( const INextBot *bot ) const +{ + return ANSWER_UNDEFINED; +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotPayloadPush::ShouldHurry( const INextBot *bot ) const +{ + return ANSWER_UNDEFINED; +} + diff --git a/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_push.h b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_push.h new file mode 100644 index 0000000..4499c25 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/payload/tf_bot_payload_push.h @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_payload_push.h +// Push the cart to the goal +// Michael Booth, April 2010 + +#ifndef TF_BOT_PAYLOAD_PUSH_H +#define TF_BOT_PAYLOAD_PUSH_H + +#include "Path/NextBotPathFollow.h" + +class CTFBotPayloadPush : 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 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 "PayloadPush"; }; + +private: + PathFollower m_path; + CountdownTimer m_repathTimer; + float m_hideAngle; +}; + +#endif // TF_BOT_PAYLOAD_PUSH_H diff --git a/game/server/tf/bot/behavior/scenario/raid/tf_bot_companion.cpp b/game/server/tf/bot/behavior/scenario/raid/tf_bot_companion.cpp new file mode 100644 index 0000000..1d78ff3 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/raid/tf_bot_companion.cpp @@ -0,0 +1,217 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_raid_companion.cpp +// Teammate bots for Raid mode +// Michael Booth, October 2009 + +#include "cbase.h" + +#ifdef TF_RAID_MODE + +#include "team.h" +#include "bot/tf_bot.h" +#include "team_control_point_master.h" +#include "bot/behavior/medic/tf_bot_medic_heal.h" +#include "bot/behavior/scenario/raid/tf_bot_companion.h" +#include "bot/behavior/tf_bot_attack.h" +#include "bot/behavior/tf_bot_move_to_vantage_point.h" +#include "bot/behavior/engineer/tf_bot_engineer_build.h" +#include "bot/behavior/sniper/tf_bot_sniper_lurk.h" + +#include "bot/map_entities/tf_bot_generator.h" // action point + +ConVar tf_raid_companion_follow_range( "tf_raid_companion_follow_range", "150", FCVAR_CHEAT ); +ConVar tf_raid_companion_allow_bot_leader( "tf_raid_companion_allow_bot_leader", "0", FCVAR_CHEAT ); + +extern ConVar tf_bot_path_lookahead_range; + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotCompanion::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +CTFPlayer *CTFBotCompanion::GetLeader( void ) +{ + CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE ); + CTFPlayer *leader = NULL; + float leaderSpeed = FLT_MAX; + + for( int i=0; i<raidingTeam->GetNumPlayers(); ++i ) + { + CTFPlayer *player = (CTFPlayer *)raidingTeam->GetPlayer(i); + + if ( player->IsBot() && !tf_raid_companion_allow_bot_leader.GetBool() ) + continue; + +/* + if ( player->IsPlayerClass( TF_CLASS_ENGINEER ) || + player->IsPlayerClass( TF_CLASS_SNIPER ) || + player->IsPlayerClass( TF_CLASS_MEDIC ) ) + continue; +*/ + + if ( player->IsAlive() ) + { + float speed = player->GetPlayerClass()->GetMaxSpeed(); + + if ( speed < leaderSpeed ) + { + leader = player; + leaderSpeed = speed; + } + } + } + + return leader; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotCompanion::Update( CTFBot *me, float interval ) +{ + if ( me->IsPlayerClass( TF_CLASS_MEDIC ) ) + { + const CKnownEntity *patient = me->GetVisionInterface()->GetClosestKnown( me->GetTeamNumber() ); + if ( patient ) + { + return SuspendFor( new CTFBotMedicHeal ); + } + } + + CTFPlayer *leader = GetLeader(); + if ( !leader ) + return Continue(); + + CTFBotPathCost cost( me, FASTEST_ROUTE ); + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + + if ( me->IsSelf( leader ) ) + { + const float engageRange = 500.0f; + if ( threat && threat->IsVisibleRecently() && me->IsRangeLessThan( threat->GetEntity(), engageRange ) ) + { + // stop pushing ahead and kill nearby threats + return SuspendFor( new CTFBotAttack, "Attacking nearby threats" ); + } + + // head toward next capture point + CTeamControlPoint *point = me->GetMyControlPoint(); + if ( point ) + { + m_path.Update( me, point, cost ); + } + } + else + { + if ( ( !threat || threat->GetTimeSinceLastSeen() > 3.0f ) && leader->GetTimeSinceLastInjury() < 1.0f ) + { + // we don't see anything, but the leader is under attack - find a better vantage point + const float nearRange = 1000.0f; + return SuspendFor( new CTFBotMoveToVantagePoint( nearRange ), "Moving to where I can see the enemy" ); + } + + if ( leader && me->IsDistanceBetweenGreaterThan( leader, tf_raid_companion_follow_range.GetFloat() ) ) + { + m_path.Update( me, leader, cost ); + } + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotCompanion::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + m_path.Invalidate(); + return Continue(); +} + + + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotGuardian::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotGuardian::Update( CTFBot *me, float interval ) +{ + if ( me->GetActionPoint() ) + { + const float atHomeRange = 35.0f; // 25.0f; + const Vector &home = me->GetActionPoint()->GetAbsOrigin(); + + if ( me->IsRangeGreaterThan( home, atHomeRange ) ) + { + if ( m_repathTimer.IsElapsed() && !m_path.IsValid() ) + { + m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) ); + + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_path.Compute( me, home, cost ); + } + + // move home + m_path.Update( me ); + + return Continue(); + } + } + + // at home + m_path.Invalidate(); + me->SetHomeArea( me->GetLastKnownArea() ); + + if ( me->IsPlayerClass( TF_CLASS_ENGINEER ) ) + { + return SuspendFor( new CTFBotEngineerBuild ); + } + + if ( me->IsPlayerClass( TF_CLASS_SNIPER ) ) + { + return SuspendFor( new CTFBotSniperLurk ); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotGuardian::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + m_path.Invalidate(); + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotGuardian::OnStuck( CTFBot *me ) +{ + m_path.Invalidate(); + return TryContinue( RESULT_IMPORTANT ); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotGuardian::OnMoveToSuccess( CTFBot *me, const Path *path ) +{ + m_path.Invalidate(); + return TryContinue( RESULT_IMPORTANT ); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotGuardian::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ) +{ + m_path.Invalidate(); + return TryContinue( RESULT_IMPORTANT ); +} + +#endif // TF_RAID_MODE diff --git a/game/server/tf/bot/behavior/scenario/raid/tf_bot_companion.h b/game/server/tf/bot/behavior/scenario/raid/tf_bot_companion.h new file mode 100644 index 0000000..e379b0b --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/raid/tf_bot_companion.h @@ -0,0 +1,57 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_companion.h +// Teammate bots for Raid mode +// Michael Booth, October 2009 + +#ifndef TF_BOT_COMPANION_H +#define TF_BOT_COMPANION_H + +#ifdef TF_RAID_MODE + +#include "Path/NextBotPathFollow.h" +#include "Path/NextBotChasePath.h" + +// +// Friendly teammate bots +// +class CTFBotCompanion : 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 const char *GetName( void ) const { return "Companion"; }; + +private: + ChasePath m_path; + CTFPlayer *GetLeader( void ); +}; + + +// +// Friendly defenders of the base +// +class CTFBotGuardian : 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 const char *GetName( void ) const { return "Guardian"; }; + +private: + PathFollower m_path; + CountdownTimer m_repathTimer; +}; + +#endif // TF_RAID_MODE + +#endif // TF_BOT_COMPANION_H diff --git a/game/server/tf/bot/behavior/scenario/raid/tf_bot_guard_area.cpp b/game/server/tf/bot/behavior/scenario/raid/tf_bot_guard_area.cpp new file mode 100644 index 0000000..32759d9 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/raid/tf_bot_guard_area.cpp @@ -0,0 +1,261 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_guard_area.cpp +// Defend an area against intruders +// Michael Booth, October 2009 + +#include "cbase.h" + +#ifdef TF_RAID_MODE + +#include "tf_player.h" +#include "bot/tf_bot.h" +#include "team_control_point_master.h" +#include "econ_entity_creation.h" +#include "bot/behavior/tf_bot_retreat_to_cover.h" +#include "bot/behavior/sniper/tf_bot_sniper_attack.h" +#include "bot/behavior/engineer/tf_bot_engineer_build.h" +#include "bot/behavior/medic/tf_bot_medic_heal.h" +#include "bot/behavior/scenario/raid/tf_bot_wander.h" +#include "bot/behavior/scenario/raid/tf_bot_guard_area.h" +#include "bot/behavior/tf_bot_attack.h" +#include "bot/behavior/demoman/tf_bot_prepare_stickybomb_trap.h" + +#include "nav_mesh.h" + +extern ConVar tf_bot_path_lookahead_range; +ConVar tf_bot_guard_aggro_range( "tf_bot_guard_aggro_range", "750", FCVAR_CHEAT ); +//ConVar tf_bot_guard_give_up_range( "tf_bot_guard_give_up_range", "1250", FCVAR_CHEAT ); + +ConVar tf_raid_special_vocalize_min_interval( "tf_raid_special_vocalize_min_interval", "10", FCVAR_CHEAT ); +ConVar tf_raid_special_vocalize_max_interval( "tf_raid_special_vocalize_max_interval", "15", FCVAR_CHEAT ); + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotGuardArea::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_chasePath.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() ); + +/* + // give this guy a hat! + randomitemcriteria_t criteria; + criteria.iItemLevel = AE_USE_SCRIPT_VALUE; + criteria.iItemQuality = AE_USE_SCRIPT_VALUE; + criteria.vecAbsOrigin = me->GetAbsOrigin(); + criteria.vecAbsAngles = vec3_angle; + + switch( me->GetPlayerClass()->GetClassIndex() ) + { + case TF_CLASS_SCOUT: criteria.pszItemName = "Scout Hat 1"; break; + case TF_CLASS_SNIPER: criteria.pszItemName = "Sniper Hat 1"; break; + case TF_CLASS_SOLDIER: criteria.pszItemName = "Soldier Pot Hat"; break; + case TF_CLASS_DEMOMAN: criteria.pszItemName = "Demo Top Hat"; break; + case TF_CLASS_MEDIC: criteria.pszItemName = "Medic Hat 1"; break; + case TF_CLASS_HEAVYWEAPONS: criteria.pszItemName = "Heavy Ushanka Hat"; break; + case TF_CLASS_PYRO: criteria.pszItemName = "Pyro Chicken Hat"; break; + case TF_CLASS_SPY: criteria.pszItemName = "Spy Derby Hat"; break; + case TF_CLASS_ENGINEER: criteria.pszItemName = "Engineer Hat 1"; break; + default: criteria.pszItemName = ""; break; + } + + CBaseEntity *hat = ItemGeneration()->GenerateRandomItem( &criteria ); + if ( hat ) + { + // Fake global id + static int s_nFakeID = 1; + static_cast< CEconEntity * >( hat )->GetAttributeContainer()->GetItem()->SetItemID( s_nFakeID++ ); + + DispatchSpawn( hat ); + static_cast< CEconEntity * >( hat )->GetAttributeContainer()->GetItem()->GenerateAttributes(); + static_cast< CEconEntity * >( hat )->GiveTo( me ); + } + else + { + Msg( "Failed to create hat\n" ); + } +*/ + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +class CFindVantagePoint : public ISearchSurroundingAreasFunctor +{ +public: + CFindVantagePoint( void ) + { + m_vantageArea = NULL; + } + + virtual bool operator() ( CNavArea *baseArea, CNavArea *priorArea, float travelDistanceSoFar ) + { + if ( travelDistanceSoFar > 2000.0f ) + return false; + + CTFNavArea *area = (CTFNavArea *)baseArea; + + CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE ); + for( int i=0; i<raidingTeam->GetNumPlayers(); ++i ) + { + CTFPlayer *player = (CTFPlayer *)raidingTeam->GetPlayer(i); + + if ( !player->IsAlive() || !player->GetLastKnownArea() ) + continue; + + CTFNavArea *playerArea = (CTFNavArea *)player->GetLastKnownArea(); + if ( playerArea->IsCompletelyVisible( area ) ) + { + // nearby area from which we can see the enemy team + m_vantageArea = area; + return false; + } + } + + return true; + } + + CTFNavArea *m_vantageArea; +}; + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotGuardArea::Update( CTFBot *me, float interval ) +{ + // emit vocalizations to warn players we're in the area + if ( m_vocalizeTimer.IsElapsed() ) + { + m_vocalizeTimer.Start( RandomFloat( tf_raid_special_vocalize_min_interval.GetFloat(), tf_raid_special_vocalize_max_interval.GetFloat() ) ); + me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_JEERS ); + } + + if ( me->IsPlayerClass( TF_CLASS_MEDIC ) ) + { + return SuspendFor( new CTFBotMedicHeal ); + } + + if ( me->IsPlayerClass( TF_CLASS_ENGINEER ) ) + { + return SuspendFor( new CTFBotEngineerBuild ); + } + + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( threat && threat->IsVisibleRecently() ) + { + m_pathToVantageArea.Invalidate(); + + CTFNavArea *myArea = (CTFNavArea *)me->GetLastKnownArea(); + CTFNavArea *threatArea = (CTFNavArea *)threat->GetLastKnownArea(); + if ( myArea && threatArea ) + { + if ( threatArea->GetIncursionDistance( TF_TEAM_BLUE ) < myArea->GetIncursionDistance( TF_TEAM_BLUE ) ) + { + if ( me->IsRangeGreaterThan( threat->GetLastKnownPosition(), tf_bot_guard_aggro_range.GetFloat() ) ) + { + // threat is far off and hasn't reached us yet - hide until they are closer + return SuspendFor( new CTFBotRetreatToCover, "Hiding until threat gets closer" ); + } + } + } + + // attack! + return SuspendFor( new CTFBotAttack, "Attacking nearby threat" ); + } + else + { + // no enemy is visible + Vector moveTo = me->GetAbsOrigin(); + + // if point is being captured, move to it + CTeamControlPoint *point = me->GetMyControlPoint(); + if ( point && point->LastContestedAt() > 0.0f && ( gpGlobals->curtime - point->LastContestedAt() ) < 5.0f ) + { + // the point is, or was very recently, contested - defend it! + moveTo = point->GetAbsOrigin(); + } + else if ( me->GetHomeArea() ) + { + // no enemy is visible - return to our home position + moveTo = me->GetHomeArea()->GetCenter(); + } + + if ( !m_pathToPoint.IsValid() || m_repathTimer.IsElapsed() ) + { + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_pathToPoint.Compute( me, moveTo, cost ); + m_repathTimer.Start( RandomFloat( 2.0f, 3.0f ) ); + } + + if ( ( me->GetAbsOrigin() - moveTo ).IsLengthGreaterThan( 25.0f ) ) + { + m_pathToPoint.Update( me ); + } + + if ( me->GetHomeArea() == me->GetLastKnownArea() ) + { + // at home + if ( CTFBotPrepareStickybombTrap::IsPossible( me ) ) + { + return SuspendFor( new CTFBotPrepareStickybombTrap, "Laying sticky bombs!" ); + } + } + +/* + // no enemy is visible - move to where we can see them + if ( !m_pathToVantageArea.IsValid() ) + { + CTFNavArea *vantageArea = me->FindVantagePoint(); + if ( vantageArea ) + { + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_pathToVantageArea.Compute( me, vantageArea->GetCenter(), cost ); + } + } + + m_pathToVantageArea.Update( me ); +*/ + } + + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotGuardArea::OnStuck( CTFBot *me ) +{ + m_chasePath.Invalidate(); + m_pathToPoint.Invalidate(); + m_pathToVantageArea.Invalidate(); + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotGuardArea::OnMoveToSuccess( CTFBot *me, const Path *path ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotGuardArea::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotGuardArea::ShouldRetreat( const INextBot *me ) const +{ + return ANSWER_NO; +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotGuardArea::OnCommandApproach( CTFBot *me, const Vector &pos, float range ) +{ + return TryContinue(); +} + +#endif // TF_RAID_MODE diff --git a/game/server/tf/bot/behavior/scenario/raid/tf_bot_guard_area.h b/game/server/tf/bot/behavior/scenario/raid/tf_bot_guard_area.h new file mode 100644 index 0000000..891dda7 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/raid/tf_bot_guard_area.h @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_guard_area.h +// Defend an area against intruders +// Michael Booth, October 2009 + +#ifdef TF_RAID_MODE + +#ifndef TF_BOT_GUARD_AREA_H +#define TF_BOT_GUARD_AREA_H + +#include "Path/NextBotChasePath.h" + +class CTFBotGuardArea : public Action< CTFBot > +{ +public: + virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction ); + virtual ActionResult< CTFBot > Update( CTFBot *me, float interval ); + + virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me ); + virtual EventDesiredResult< CTFBot > OnMoveToSuccess( CTFBot *me, const Path *path ); + virtual EventDesiredResult< CTFBot > OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ); + + virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat? + + virtual EventDesiredResult< CTFBot > OnCommandApproach( CTFBot *me, const Vector &pos, float range ); + + virtual const char *GetName( void ) const { return "GuardArea"; }; + +private: + ChasePath m_chasePath; + PathFollower m_pathToPoint; + PathFollower m_pathToVantageArea; + CountdownTimer m_vocalizeTimer; + CountdownTimer m_repathTimer; +}; + +#endif // TF_RAID_MODE + +#endif // TF_BOT_GUARD_AREA_H diff --git a/game/server/tf/bot/behavior/scenario/raid/tf_bot_mob_rush.cpp b/game/server/tf/bot/behavior/scenario/raid/tf_bot_mob_rush.cpp new file mode 100644 index 0000000..f80117d --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/raid/tf_bot_mob_rush.cpp @@ -0,0 +1,164 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_mob_rush.cpp +// A member of a rushing mob of melee attackers +// Michael Booth, October 2009 + +#include "cbase.h" + +#ifdef TF_RAID_MODE + +#include "team.h" +#include "bot/tf_bot.h" +#include "bot/behavior/tf_bot_taunt.h" +#include "bot/behavior/scenario/raid/tf_bot_mob_rush.h" + + +ConVar tf_bot_taunt_range( "tf_bot_taunt_range", "100", FCVAR_CHEAT ); +ConVar tf_raid_mob_rush_vocalize_min_interval( "tf_raid_mob_rush_vocalize_min_interval", "5", FCVAR_CHEAT ); +ConVar tf_raid_mob_rush_vocalize_max_interval( "tf_raid_mob_rush_vocalize_max_interval", "8", FCVAR_CHEAT ); +ConVar tf_raid_mob_avoid_range( "tf_raid_mob_avoid_range", "100", FCVAR_CHEAT ); + + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +CTFBotMobRush::CTFBotMobRush( CTFPlayer *victim, float reactionTime ) +{ + m_victim = victim; + + // this isn't strictly correct - we shouldn't start the timer until OnStart + m_reactionTimer.Start( reactionTime ); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMobRush::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_vocalizeTimer.Start( RandomFloat( tf_raid_mob_rush_vocalize_min_interval.GetFloat(), tf_raid_mob_rush_vocalize_max_interval.GetFloat() ) ); + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMobRush::Update( CTFBot *me, float interval ) +{ + // mobs use only their melee weapons + CBaseCombatWeapon *meleeWeapon = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE ); + if ( meleeWeapon ) + { + me->Weapon_Switch( meleeWeapon ); + } + + + if ( m_victim == NULL ) + { + return Done( "No victim" ); + } + + me->GetBodyInterface()->AimHeadTowards( m_victim, IBody::CRITICAL, 1.0f, NULL, "Looking at our melee target" ); + + if ( m_reactionTimer.HasStarted() ) + { + if ( m_reactionTimer.IsElapsed() ) + { + // snap out of it! + me->DoAnimationEvent( PLAYERANIMEVENT_VOICE_COMMAND_GESTURE, ACT_MP_GESTURE_VC_FINGERPOINT_PRIMARY ); + me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY ); + m_reactionTimer.Invalidate(); + } + else + { + // wait for reaction time to elapse + return Continue(); + } + } + + if ( me->IsPlayingGesture( ACT_MP_GESTURE_VC_FINGERPOINT_PRIMARY ) ) + { + // wait for "wake up" anim to finish + return Continue(); + } + + // just keep swinging + me->PressFireButton(); + + // chase them down + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_path.Update( me, m_victim, cost ); + + // avoid friends + CTeam *team = GetGlobalTeam( TF_TEAM_RED ); + for( int t=0; t<team->GetNumPlayers(); ++t ) + { + CTFPlayer *teamMember = (CTFPlayer *)team->GetPlayer(t); + + if ( !teamMember->IsAlive() ) + continue; + + Vector toBuddy = teamMember->GetAbsOrigin() - me->GetAbsOrigin(); + if ( toBuddy.IsLengthLessThan( tf_raid_mob_avoid_range.GetFloat() ) ) + { + float range = toBuddy.NormalizeInPlace(); + + me->GetLocomotionInterface()->Approach( me->GetAbsOrigin() - 100.0f * toBuddy, 1.0f - ( range / tf_raid_mob_avoid_range.GetFloat() ) ); + } + } + + + if ( !m_victim->IsAlive() && me->IsRangeLessThan( m_victim, tf_bot_taunt_range.GetFloat() ) ) + { + // we got 'em! + return ChangeTo( new CTFBotTaunt, "Taunt their corpse" ); + } + + if ( m_vocalizeTimer.IsElapsed() ) + { + m_vocalizeTimer.Start( RandomFloat( tf_raid_mob_rush_vocalize_min_interval.GetFloat(), tf_raid_mob_rush_vocalize_max_interval.GetFloat() ) ); + + if ( me->IsPlayerClass( TF_CLASS_SCOUT ) ) + me->EmitSound( "Scout.MobJabber" ); + else if ( me->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) + me->EmitSound( "Heavy.MobJabber" ); + else + me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY ); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotMobRush::OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result ) +{ + return TryToSustain( RESULT_CRITICAL, "Ignoring contact" ); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotMobRush::OnInjured( CTFBot *me, const CTakeDamageInfo &info ) +{ + return TryToSustain( RESULT_CRITICAL, "Ignoring injury" ); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotMobRush::OnOtherKilled( CTFBot *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info ) +{ + return TryToSustain( RESULT_CRITICAL, "Ignoring friend death" ); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotMobRush::OnStuck( CTFBot *me ) +{ + m_path.Invalidate(); + return TryToSustain( RESULT_CRITICAL ); +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotMobRush::ShouldRetreat( const INextBot *me ) const +{ + return ANSWER_NO; +} + +#endif // TF_RAID_MODE diff --git a/game/server/tf/bot/behavior/scenario/raid/tf_bot_mob_rush.h b/game/server/tf/bot/behavior/scenario/raid/tf_bot_mob_rush.h new file mode 100644 index 0000000..0331acc --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/raid/tf_bot_mob_rush.h @@ -0,0 +1,42 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_mob_rush.h +// A member of a rushing mob of melee attackers +// Michael Booth, October 2009 + +#ifndef TF_BOT_MOB_RUSH_H +#define TF_BOT_MOB_RUSH_H + +#ifdef TF_RAID_MODE + +#include "Path/NextBotChasePath.h" + + +//----------------------------------------------------------------------------- +class CTFBotMobRush : public Action< CTFBot > +{ +public: + CTFBotMobRush( CTFPlayer *victim, float reactionTime = 0.0f ); + + virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction ); + virtual ActionResult< CTFBot > Update( CTFBot *me, float interval ); + + virtual EventDesiredResult< CTFBot > OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result = NULL ); + virtual EventDesiredResult< CTFBot > OnInjured( CTFBot *me, const CTakeDamageInfo &info ); + virtual EventDesiredResult< CTFBot > OnOtherKilled( CTFBot *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info ); + virtual EventDesiredResult< CTFBot > OnStuck( CTFBot *me ); + + QueryResultType ShouldRetreat( const INextBot *me ) const; + + virtual const char *GetName( void ) const { return "MobRush"; }; + +private: + CHandle< CTFPlayer > m_victim; + CountdownTimer m_reactionTimer; + CountdownTimer m_tauntTimer; + CountdownTimer m_vocalizeTimer; + ChasePath m_path; +}; + +#endif // TF_RAID_MODE + +#endif // TF_BOT_MOB_RUSH_H diff --git a/game/server/tf/bot/behavior/scenario/raid/tf_bot_squad_attack.cpp b/game/server/tf/bot/behavior/scenario/raid/tf_bot_squad_attack.cpp new file mode 100644 index 0000000..95d4362 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/raid/tf_bot_squad_attack.cpp @@ -0,0 +1,150 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_squad_attack.cpp +// Move and attack as a small, cohesive, group +// Michael Booth, October 2009 + +#include "cbase.h" + +#ifdef TF_RAID_MODE + +#include "team.h" +#include "raid/tf_raid_logic.h" +#include "bot/tf_bot.h" +#include "bot/behavior/scenario/raid/tf_bot_wander.h" +#include "bot/behavior/scenario/raid/tf_bot_squad_attack.h" +#include "bot/behavior/medic/tf_bot_medic_heal.h" +#include "bot/behavior/tf_bot_move_to_vantage_point.h" + + +ConVar tf_squad_radius( "tf_squad_radius", "200", FCVAR_CHEAT ); +ConVar tf_squad_debug( "tf_squad_debug", "0", FCVAR_CHEAT ); +ConVar tf_raid_squad_vocalize_min_interval( "tf_raid_squad_vocalize_min_interval", "5", FCVAR_CHEAT ); +ConVar tf_raid_squad_vocalize_max_interval( "tf_raid_squad_vocalize_max_interval", "8", FCVAR_CHEAT ); + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotSquadAttack::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_vocalizeTimer.Start( RandomFloat( tf_raid_squad_vocalize_min_interval.GetFloat(), tf_raid_squad_vocalize_max_interval.GetFloat() ) ); + m_victim = NULL; + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +// the leader is the slowest member of the squad +CTFBot *CTFBotSquadAttack::GetSquadLeader( CTFBot *me ) const +{ + CTFBot *leader = NULL; + float leaderSpeed = FLT_MAX; + + CTFBotSquad *squad = me->GetSquad(); + CTFBotSquad::Iterator it; + for( it = squad->GetFirstMember(); it != squad->InvalidIterator(); it = squad->GetNextMember( it ) ) + { + CTFBot *bot = it(); + + float speed = bot->GetPlayerClass()->GetMaxSpeed(); + + if ( speed < leaderSpeed ) + { + leader = bot; + leaderSpeed = speed; + } + } + + return leader; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotSquadAttack::Update( CTFBot *me, float interval ) +{ + if ( !me->IsInASquad() ) + return Done( "Not in a squad" ); + + if ( me->IsPlayerClass( TF_CLASS_MEDIC ) ) + { + return SuspendFor( new CTFBotMedicHeal ); + } + + CTFBot *leader = GetSquadLeader( me ); + CTFBotPathCost cost( me, FASTEST_ROUTE ); + + if ( m_victim == NULL || m_victimConsiderTimer.IsElapsed() ) + { + m_victimConsiderTimer.Start( 3.0f ); + + m_victim = TFGameRules()->GetRaidLogic()->SelectRaiderToAttack(); + } + + if ( m_victim ) + { + const float engageRange = 500.0f; + if ( me->IsPlayerClass( TF_CLASS_PYRO ) || + me->IsRangeGreaterThan( m_victim->GetAbsOrigin(), engageRange ) || + !me->GetVisionInterface()->IsAbleToSee( m_victim, IVision::DISREGARD_FOV ) ) + { + if ( me->IsSelf( leader ) || me->IsRangeLessThan( leader, tf_squad_radius.GetFloat() ) ) + { + // chase down the enemy + m_chasePath.Update( me, m_victim, cost ); + } + } + + if ( !me->IsSelf( leader ) && me->IsRangeGreaterThan( leader, 1.25f * tf_squad_radius.GetFloat() ) ) + { + // too far from leader - return to him + m_chasePath.Update( me, leader, cost ); + } + + if ( tf_squad_debug.GetBool() && me->IsSelf( leader ) ) + { + NDebugOverlay::Circle( me->GetAbsOrigin(), 20.0f, 255, 255, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); + + CTFBotSquad *squad = me->GetSquad(); + CTFBotSquad::Iterator it; + for( it = squad->GetFirstMember(); it != squad->InvalidIterator(); it = squad->GetNextMember( it ) ) + { + CTFBot *bot = it(); + + if ( me->IsSelf( bot ) ) + continue; + + NDebugOverlay::Line( me->WorldSpaceCenter(), bot->WorldSpaceCenter(), 0, 255, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER ); + } + } + } + + if ( m_vocalizeTimer.IsElapsed() ) + { + m_vocalizeTimer.Start( RandomFloat( tf_raid_squad_vocalize_min_interval.GetFloat(), tf_raid_squad_vocalize_max_interval.GetFloat() ) ); + + if ( me->IsPlayerClass( TF_CLASS_SCOUT ) ) + me->EmitSound( "Scout.MobJabber" ); + else if ( me->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) + me->EmitSound( "Heavy.MobJabber" ); + else + me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_BATTLECRY ); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotSquadAttack::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + m_path.Invalidate(); + return Continue(); +} + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotSquadAttack::OnStuck( CTFBot *me ) +{ + m_path.Invalidate(); + return TryContinue(); +} + +#endif // TF_RAID_MODE
\ No newline at end of file diff --git a/game/server/tf/bot/behavior/scenario/raid/tf_bot_squad_attack.h b/game/server/tf/bot/behavior/scenario/raid/tf_bot_squad_attack.h new file mode 100644 index 0000000..a685c65 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/raid/tf_bot_squad_attack.h @@ -0,0 +1,47 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_squad_attack.h +// Move and attack as a small, cohesive, group +// Michael Booth, October 2009 + +#ifndef TF_BOT_SQUAD_ATTACK_H +#define TF_BOT_SQUAD_ATTACK_H + +#ifdef TF_RAID_MODE + +#include "Path/NextBotPathFollow.h" +#include "Path/NextBotChasePath.h" + + +//----------------------------------------------------------------------------- +class CTFBotSquadAttack : 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 ); + + QueryResultType ShouldRetreat( const INextBot *me ) const; + + virtual const char *GetName( void ) const { return "SquadPatrol"; }; + +private: + CountdownTimer m_vocalizeTimer; + PathFollower m_path; + ChasePath m_chasePath; + CHandle< CTFPlayer > m_victim; + CountdownTimer m_victimConsiderTimer; + + CTFBot *GetSquadLeader( CTFBot *me ) const; +}; + +inline QueryResultType CTFBotSquadAttack::ShouldRetreat( const INextBot *me ) const +{ + return ANSWER_NO; +} + +#endif // TF_RAID_MODE + +#endif // TF_BOT_SQUAD_ATTACK_H diff --git a/game/server/tf/bot/behavior/scenario/raid/tf_bot_wander.cpp b/game/server/tf/bot/behavior/scenario/raid/tf_bot_wander.cpp new file mode 100644 index 0000000..ffd08f4 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/raid/tf_bot_wander.cpp @@ -0,0 +1,175 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_wander.cpp +// Wanderering/idle enemies for Squad Co-op mode +// Michael Booth, October 2009 + +#include "cbase.h" + +#ifdef TF_RAID_MODE + +#include "team.h" +#include "raid/tf_raid_logic.h" +#include "bot/tf_bot.h" +#include "bot/behavior/scenario/raid/tf_bot_wander.h" +#include "bot/behavior/scenario/raid/tf_bot_mob_rush.h" + + +ConVar tf_raid_wanderer_aggro_range( "tf_raid_wanderer_aggro_range", "500", FCVAR_CHEAT, "If wanderers see a threat closer than this, they attack" ); +ConVar tf_raid_wanderer_notice_friend_death_range( "tf_raid_wanderer_notice_friend_death_range", "1000", FCVAR_CHEAT, "If a friend dies within this radius of a wanderer, it wakes up and attacks the attacker" ); +ConVar tf_raid_wanderer_reaction_factor( "tf_raid_wanderer_reaction_factor", "1", FCVAR_CHEAT ); +ConVar tf_raid_wanderer_vocalize_min_interval( "tf_raid_wanderer_vocalize_min_interval", "20", FCVAR_CHEAT ); +ConVar tf_raid_wanderer_vocalize_max_interval( "tf_raid_wanderer_vocalize_max_interval", "30", FCVAR_CHEAT ); + + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +CTFBotWander::CTFBotWander( void ) +{ +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotWander::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_vocalizeTimer.Start( RandomFloat( tf_raid_wanderer_vocalize_min_interval.GetFloat(), tf_raid_wanderer_vocalize_max_interval.GetFloat() ) ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotWander::Update( CTFBot *me, float interval ) +{ + // mobs use only their melee weapons + CBaseCombatWeapon *meleeWeapon = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE ); + if ( meleeWeapon ) + { + me->Weapon_Switch( meleeWeapon ); + } + + + CTeam *raidingTeam = GetGlobalTeam( TF_TEAM_BLUE ); + + if ( me->HasAttribute( CTFBot::AGGRESSIVE ) ) + { + // I'm a mob rusher - pick a random raider and attack them! + CTFPlayer *victim = TFGameRules()->GetRaidLogic()->SelectRaiderToAttack(); + if ( victim ) + { + return SuspendFor( new CTFBotMobRush( victim ), "Rushing a raider" ); + } + } + else if ( m_visionTimer.IsElapsed() ) + { + // I'm a wanderer - look for very nearby threats + m_visionTimer.Start( RandomFloat( 0.5f, 1.0f ) ); + + // find closest visible raider within aggro range + CTFPlayer *threat = NULL; + float closeThreatRangeSq = tf_raid_wanderer_aggro_range.GetFloat() * tf_raid_wanderer_aggro_range.GetFloat(); + + for( int i=0; i<raidingTeam->GetNumPlayers(); ++i ) + { + CTFPlayer *player = (CTFPlayer *)raidingTeam->GetPlayer(i); + + if ( !player->IsAlive() ) + continue; + + float rangeSq = me->GetRangeSquaredTo( player ); + if ( rangeSq < closeThreatRangeSq ) + { + if ( me->GetVisionInterface()->IsLineOfSightClearToEntity( player ) ) + { + threat = player; + closeThreatRangeSq = rangeSq; + } + } + } + + if ( threat ) + { + return SuspendFor( new CTFBotMobRush( threat ), "Attacking threat!" ); + } + } + + if ( m_vocalizeTimer.IsElapsed() ) + { + m_vocalizeTimer.Start( RandomFloat( tf_raid_wanderer_vocalize_min_interval.GetFloat(), tf_raid_wanderer_vocalize_max_interval.GetFloat() ) ); + + // mouth off + if ( me->IsPlayerClass( TF_CLASS_SCOUT ) ) + me->EmitSound( "Scout.WanderJabber" ); + else + me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_JEERS ); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotWander::OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result ) +{ + if ( other && other->IsPlayer() && me->IsEnemy( other ) ) + { + return TrySuspendFor( new CTFBotMobRush( (CTFPlayer *)other ), RESULT_IMPORTANT, "Attacking threat who touched me!" ); + } + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotWander::OnInjured( CTFBot *me, const CTakeDamageInfo &info ) +{ + if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() && me->IsEnemy( info.GetAttacker() ) ) + { + return TrySuspendFor( new CTFBotMobRush( (CTFPlayer *)info.GetAttacker() ), RESULT_IMPORTANT, "Attacking threat who attacked me!" ); + } + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotWander::OnOtherKilled( CTFBot *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info ) +{ + if ( victim && me->IsFriend( victim ) ) + { + if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() && me->IsEnemy( info.GetAttacker() ) ) + { + if ( me->IsRangeLessThan( victim, tf_raid_wanderer_notice_friend_death_range.GetFloat() ) ) + { + if ( me->GetVisionInterface()->IsAbleToSee( victim, IVision::DISREGARD_FOV ) && + me->GetVisionInterface()->IsAbleToSee( info.GetAttacker(), IVision::DISREGARD_FOV ) ) + { + float rangeToAttacker = me->GetRangeTo( info.GetAttacker() ); + float reactionTime; + + if ( rangeToAttacker < tf_raid_wanderer_aggro_range.GetFloat() ) + { + reactionTime = 0.0f; + } + else + { + reactionTime = tf_raid_wanderer_reaction_factor.GetFloat() * ( rangeToAttacker - tf_raid_wanderer_aggro_range.GetFloat() ) / tf_raid_wanderer_aggro_range.GetFloat(); + } + + return TrySuspendFor( new CTFBotMobRush( (CTFPlayer *)info.GetAttacker(), reactionTime ), RESULT_IMPORTANT, "Attacking my friend's attacker!" ); + } + } + } + } + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotWander::OnCommandAttack( CTFBot *me, CBaseEntity *victim ) +{ + return TryContinue(); +} + + +#endif // TF_RAID_MODE diff --git a/game/server/tf/bot/behavior/scenario/raid/tf_bot_wander.h b/game/server/tf/bot/behavior/scenario/raid/tf_bot_wander.h new file mode 100644 index 0000000..0364903 --- /dev/null +++ b/game/server/tf/bot/behavior/scenario/raid/tf_bot_wander.h @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_wander.h +// Wanderering/idle enemies for Squad Co-op mode +// Michael Booth, October 2009 + +#ifndef TF_BOT_WANDER_H +#define TF_BOT_WANDER_H + +#ifdef TF_RAID_MODE + +//----------------------------------------------------------------------------- +class CTFBotWander : public Action< CTFBot > +{ +public: + CTFBotWander( void ); + + virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction ); + virtual ActionResult< CTFBot > Update( CTFBot *me, float interval ); + + virtual EventDesiredResult< CTFBot > OnContact( CTFBot *me, CBaseEntity *other, CGameTrace *result = NULL ); + virtual EventDesiredResult< CTFBot > OnInjured( CTFBot *me, const CTakeDamageInfo &info ); + virtual EventDesiredResult< CTFBot > OnOtherKilled( CTFBot *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info ); + + virtual EventDesiredResult< CTFBot > OnCommandAttack( CTFBot *me, CBaseEntity *victim ); + + virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry? + virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat? + + virtual const char *GetName( void ) const { return "Wander"; }; + +private: + CountdownTimer m_visionTimer; + CountdownTimer m_vocalizeTimer; +}; + + +inline QueryResultType CTFBotWander::ShouldHurry( const INextBot *me ) const +{ + return ANSWER_YES; +} + + +inline QueryResultType CTFBotWander::ShouldRetreat( const INextBot *me ) const +{ + return ANSWER_NO; +} + + +#endif TF_RAID_MODE + +#endif // TF_BOT_WANDER_H |