You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
504 lines
15 KiB
504 lines
15 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// tf_bot_engineer_move_to_build.cpp |
|
// Engineer moving into position to build |
|
// Michael Booth, February 2009 |
|
|
|
#include "cbase.h" |
|
#include "nav_mesh/tf_nav_mesh.h" |
|
#include "tf_player.h" |
|
#include "tf_gamerules.h" |
|
#include "tf_obj_sentrygun.h" |
|
#include "tf_weapon_builder.h" |
|
#include "team_train_watcher.h" |
|
#include "bot/tf_bot.h" |
|
#include "bot/behavior/engineer/tf_bot_engineer_build.h" |
|
#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h" |
|
#include "bot/behavior/engineer/tf_bot_engineer_building.h" |
|
#include "bot/map_entities/tf_bot_hint_sentrygun.h" |
|
#include "bot/behavior/tf_bot_get_ammo.h" |
|
#include "bot/behavior/tf_bot_retreat_to_cover.h" |
|
#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h" |
|
#include "trigger_area_capture.h" |
|
|
|
#include "raid/tf_raid_logic.h" |
|
|
|
|
|
extern ConVar tf_bot_path_lookahead_range; |
|
|
|
ConVar tf_bot_debug_sentry_placement( "tf_bot_debug_sentry_placement", "0", FCVAR_CHEAT ); |
|
ConVar tf_bot_max_teleport_exit_travel_to_point( "tf_bot_max_teleport_exit_travel_to_point", "2500", FCVAR_CHEAT, "In an offensive engineer bot's tele exit is farther from the point than this, destroy it" ); |
|
ConVar tf_bot_min_teleport_travel( "tf_bot_min_teleport_travel", "3000", FCVAR_CHEAT, "Minimum travel distance between teleporter entrance and exit before engineer bot will build one" ); |
|
|
|
//-------------------------------------------------------------------------------------------------------- |
|
static Vector s_pointCentroid; |
|
|
|
int CompareRangeToPoint( CTFNavArea * const *area1, CTFNavArea * const *area2 ) |
|
{ |
|
float d1 = ( (*area1)->GetCenter() - s_pointCentroid ).LengthSqr(); |
|
float d2 = ( (*area2)->GetCenter() - s_pointCentroid ).LengthSqr(); |
|
|
|
// reversed so farthest is sorted first in the vector |
|
if ( d1 < d2 ) |
|
return 1; |
|
|
|
if ( d1 > d2 ) |
|
return -1; |
|
|
|
return 0; |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
void CTFBotEngineerMoveToBuild::CollectBuildAreas( CTFBot *me ) |
|
{ |
|
// if we have a predesignated build area, we're done |
|
if ( me->GetHomeArea() ) |
|
return; |
|
|
|
m_sentryAreaVector.RemoveAll(); |
|
|
|
CUtlVector< CTFNavArea * > pointAreaVector; |
|
Vector pointCentroid = vec3_origin; |
|
float pointEnemyIncursion = 0.0f; |
|
int i; |
|
|
|
int myTeam = me->GetTeamNumber(); |
|
int enemyTeam = ( myTeam == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE; |
|
|
|
CCaptureZone *zone = me->GetFlagCaptureZone(); |
|
if ( zone ) |
|
{ |
|
// NOTE: Not strictly the right thing - should defend location of our team's flag |
|
CTFNavArea *zoneArea = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( zone->WorldSpaceCenter(), false, 500.0f, true ); |
|
if ( zoneArea ) |
|
{ |
|
pointAreaVector.AddToTail( zoneArea ); |
|
pointCentroid += zoneArea->GetCenter(); |
|
pointEnemyIncursion += zoneArea->GetIncursionDistance( enemyTeam ); |
|
} |
|
} |
|
else if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT ) |
|
{ |
|
CTeamTrainWatcher *trainWatcher; |
|
|
|
if ( myTeam == TF_TEAM_BLUE ) |
|
{ |
|
trainWatcher = TFGameRules()->GetPayloadToPush( me->GetTeamNumber() ); |
|
} |
|
else |
|
{ |
|
trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() ); |
|
} |
|
|
|
if ( trainWatcher ) |
|
{ |
|
Vector checkpointPos = trainWatcher->GetNextCheckpointPosition(); |
|
|
|
CTFNavArea *checkpointArea = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( checkpointPos, false, 500.0f, true ); |
|
if ( checkpointArea ) |
|
{ |
|
pointAreaVector.AddToTail( checkpointArea ); |
|
pointCentroid += checkpointArea->GetCenter(); |
|
pointEnemyIncursion += checkpointArea->GetIncursionDistance( enemyTeam ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// collect all areas overlapping the point |
|
CTeamControlPoint *ctrlPoint = me->GetMyControlPoint(); |
|
if ( !ctrlPoint ) |
|
return; |
|
|
|
const CUtlVector< CTFNavArea * > *ctrlPointAreaVector = TheTFNavMesh()->GetControlPointAreas( ctrlPoint->GetPointIndex() ); |
|
|
|
if ( ctrlPointAreaVector ) |
|
{ |
|
for( i=0; i<ctrlPointAreaVector->Count(); ++i ) |
|
{ |
|
CTFNavArea *area = ctrlPointAreaVector->Element(i); |
|
|
|
pointAreaVector.AddToTail( area ); |
|
pointCentroid += area->GetCenter(); |
|
pointEnemyIncursion += area->GetIncursionDistance( enemyTeam ); |
|
} |
|
} |
|
} |
|
|
|
if ( pointAreaVector.Count() == 0 ) |
|
return; |
|
|
|
pointCentroid /= pointAreaVector.Count(); |
|
pointEnemyIncursion /= pointAreaVector.Count(); |
|
|
|
|
|
// collect all areas that can see the point |
|
CUtlVector< CTFNavArea * > exposedAreaVector; |
|
for( i=0; i<pointAreaVector.Count(); ++i ) |
|
{ |
|
CTFAreaCollector collect; |
|
pointAreaVector[i]->ForAllPotentiallyVisibleAreas( collect ); |
|
|
|
for( int j=0; j<collect.m_vector.Count(); ++j ) |
|
{ |
|
CTFNavArea *visibleArea = collect.m_vector[j]; |
|
|
|
|
|
if ( visibleArea->GetIncursionDistance( myTeam ) < 0 || visibleArea->GetIncursionDistance( enemyTeam ) < 0 ) |
|
continue; |
|
|
|
if ( TFGameRules()->IsInKothMode() ) |
|
{ |
|
// ignore areas the enemy can reach first |
|
if ( visibleArea->GetIncursionDistance( myTeam ) >= visibleArea->GetIncursionDistance( enemyTeam ) ) |
|
continue; |
|
} |
|
|
|
// incursion flow is badly behaved at cap #1, stage #2 in dustbowl |
|
// else |
|
// { |
|
// if ( pointEnemyIncursion > visibleArea->GetIncursionDistance( enemyTeam ) ) |
|
// continue; |
|
// } |
|
|
|
if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP ) |
|
{ |
|
// don't build directly on the point |
|
if ( visibleArea->HasAttributeTF( TF_NAV_CONTROL_POINT ) ) |
|
continue; |
|
|
|
// ignore areas below the point |
|
const float tooFarBelow = 150.0f; |
|
if ( visibleArea->GetCenter().z < pointCentroid.z - tooFarBelow ) |
|
continue; |
|
|
|
// ignore areas too far from the point for the sentry gun to reach |
|
const float tolerance = 1.1f; |
|
if ( ( visibleArea->GetCenter() - pointCentroid ).IsLengthGreaterThan( SENTRY_MAX_RANGE * tolerance ) ) |
|
continue; |
|
} |
|
|
|
// ignore areas that don't have clear line of FIRE (not sight) |
|
const float sentryEyeHeight = 60.0f; |
|
const float pointFlagHeight = 70.0f; // 100.0f; |
|
if ( !me->IsLineOfFireClear( visibleArea->GetCenter() + Vector( 0, 0, sentryEyeHeight ), pointCentroid + Vector( 0, 0, pointFlagHeight ) ) ) |
|
continue; |
|
|
|
if ( !exposedAreaVector.HasElement( visibleArea ) ) |
|
exposedAreaVector.AddToTail( visibleArea ); |
|
} |
|
} |
|
|
|
// keep the farthest away areas |
|
const float keepRatio = 1.0f; // 0.5f; |
|
s_pointCentroid = pointCentroid; |
|
exposedAreaVector.Sort( CompareRangeToPoint ); |
|
|
|
for( i=0; i<exposedAreaVector.Count() * keepRatio; ++i ) |
|
{ |
|
CTFNavArea *usableArea = exposedAreaVector[i]; |
|
|
|
m_sentryAreaVector.AddToTail( usableArea ); |
|
} |
|
|
|
// calculate total surface area |
|
m_totalSurfaceArea = 0.0f; |
|
FOR_EACH_VEC( m_sentryAreaVector, it ) |
|
{ |
|
CTFNavArea *area = m_sentryAreaVector[ it ]; |
|
|
|
m_totalSurfaceArea += area->GetSizeX() * area->GetSizeY(); |
|
|
|
if ( tf_bot_debug_sentry_placement.GetBool() ) |
|
{ |
|
TheNavMesh->AddToSelectedSet( area ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
/** |
|
* Doesn't recompute the potential areas, just reselected from the list |
|
*/ |
|
void CTFBotEngineerMoveToBuild::SelectBuildLocation( CTFBot *me ) |
|
{ |
|
m_path.Invalidate(); |
|
|
|
m_sentryBuildHint = NULL; |
|
m_sentryBuildLocation = vec3_origin; |
|
|
|
|
|
// if we have a build spot, use it |
|
if ( me->GetHomeArea() ) |
|
{ |
|
m_sentryBuildLocation = me->GetHomeArea()->GetCenter(); |
|
return; |
|
} |
|
|
|
// if we have a set of specific build locations, pick one of them |
|
CUtlVector< CTFBotHintSentrygun * > sentryHintVector; |
|
|
|
CTFBotHintSentrygun *sentryHint; |
|
for( sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( NULL, "bot_hint_sentrygun" ) ); |
|
sentryHint; |
|
sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( sentryHint, "bot_hint_sentrygun" ) ) ) |
|
{ |
|
// clear the previous owner if it is us |
|
if ( sentryHint->GetPlayerOwner() == me ) |
|
{ |
|
sentryHint->SetPlayerOwner( NULL ); |
|
} |
|
if ( sentryHint->IsAvailableForSelection( me ) ) |
|
{ |
|
sentryHintVector.AddToTail( sentryHint ); |
|
} |
|
} |
|
|
|
if ( sentryHintVector.Count() > 0 ) |
|
{ |
|
int which = RandomInt( 0, sentryHintVector.Count()-1 ); |
|
|
|
m_sentryBuildHint = sentryHintVector[ which ]; |
|
m_sentryBuildHint->SetPlayerOwner( me ); |
|
m_sentryBuildLocation = m_sentryBuildHint->GetAbsOrigin(); |
|
|
|
return; |
|
} |
|
|
|
|
|
// collect nav area candidates |
|
CollectBuildAreas( me ); |
|
|
|
// choose based on surface area to avoid biasing finely subdivided areas of the mesh |
|
float which = RandomFloat( 0.0f, m_totalSurfaceArea - 1.0f ); |
|
float soFar = 0.0f; |
|
FOR_EACH_VEC( m_sentryAreaVector, sit ) |
|
{ |
|
CTFNavArea *area = m_sentryAreaVector[ sit ]; |
|
|
|
soFar += area->GetSizeX() * area->GetSizeY(); |
|
|
|
if ( which < soFar ) |
|
{ |
|
m_sentryBuildLocation = area->GetRandomPoint(); |
|
return; |
|
} |
|
} |
|
|
|
if ( !HushAsserts() ) |
|
{ |
|
Assert( !"Failed to find a build location" ); |
|
} |
|
m_sentryBuildLocation = me->GetAbsOrigin(); |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
ActionResult< CTFBot > CTFBotEngineerMoveToBuild::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) |
|
{ |
|
m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() ); |
|
|
|
#ifdef TF_RAID_MODE |
|
if ( TFGameRules()->IsRaidMode() ) |
|
{ |
|
if ( me->GetHomeArea() && TFGameRules()->GetRaidLogic() ) |
|
{ |
|
// try to pick a new area |
|
CTFNavArea *sentryArea = TFGameRules()->GetRaidLogic()->SelectRaidSentryArea(); |
|
if ( sentryArea ) |
|
{ |
|
me->SetHomeArea( sentryArea ); |
|
} |
|
} |
|
} |
|
#endif // TF_RAID_MODE |
|
|
|
SelectBuildLocation( me ); |
|
|
|
return Continue(); |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
ActionResult< CTFBot > CTFBotEngineerMoveToBuild::Update( CTFBot *me, float interval ) |
|
{ |
|
if ( me->WasPointJustLost() ) |
|
{ |
|
if ( m_fallBackTimer.HasStarted() ) |
|
{ |
|
if ( m_fallBackTimer.IsElapsed() ) |
|
{ |
|
SelectBuildLocation( me ); |
|
m_fallBackTimer.Invalidate(); |
|
} |
|
else |
|
{ |
|
// wait a moment while we decide where to build near fallback point |
|
return Continue(); |
|
} |
|
} |
|
} |
|
|
|
CBaseObject *mySentry = me->GetObjectOfType( OBJ_SENTRYGUN ); |
|
if ( mySentry ) |
|
{ |
|
// we already have a sentry from a previous life - continue what we were doing |
|
|
|
// if we used a sentry hint last time, reuse it |
|
CTFBotHintSentrygun *sentryHint; |
|
for( sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( NULL, "bot_hint_sentrygun" ) ); |
|
sentryHint; |
|
sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( sentryHint, "bot_hint_sentrygun" ) ) ) |
|
{ |
|
if ( sentryHint->GetPlayerOwner() == me ) |
|
{ |
|
return ChangeTo( new CTFBotEngineerBuilding( sentryHint ), "Going back to my existing sentry nest and reusing a sentry hint" ); |
|
} |
|
} |
|
|
|
return ChangeTo( new CTFBotEngineerBuilding, "Going back to my existing sentry nest" ); |
|
} |
|
|
|
// offensive engineers need to place a forward teleporter |
|
if ( TFGameRules()->GetGameType() == TF_GAMETYPE_CP && !TFGameRules()->IsInKothMode() && me->GetTeamNumber() == TF_TEAM_BLUE ) |
|
{ |
|
CObjectTeleporter *myTeleportExit = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT ); |
|
int myTeam = me->GetTeamNumber(); |
|
|
|
if ( myTeleportExit ) |
|
{ |
|
// if exit is too far from the point, destroy it and try again |
|
CTeamControlPoint *point = me->GetMyControlPoint(); |
|
if ( point ) |
|
{ |
|
CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() ); |
|
|
|
myTeleportExit->UpdateLastKnownArea(); |
|
CTFNavArea *exitArea = (CTFNavArea *)myTeleportExit->GetLastKnownArea(); |
|
|
|
if ( pointArea && exitArea ) |
|
{ |
|
float travelToPoint = fabs( exitArea->GetIncursionDistance( myTeam ) - pointArea->GetIncursionDistance( myTeam ) ); |
|
|
|
if ( travelToPoint > tf_bot_max_teleport_exit_travel_to_point.GetFloat() ) |
|
{ |
|
// too far, destroy it |
|
myTeleportExit->DestroyObject(); |
|
myTeleportExit = NULL; |
|
} |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
CObjectTeleporter *myTeleportEntrance = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE ); |
|
CTFNavArea *myArea = me->GetLastKnownArea(); |
|
|
|
bool shouldBuildExit = true; |
|
|
|
// if we have a teleporter entrance, don't place the exit too close to it |
|
if ( myTeleportEntrance && myArea ) |
|
{ |
|
myTeleportEntrance->UpdateLastKnownArea(); |
|
CTFNavArea *enterArea = (CTFNavArea *)myTeleportEntrance->GetLastKnownArea(); |
|
|
|
if ( enterArea ) |
|
{ |
|
float travelBetween = fabs( enterArea->GetIncursionDistance( myTeam ) - myArea->GetIncursionDistance( myTeam ) ); |
|
|
|
if ( travelBetween < tf_bot_min_teleport_travel.GetFloat() ) |
|
{ |
|
shouldBuildExit = false; |
|
} |
|
} |
|
} |
|
|
|
if ( shouldBuildExit ) |
|
{ |
|
// no exit yet - need to place one |
|
// when we see the enemy, retreat to cover and build the exit there |
|
if ( me->GetVisionInterface()->GetPrimaryKnownThreat( true ) ) |
|
{ |
|
if ( !me->m_Shared.InCond( TF_COND_INVULNERABLE ) && ShouldRetreat( me ) != ANSWER_NO ) |
|
{ |
|
Action< CTFBot > *nextActionWhenInCover = new CTFBotEngineerBuildTeleportExit; |
|
return SuspendFor( new CTFBotRetreatToCover( nextActionWhenInCover ), "Retreating to a safe place to build my teleporter exit" ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// move to build position |
|
if ( m_repathTimer.IsElapsed() ) |
|
{ |
|
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); |
|
|
|
CTFBotPathCost cost( me, SAFEST_ROUTE ); |
|
m_path.Compute( me, m_sentryBuildLocation, cost ); |
|
} |
|
|
|
Vector forward; |
|
me->EyeVectors( &forward ); |
|
forward.z = 0.0f; |
|
forward.NormalizeInPlace(); |
|
|
|
Vector myBlueprintPosition = me->GetAbsOrigin() + 50.0f * forward; |
|
|
|
const float closeToHome = 25.0f; |
|
Vector toBuild = m_sentryBuildLocation - myBlueprintPosition; |
|
Vector toMe = m_sentryBuildLocation - me->GetAbsOrigin(); |
|
|
|
if ( me->GetLocomotionInterface()->IsOnGround() ) |
|
{ |
|
// we need to wait until we're on the ground since the Build action assumes our position OnStart is where we are going to build |
|
if ( toMe.AsVector2D().IsLengthLessThan( closeToHome ) || toBuild.AsVector2D().IsLengthLessThan( closeToHome ) ) |
|
{ |
|
if ( m_sentryBuildHint != NULL ) |
|
{ |
|
return ChangeTo( new CTFBotEngineerBuilding( m_sentryBuildHint ), "Reached my precise build location" ); |
|
} |
|
|
|
return ChangeTo( new CTFBotEngineerBuilding, "Reached my build location" ); |
|
} |
|
|
|
m_path.Update( me ); |
|
} |
|
|
|
return Continue(); |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnStuck( CTFBot *me ) |
|
{ |
|
// SelectBuildLocation( me ); |
|
return TryContinue(); |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnMoveToSuccess( CTFBot *me, const Path *path ) |
|
{ |
|
return TryContinue(); |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnMoveToFailure( CTFBot *me, const Path *path, MoveToFailureType reason ) |
|
{ |
|
SelectBuildLocation( me ); |
|
|
|
return TryContinue(); |
|
} |
|
|
|
|
|
//--------------------------------------------------------------------------------------------- |
|
EventDesiredResult< CTFBot > CTFBotEngineerMoveToBuild::OnTerritoryLost( CTFBot *me, int territoryID ) |
|
{ |
|
// we have to wait a moment until contested point changes to select a new build spot |
|
m_fallBackTimer.Start( 0.2f ); |
|
|
|
return TryContinue(); |
|
}
|
|
|