diff options
Diffstat (limited to 'game/server/tf/bot/behavior/engineer')
22 files changed, 3223 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.cpp b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.cpp new file mode 100644 index 0000000..c86318f --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.cpp @@ -0,0 +1,123 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// Michael Booth, September 2012 + +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_obj.h" +#include "tf_obj_sentrygun.h" +#include "tf_obj_dispenser.h" +#include "tf_gamerules.h" +#include "tf_weapon_builder.h" +#include "bot/tf_bot.h" +#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h" +#include "bot/map_entities/tf_bot_hint_sentrygun.h" +#include "bot/map_entities/tf_bot_hint_teleporter_exit.h" +#include "string_t.h" +#include "tf_fx.h" + +extern ConVar tf_bot_engineer_mvm_building_health_multiplier; + +//--------------------------------------------------------------------------------------------- +CTFBotMvMEngineerBuildSentryGun::CTFBotMvMEngineerBuildSentryGun( CTFBotHintSentrygun* pSentryHint ) +{ + m_sentryBuildHint = pSentryHint; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMvMEngineerBuildSentryGun::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + me->StartBuildingObjectOfType( OBJ_SENTRYGUN ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMvMEngineerBuildSentryGun::Update( CTFBot *me, float interval ) +{ + if ( m_sentryBuildHint == NULL ) + return Done( "No hint entity" ); + + float rangeToBuildSpot = me->GetRangeTo( m_sentryBuildHint->GetAbsOrigin() ); + + if ( rangeToBuildSpot < 200.0f ) + { + // crouch as we get close so we don't overshoot + me->PressCrouchButton(); + + me->GetBodyInterface()->AimHeadTowards( m_sentryBuildHint->GetAbsOrigin(), IBody::MANDATORY, 0.1f, NULL, "Placing sentry" ); + } + + // various interruptions could mean we're away from our build location - move to it + if ( rangeToBuildSpot > 25.0f ) + { + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + CTFBotPathCost cost( me, SAFEST_ROUTE ); + m_path.Compute( me, m_sentryBuildHint->GetAbsOrigin(), cost ); + } + + m_path.Update( me ); + + if ( !m_path.IsValid() ) + { + return Done( "Path failed" ); + } + + return Continue(); + } + + if ( !m_delayBuildTime.HasStarted() ) + { + m_delayBuildTime.Start( 0.1f ); + TFGameRules()->PushAllPlayersAway( m_sentryBuildHint->GetAbsOrigin(), 400, 500, TF_TEAM_RED ); + } + else if ( m_delayBuildTime.HasStarted() && m_delayBuildTime.IsElapsed() ) + { + // destroy previous object + me->DetonateObjectOfType( OBJ_SENTRYGUN, MODE_SENTRYGUN_NORMAL, true ); + + // directly create a sentry gun at the precise position and orientation desired + m_sentry = (CObjectSentrygun *)CreateEntityByName( "obj_sentrygun" ); + if ( m_sentry ) + { + m_sentry->SetName( m_sentryBuildHint->GetEntityName() ); + + m_sentryBuildHint->IncrementUseCount(); + m_sentry->m_nDefaultUpgradeLevel = 2; + + m_sentry->SetAbsOrigin( m_sentryBuildHint->GetAbsOrigin() ); + m_sentry->SetAbsAngles( QAngle( 0, m_sentryBuildHint->GetAbsAngles().y, 0 ) ); + m_sentry->Spawn(); + + m_sentry->StartPlacement( me ); + m_sentry->StartBuilding( me ); + + // the sentry owns this hint now + m_sentryBuildHint->SetOwnerEntity( m_sentry ); + + m_sentry = NULL; + } + + return Done( "Built a sentry" ); + } + + return Continue(); +} + + + +//--------------------------------------------------------------------------------------------- +void CTFBotMvMEngineerBuildSentryGun::OnEnd( CTFBot *me, Action< CTFBot > *nextAction ) +{ + if ( m_sentry.Get() ) + { + m_sentry->DropCarriedObject( me ); + UTIL_Remove( m_sentry ); + m_sentry = NULL; + } +} diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h new file mode 100644 index 0000000..199492f --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// Michael Booth, September 2012 + +#ifndef TF_BOT_MVM_ENGINEER_BUILD_SENTRYGUN_H +#define TF_BOT_MVM_ENGINEER_BUILD_SENTRYGUN_H + +class CTFBotHintSentrygun; + +class CTFBotMvMEngineerBuildSentryGun : public Action< CTFBot > +{ +public: + CTFBotMvMEngineerBuildSentryGun( CTFBotHintSentrygun* pSentryHint ); + + 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 const char *GetName( void ) const { return "MvMEngineerBuildSentryGun"; }; + +private: + CHandle< CTFBotHintSentrygun > m_sentryBuildHint; + CHandle< CObjectSentrygun > m_sentry; + + CountdownTimer m_delayBuildTime; + CountdownTimer m_repathTimer; + PathFollower m_path; +}; + +#endif // TF_BOT_MVM_ENGINEER_BUILD_SENTRYGUN_H diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.cpp b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.cpp new file mode 100644 index 0000000..024b981 --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.cpp @@ -0,0 +1,105 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// Michael Booth, September 2012 + +#include "cbase.h" +#include "string_t.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_obj.h" +#include "tf_obj_sentrygun.h" +#include "tf_obj_dispenser.h" +#include "tf_gamerules.h" +#include "tf_weapon_builder.h" +#include "bot/tf_bot.h" +#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h" +#include "bot/map_entities/tf_bot_hint_teleporter_exit.h" + +ConVar tf_bot_engineer_mvm_building_health_multiplier( "tf_bot_engineer_building_health_multiplier", "2", FCVAR_CHEAT ); + +extern ConVar tf_bot_path_lookahead_range; + + +//--------------------------------------------------------------------------------------------- +CTFBotMvMEngineerBuildTeleportExit::CTFBotMvMEngineerBuildTeleportExit( CTFBotHintTeleporterExit *hint ) +{ + m_teleporterBuildHint = hint; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMvMEngineerBuildTeleportExit::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMvMEngineerBuildTeleportExit::Update( CTFBot *me, float interval ) +{ + if ( m_teleporterBuildHint == NULL ) + return Done( "No hint entity" ); + + // various interruptions could mean we're away from our build location - move to it + if ( me->IsRangeGreaterThan( m_teleporterBuildHint->GetAbsOrigin(), 25.0f ) ) + { + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_path.Compute( me, m_teleporterBuildHint->GetAbsOrigin(), cost ); + } + + m_path.Update( me ); + + if ( !m_path.IsValid() ) + { + return Done( "Path failed" ); + } + + return Continue(); + } + + if ( !m_delayBuildTime.HasStarted() ) + { + m_delayBuildTime.Start( 0.1f ); + TFGameRules()->PushAllPlayersAway( m_teleporterBuildHint->GetAbsOrigin(), 400, 500, TF_TEAM_RED ); + } + else if ( m_delayBuildTime.IsElapsed() ) + { + // destroy previous object + me->DetonateObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT, true ); + + // directly create at the precise position and orientation desired + CObjectTeleporter* myTeleporter = (CObjectTeleporter *)CreateEntityByName( "obj_teleporter" ); + if ( myTeleporter ) + { + myTeleporter->SetAbsOrigin( m_teleporterBuildHint->GetAbsOrigin() ); + myTeleporter->SetAbsAngles( QAngle( 0, m_teleporterBuildHint->GetAbsAngles().y, 0 ) ); + myTeleporter->SetObjectMode( MODE_TELEPORTER_EXIT ); + myTeleporter->Spawn(); + + myTeleporter->SetTeleportWhere( me->GetTeleportWhere() ); + + if ( me->ShouldQuickBuild() ) + { + myTeleporter->ForceQuickBuild(); + } + + myTeleporter->StartPlacement( me ); + myTeleporter->StartBuilding( me ); + + int iHealth = myTeleporter->GetMaxHealthForCurrentLevel() * tf_bot_engineer_mvm_building_health_multiplier.GetFloat(); + myTeleporter->SetMaxHealth( iHealth ); + myTeleporter->SetHealth( iHealth ); + + m_teleporterBuildHint->SetOwnerEntity( myTeleporter ); + + me->EmitSound( "Engineer.MVM_AutoBuildingTeleporter02" ); + + return Done( "Teleport exit built" ); + } + } + + return Continue(); +} diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h new file mode 100644 index 0000000..7acb0b6 --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h @@ -0,0 +1,27 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// Michael Booth, September 2012 + +#ifndef TF_BOT_MVM_ENGINEER_BUILD_TELEPORTER_H +#define TF_BOT_MVM_ENGINEER_BUILD_TELEPORTER_H + +class CTFBotHintTeleporterExit; + +class CTFBotMvMEngineerBuildTeleportExit : public Action< CTFBot > +{ +public: + CTFBotMvMEngineerBuildTeleportExit( CTFBotHintTeleporterExit *hint ); + + virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction ); + virtual ActionResult< CTFBot > Update( CTFBot *me, float interval ); + + virtual const char *GetName( void ) const { return "MvMEngineerBuildTeleportExit"; }; + +private: + CHandle< CTFBotHintTeleporterExit > m_teleporterBuildHint; + + CountdownTimer m_delayBuildTime; + CountdownTimer m_repathTimer; + PathFollower m_path; +}; + +#endif // TF_BOT_MVM_ENGINEER_BUILD_TELEPORTER_H diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.cpp b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.cpp new file mode 100644 index 0000000..c6157dc --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.cpp @@ -0,0 +1,662 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// Michael Booth, September 2012 + +#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_obj_teleporter.h" +#include "bot/tf_bot.h" +#include "bot/map_entities/tf_bot_hint_engineer_nest.h" +#include "bot/map_entities/tf_bot_hint_sentrygun.h" +#include "bot/map_entities/tf_bot_hint_teleporter_exit.h" + +#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.h" +#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_sentry.h" +#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_build_teleporter.h" +#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h" +#include "bot/behavior/tf_bot_retreat_to_cover.h" + + +ConVar tf_bot_engineer_mvm_sentry_hint_bomb_forward_range( "tf_bot_engineer_mvm_sentry_hint_bomb_forward_range", "0", FCVAR_CHEAT ); +ConVar tf_bot_engineer_mvm_sentry_hint_bomb_backward_range( "tf_bot_engineer_mvm_sentry_hint_bomb_backward_range", "3000", FCVAR_CHEAT ); +ConVar tf_bot_engineer_mvm_hint_min_distance_from_bomb( "tf_bot_engineer_mvm_hint_min_distance_from_bomb", "1300", FCVAR_CHEAT ); + +struct BombInfo_t +{ + Vector m_vPosition; + float m_flMinBattleFront; + float m_flMaxBattleFront; +}; + + +bool GetBombInfo( BombInfo_t* pBombInfo = NULL ) +{ + // find the incursion distance of the current "front" (the location of the bomb) + + // first find farthest bomb delivery distance of invading team since maps + // have different spawn room sizes and geometries + float battlefront = 0.0f; + + for( int n=0; n<TheNavAreas.Count(); ++n ) + { + CTFNavArea *area = (CTFNavArea *)TheNavAreas[n]; + + if ( area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) ) + { + continue; + } + + float areaDistanceToTarget = area->GetTravelDistanceToBombTarget(); + if ( areaDistanceToTarget > battlefront && areaDistanceToTarget > 0.0f ) + { + battlefront = areaDistanceToTarget; + } + } + + + // find the travel distance from the bomb to the delivery target and use it as the front + CCaptureFlag *flag = NULL; + Vector vBombSpot(0, 0, 0); + for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i ) + { + CCaptureFlag *pTempFlag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[i] ); + Vector vTempBombSpot; + CTFPlayer *carrier = ToTFPlayer( pTempFlag->GetOwnerEntity() ); + if ( carrier ) + { + vTempBombSpot = carrier->GetAbsOrigin(); + } + else + { + vTempBombSpot = pTempFlag->WorldSpaceCenter(); + } + + CTFNavArea *flagArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( vTempBombSpot, false, 1000.0f ); + if ( flagArea ) + { + float flagDistanceToTarget = flagArea->GetTravelDistanceToBombTarget(); + + if ( flagDistanceToTarget < battlefront && flagDistanceToTarget >= 0.0f ) + { + battlefront = flagDistanceToTarget; + flag = pTempFlag; + vBombSpot = vTempBombSpot; + } + } + } + + float flMaxBattlefront = battlefront + tf_bot_engineer_mvm_sentry_hint_bomb_backward_range.GetFloat(); + float flMinBattlefront = battlefront - tf_bot_engineer_mvm_sentry_hint_bomb_forward_range.GetFloat(); + + if ( pBombInfo ) + { + pBombInfo->m_vPosition = vBombSpot; + pBombInfo->m_flMinBattleFront = flMinBattlefront; + pBombInfo->m_flMaxBattleFront = flMaxBattlefront; + } + + return flag ? true : false; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMvMEngineerIdle::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() ); + + me->StopLookingAroundForEnemies(); + + m_sentryHint = NULL; + m_teleporterHint = NULL; + m_nestHint = NULL; + m_nTeleportedCount = 0; + m_bTeleportedToHint = false; + m_bTriedToDetonateStaleNest = false; + + return Continue(); +} + + +void CTFBotMvMEngineerIdle::TakeOverStaleNest( CBaseTFBotHintEntity* pHint, CTFBot *me ) +{ + if ( pHint != NULL && pHint->OwnerObjectHasNoOwner() ) + { + CBaseObject* pObj = static_cast< CBaseObject* >( pHint->GetOwnerEntity() ); + pObj->SetOwnerEntity( me ); + pObj->SetBuilder( me ); + me->AddObject( pObj ); + } +} + + +bool CTFBotMvMEngineerIdle::ShouldAdvanceNestSpot( CTFBot *me ) +{ + if ( !m_nestHint ) + { + return false; + } + + if ( !m_reevaluateNestTimer.HasStarted() ) + { + m_reevaluateNestTimer.Start( 5.f ); + return false; + } + + for ( int i=0; i<me->GetObjectCount(); ++i ) + { + CBaseObject *pObj = me->GetObject( i ); + if ( pObj && pObj->GetHealth() < pObj->GetMaxHealth() ) + { + // if the nest is under attack, don't advance the nest + m_reevaluateNestTimer.Start( 5.f ); + return false; + } + } + + if ( m_reevaluateNestTimer.IsElapsed() ) + { + m_reevaluateNestTimer.Invalidate(); + } + + BombInfo_t bombInfo; + if ( GetBombInfo( &bombInfo ) ) + { + if ( m_nestHint ) + { + CTFNavArea *hintArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( m_nestHint->GetAbsOrigin(), false, 1000.0f ); + if ( hintArea ) + { + float hintDistanceToTarget = hintArea->GetTravelDistanceToBombTarget(); + + bool bShouldAdvance = ( hintDistanceToTarget > bombInfo.m_flMaxBattleFront ); + + return bShouldAdvance; + } + } + } + + return false; +} + + +void CTFBotMvMEngineerIdle::TryToDetonateStaleNest() +{ + if ( m_bTriedToDetonateStaleNest ) + return; + + // wait until the engy finish building his nest + if ( ( m_sentryHint && !m_sentryHint->OwnerObjectFinishBuilding() ) || + ( m_teleporterHint && !m_teleporterHint->OwnerObjectFinishBuilding() ) ) + return; + + // collect all existing and active teleporter hints + CUtlVector< CTFBotHintEngineerNest* > activeEngineerNest; + for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i ) + { + CBaseTFBotHintEntity *pHint = static_cast<CBaseTFBotHintEntity*>( ITFBotHintEntityAutoList::AutoList()[i] ); + if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_ENGINEER_NEST ) && pHint->IsEnabled() && pHint->GetOwnerEntity() == NULL ) + { + activeEngineerNest.AddToTail( static_cast< CTFBotHintEngineerNest* >( pHint ) ); + } + } + + // try to detonate stale nest that's out of range, when engineer finished building his nest + for ( int i=0; i<activeEngineerNest.Count(); ++i ) + { + CTFBotHintEngineerNest *pNest = activeEngineerNest[i]; + if ( pNest->IsStaleNest() ) + { + pNest->DetonateStaleNest(); + } + } + + m_bTriedToDetonateStaleNest = true; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMvMEngineerIdle::Update( CTFBot *me, float interval ) +{ + if ( !me->IsAlive() ) + { + // don't do anything when I'm dead + return Done(); + } + + // Always equip my wrench + CBaseCombatWeapon *wrench = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE ); + if ( wrench ) + { + me->Weapon_Switch( wrench ); + } + + if ( m_nestHint == NULL || ShouldAdvanceNestSpot( me ) ) + { + if ( m_findHintTimer.HasStarted() && !m_findHintTimer.IsElapsed() ) + { + // too soon + return Continue(); + } + + m_findHintTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + // figure out where to teleport into the map + bool bShouldTeleportToHint = me->HasAttribute( CTFBot::TELEPORT_TO_HINT ); + bool bShouldCheckForBlockingObject = !m_bTeleportedToHint && bShouldTeleportToHint; + CHandle< CTFBotHintEngineerNest > newNest = NULL; + if ( !CTFBotMvMEngineerHintFinder::FindHint( bShouldCheckForBlockingObject, !bShouldTeleportToHint, &newNest ) ) + { + // try again next time + return Continue(); + } + + // unown the old nest + if ( m_nestHint ) + { + m_nestHint->SetOwnerEntity( NULL ); + } + + m_nestHint = newNest; + m_nestHint->SetOwnerEntity( me ); + m_sentryHint = m_nestHint->GetSentryHint(); + TakeOverStaleNest( m_sentryHint, me ); + + if ( me->GetTeleportWhere().Count() > 0 ) + { + m_teleporterHint = m_nestHint->GetTeleporterHint(); + TakeOverStaleNest( m_teleporterHint, me ); + } + } + + if ( !m_bTeleportedToHint && me->HasAttribute( CTFBot::TELEPORT_TO_HINT ) ) + { + m_nTeleportedCount++; + bool bFirstTeleportSpawn = m_nTeleportedCount == 1; + m_bTeleportedToHint = true; + return SuspendFor( new CTFBotMvMEngineerTeleportSpawn( m_nestHint, bFirstTeleportSpawn ), "In spawn area - teleport to the teleporter hint" ); + } + + const float rebuildInterval = 3.0f; + CObjectSentrygun *mySentry = NULL; + if ( m_sentryHint ) + { + if ( m_sentryHint->GetOwnerEntity() && m_sentryHint->GetOwnerEntity()->IsBaseObject() ) + { + mySentry = assert_cast< CObjectSentrygun* >( m_sentryHint->GetOwnerEntity() ); + } + + if ( mySentry ) + { + // force an interval between sentry being destroyed and me trying to rebuild it + m_sentryRebuildTimer.Start( rebuildInterval ); + } + else + { + // check if there's a stale object on the hint + if ( m_sentryHint->GetOwnerEntity() && m_sentryHint->GetOwnerEntity()->IsBaseObject() ) + { + mySentry = assert_cast< CObjectSentrygun* >( m_sentryHint->GetOwnerEntity() ); + me->AddObject( mySentry ); + mySentry->SetOwnerEntity( me ); + } + else + { + if ( m_sentryRebuildTimer.IsElapsed() ) + { + return SuspendFor( new CTFBotMvMEngineerBuildSentryGun( m_sentryHint ), "No sentry - building a new one" ); + } + else + { + // run away! + return SuspendFor( new CTFBotRetreatToCover( 1.0f ), "Lost my sentry - retreat!" ); + } + } + } + } + + if ( mySentry && mySentry->GetHealth() < mySentry->GetMaxHealth() && !mySentry->IsBuilding() ) + { + // track when sentry was last hurt + m_sentryInjuredTimer.Start( 3.0f ); + } + + + CObjectTeleporter *myTeleporter = NULL; + if ( m_teleporterHint && m_sentryInjuredTimer.IsElapsed() ) + { + if ( m_teleporterHint->GetOwnerEntity() && m_teleporterHint->GetOwnerEntity()->IsBaseObject() ) + { + // force an interval between teleporter being destroyed and me trying to rebuild it + myTeleporter = assert_cast< CObjectTeleporter* >( m_teleporterHint->GetOwnerEntity() ); + m_teleporterRebuildTimer.Start( rebuildInterval ); + } + else if ( m_teleporterRebuildTimer.IsElapsed() ) + { + return SuspendFor( new CTFBotMvMEngineerBuildTeleportExit( m_teleporterHint ), "Sentry is safe - building a teleport exit" ); + } + } + + // fix teleporter if sentry is not hurt + if ( myTeleporter && m_sentryInjuredTimer.IsElapsed() && myTeleporter->GetHealth() < myTeleporter->GetMaxHealth() && !myTeleporter->IsBuilding() ) + { + float rangeToTeleporter = me->GetDistanceBetween( myTeleporter ); + + const float nearTeleporterRange = 75.0f; + + if ( rangeToTeleporter < 1.2f * nearTeleporterRange ) + { + // crouch as I get close + me->PressCrouchButton(); + } + + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + Vector toTeleporter = myTeleporter->GetAbsOrigin() - me->GetAbsOrigin(); + Vector hittingTeleporterSpot = myTeleporter->GetAbsOrigin() - 50.0f * toTeleporter.Normalized(); + + CTFBotPathCost cost( me, SAFEST_ROUTE ); + m_path.Compute( me, hittingTeleporterSpot, cost ); + } + + m_path.Update( me ); + + if ( rangeToTeleporter < nearTeleporterRange ) + { + // we are in position - hit sentry with wrench + me->GetBodyInterface()->AimHeadTowards( myTeleporter->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my Teleporter" ); + me->PressFireButton(); + } + } + else if ( mySentry ) + { + float rangeToSentry = me->GetDistanceBetween( mySentry ); + + const float nearSentryRange = 75.0f; + + if ( rangeToSentry < 1.2f * nearSentryRange ) + { + // crouch as I get close + me->PressCrouchButton(); + } + + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + Vector mySentryForward; + AngleVectors( mySentry->GetTurretAngles(), &mySentryForward ); + + Vector behindSentrySpot = mySentry->GetAbsOrigin() - 50.0f * mySentryForward; + + CTFBotPathCost cost( me, SAFEST_ROUTE ); + m_path.Compute( me, behindSentrySpot, cost ); + } + + m_path.Update( me ); + + if ( rangeToSentry < nearSentryRange ) + { + // we are in position - hit sentry with wrench + me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my Sentry" ); + me->PressFireButton(); + } + } + + TryToDetonateStaleNest(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotMvMEngineerIdle::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const +{ + return ANSWER_NO; +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotMvMEngineerIdle::ShouldRetreat( const INextBot *me ) const +{ + return ANSWER_NO; +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotMvMEngineerIdle::ShouldHurry( const INextBot *me ) const +{ + return ANSWER_YES; +} + + +CTFBotHintEngineerNest* SelectOutOfRangeNest( const CUtlVector< CTFBotHintEngineerNest* >& nestVector ) +{ + if ( nestVector.Count() ) + { + for ( int i=0; i<nestVector.Count(); ++i ) + { + if ( nestVector[i]->IsStaleNest() ) + { + return nestVector[i]; + } + } + + int which = RandomInt( 0, nestVector.Count() - 1 ); + return nestVector[which]; + } + + return NULL; +} + + +//--------------------------------------------------------------------------------------------- +bool CTFBotMvMEngineerHintFinder::FindHint( bool bShouldCheckForBlockingObjects, bool bAllowOutOfRangeNest, CHandle< CTFBotHintEngineerNest >* pFoundNest /*= NULL*/ ) +{ + // collect all existing and active teleporter hints + CUtlVector< CTFBotHintEngineerNest* > activeEngineerNest; + for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i ) + { + CBaseTFBotHintEntity *pHint = static_cast<CBaseTFBotHintEntity*>( ITFBotHintEntityAutoList::AutoList()[i] ); + if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_ENGINEER_NEST ) && pHint->IsEnabled() && pHint->GetOwnerEntity() == NULL ) + { + activeEngineerNest.AddToTail( static_cast< CTFBotHintEngineerNest* >( pHint ) ); + } + } + + if ( activeEngineerNest.Count() == 0 ) + { + if ( pFoundNest ) + { + *pFoundNest = NULL; + } + + return false; + } + + BombInfo_t bombInfo; + GetBombInfo( &bombInfo ); + + CUtlVector< CTFBotHintEngineerNest* > forwardOutOfRangeHintVector; + CUtlVector< CTFBotHintEngineerNest* > backwardOutOfRangeHintVector; + + CUtlVector< CTFBotHintEngineerNest* > freeAtFrontHintVector; + CUtlVector< CTFBotHintEngineerNest* > staleAtFrontHintVector; + for( int i=0; i<activeEngineerNest.Count(); ++i ) + { + CTFBotHintEngineerNest* pCurrentNest = activeEngineerNest[i]; + const Vector& vNestPosition = pCurrentNest->GetAbsOrigin(); + CTFNavArea *hintArea = (CTFNavArea *)TheNavMesh->GetNearestNavArea( vNestPosition, false, 1000.0f ); + if ( !hintArea ) + { + Warning( "Sentry hint has NULL nav area!\n" ); + continue; + } + + + float hintDistanceToTarget = hintArea->GetTravelDistanceToBombTarget(); + if ( hintDistanceToTarget > bombInfo.m_flMinBattleFront && hintDistanceToTarget < bombInfo.m_flMaxBattleFront ) + { + if ( bShouldCheckForBlockingObjects ) + { + // check for blocking players and objects + CBaseEntity *pList[256]; + int count = UTIL_EntitiesInBox( pList, ARRAYSIZE( pList ), vNestPosition + VEC_HULL_MIN, vNestPosition + VEC_HULL_MAX, FL_CLIENT|FL_OBJECT ); + if ( count > 0 ) + { + continue; + } + } + + // this hint is in range of the front + if ( pCurrentNest->IsStaleNest() ) + { + // some dead engineer was here and left his object(s) behind. I should take over + staleAtFrontHintVector.AddToTail( pCurrentNest ); + } + else + { + if ( VectorLength( bombInfo.m_vPosition - vNestPosition ) < tf_bot_engineer_mvm_hint_min_distance_from_bomb.GetFloat() ) + { + // the hint is too close to the bomb, don't go there + continue; + } + // this hint is also unowned + freeAtFrontHintVector.AddToTail( pCurrentNest ); + } + } + else if ( hintDistanceToTarget > bombInfo.m_flMaxBattleFront ) + { + forwardOutOfRangeHintVector.AddToTail( pCurrentNest ); + } + else + { + backwardOutOfRangeHintVector.AddToTail( pCurrentNest ); + } + } + + CTFBotHintEngineerNest *hint = NULL; + if ( freeAtFrontHintVector.Count() == 0 && staleAtFrontHintVector.Count() == 0 ) + { + if ( bAllowOutOfRangeNest ) + { + // try to advance forward before falling backward + hint = SelectOutOfRangeNest( forwardOutOfRangeHintVector ); + if ( !hint ) + { + hint = SelectOutOfRangeNest( backwardOutOfRangeHintVector ); + } + } + + // no hints are in range, or they are all in use + if ( pFoundNest ) + { + *pFoundNest = hint; + } + } + else + { + // try to pick stale nest in range first + if ( staleAtFrontHintVector.Count() ) + { + int whichHint = RandomInt( 0, staleAtFrontHintVector.Count()-1 ); + hint = staleAtFrontHintVector[ whichHint ]; + } + // if I didn't find any stale nest, try to find a free one + else if ( freeAtFrontHintVector.Count() ) + { + int whichHint = RandomInt( 0, freeAtFrontHintVector.Count()-1 ); + hint = freeAtFrontHintVector[ whichHint ]; + } + + if ( pFoundNest ) + { + *pFoundNest = hint; + } + } + + return hint != NULL; +} + + +//-------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( tf_bot_mvm_show_engineer_hint_region, "Show the nav areas MvM engineer bots will consider when selecting sentry and teleporter hints", FCVAR_CHEAT ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + + trace_t result; + Vector forward; + pPlayer->EyeVectors( &forward ); + + UTIL_TraceLine( pPlayer->EyePosition(), + pPlayer->EyePosition() + forward * 10000.0f, MASK_SOLID, + pPlayer, COLLISION_GROUP_NONE, &result ); + + float flDrawTime = 5.0f; + + if ( result.DidHit() ) + { + CTFNavArea *area = (CTFNavArea *)TheTFNavMesh()->GetNearestNavArea( result.endpos ); + + if ( area ) + { + float battlefront = area->GetTravelDistanceToBombTarget(); + + float maxBattlefront = battlefront + tf_bot_engineer_mvm_sentry_hint_bomb_backward_range.GetFloat(); + float minBattlefront = battlefront - tf_bot_engineer_mvm_sentry_hint_bomb_forward_range.GetFloat(); + + CUtlVector< CTFNavArea * > battlefrontAreaVector; + TheTFNavMesh()->CollectAreaWithinBombTravelRange( &battlefrontAreaVector, minBattlefront, maxBattlefront ); + + CUtlVector< CTFNavArea * > hintAreaVector; + for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i ) + { + CBaseTFBotHintEntity *pHint = static_cast< CBaseTFBotHintEntity* >( ITFBotHintEntityAutoList::AutoList()[i] ); + hintAreaVector.AddToTail( (CTFNavArea*)TheNavMesh->GetNearestNavArea( pHint ) ); + } + + for( int i=0; i<battlefrontAreaVector.Count(); ++i ) + { + CTFNavArea *fillArea = battlefrontAreaVector[i]; + + if ( fillArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE || TF_NAV_SPAWN_ROOM_RED ) ) + { + continue; + } + + fillArea->DrawFilled( 255, 100, 0, 0, flDrawTime ); + + for ( int j=0; j<hintAreaVector.Count(); ++j ) + { + if ( fillArea == hintAreaVector[j] ) + { + CBaseTFBotHintEntity *pHint = static_cast< CBaseTFBotHintEntity* >( ITFBotHintEntityAutoList::AutoList()[j] ); + Color color; + if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_SENTRYGUN ) ) + { + color = Color( 0, 255, 0 ); + } + else if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_TELEPORTER_EXIT ) ) + { + color = Color( 0, 0, 255 ); + } + else + { + bool bTooCloseToBomb = VectorLength( result.endpos - pHint->GetAbsOrigin() ) < tf_bot_engineer_mvm_hint_min_distance_from_bomb.GetFloat(); + color = bTooCloseToBomb ? Color( 255, 0, 0 ) : Color( 255, 255, 0 ); + } + NDebugOverlay::Sphere( pHint->GetAbsOrigin(), 50, color.r(), color.g(), color.b(), true, flDrawTime ); + } + } + } + + NDebugOverlay::Sphere( result.endpos, tf_bot_engineer_mvm_hint_min_distance_from_bomb.GetFloat(), 255, 255, 0, false, flDrawTime ); + } + } +} diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.h b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.h new file mode 100644 index 0000000..13ea69f --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_idle.h @@ -0,0 +1,55 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// Michael Booth, September 2012 + +#ifndef TF_BOT_MVM_ENGINEER_IDLE_H +#define TF_BOT_MVM_ENGINEER_IDLE_H + +#include "Path/NextBotPathFollow.h" + +class CBaseTFBotHintEntity; +class CTFBotHintSentrygun; +class CTFBotHintTeleporterExit; +class CTFBotHintEngineerNest; + +class CTFBotMvMEngineerIdle : public Action< CTFBot > +{ +public: + virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction ); + virtual ActionResult< CTFBot > Update( CTFBot *me, float interval ); + + virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; + 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 "MvMEngineerIdle"; }; + +private: + PathFollower m_path; + CountdownTimer m_repathTimer; + CountdownTimer m_sentryInjuredTimer; + CountdownTimer m_sentryRebuildTimer; + CountdownTimer m_teleporterRebuildTimer; + CountdownTimer m_findHintTimer; + CountdownTimer m_reevaluateNestTimer; + + int m_nTeleportedCount; + bool m_bTeleportedToHint; + CHandle< CTFBotHintTeleporterExit > m_teleporterHint; + CHandle< CTFBotHintSentrygun > m_sentryHint; + CHandle< CTFBotHintEngineerNest > m_nestHint; + + void TakeOverStaleNest( CBaseTFBotHintEntity* pHint, CTFBot *me ); + bool ShouldAdvanceNestSpot( CTFBot *me ); + + void TryToDetonateStaleNest(); + bool m_bTriedToDetonateStaleNest; +}; + +class CTFBotMvMEngineerHintFinder +{ +public: + static bool FindHint( bool bShouldCheckForBlockingObjects, bool bAllowOutOfRangeNest, CHandle< CTFBotHintEngineerNest >* pFoundNest = NULL ); +}; + + +#endif // TF_BOT_MVM_ENGINEER_IDLE_H diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.cpp b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.cpp new file mode 100644 index 0000000..5b439e6 --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.cpp @@ -0,0 +1,95 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_obj.h" +#include "tf_gamerules.h" +#include "bot/tf_bot.h" +#include "tf_obj_sentrygun.h" +#include "bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h" +#include "bot/map_entities/tf_bot_hint_entity.h" +#include "string_t.h" +#include "tf_fx.h" +#include "player_vs_environment/tf_population_manager.h" + +//--------------------------------------------------------------------------------------------- +CTFBotMvMEngineerTeleportSpawn::CTFBotMvMEngineerTeleportSpawn( CBaseTFBotHintEntity* pHint, bool bFirstTeleportSpawn ) +{ + m_hintEntity = pHint; + m_bFirstTeleportSpawn = bFirstTeleportSpawn; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMvMEngineerTeleportSpawn::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + if ( !me->HasAttribute( CTFBot::TELEPORT_TO_HINT ) ) + { + return Done( "Cannot teleport to hint with out Attributes TeleportToHint" ); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMvMEngineerTeleportSpawn::Update( CTFBot *me, float interval ) +{ + if ( !m_teleportDelay.HasStarted() ) + { + m_teleportDelay.Start( 0.1f ); + if ( m_hintEntity ) + TFGameRules()->PushAllPlayersAway( m_hintEntity->GetAbsOrigin(), 400, 500, TF_TEAM_RED ); + } + else if ( m_teleportDelay.IsElapsed() ) + { + if ( !m_hintEntity ) + return Done( "Cannot teleport to hint as m_hintEntity is NULL" ); + + // teleport the engineer to the sentry spawn point + QAngle angles = m_hintEntity->GetAbsAngles(); + Vector origin = m_hintEntity->GetAbsOrigin(); + origin.z += 10.f; // move up off the around a little bit to prevent the engineer from getting stuck in the ground + + me->Teleport( &origin, &angles, NULL ); + + CPVSFilter filter( origin ); + TE_TFParticleEffect( filter, 0.0, "teleported_blue", origin, vec3_angle ); + TE_TFParticleEffect( filter, 0.0, "player_sparkles_blue", origin, vec3_angle ); + + if ( m_bFirstTeleportSpawn ) + { + // notify players that engineer's teleported into the map + TE_TFParticleEffect( filter, 0.0, "teleported_mvm_bot", origin, vec3_angle ); + me->EmitSound( "Engineer.MVM_BattleCry07" ); + m_hintEntity->EmitSound( "MVM.Robot_Engineer_Spawn" ); + + if ( g_pPopulationManager ) + { + CWave *pWave = g_pPopulationManager->GetCurrentWave(); + if ( pWave ) + { + if ( pWave->NumEngineersTeleportSpawned() == 0 ) + { + TFGameRules()->BroadcastSound( 255, "Announcer.MVM_First_Engineer_Teleport_Spawned" ); + } + else + { + TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Another_Engineer_Teleport_Spawned" ); + } + + pWave->IncrementEngineerTeleportSpawned(); + } + } + } + + return Done( "Teleported" ); + } + + return Continue(); +} + diff --git a/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h new file mode 100644 index 0000000..cd2f32b --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/mvm_engineer/tf_bot_mvm_engineer_teleport_spawn.h @@ -0,0 +1,28 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= + +#ifndef TF_BOT_MVM_ENGINEER_TELEPORT_SPAWN_H +#define TF_BOT_MVM_ENGINEER_TELEPORT_SPAWN_H + +class CBaseTFBotHintEntity; + +class CTFBotMvMEngineerTeleportSpawn : public Action< CTFBot > +{ +public: + CTFBotMvMEngineerTeleportSpawn( CBaseTFBotHintEntity* pHint, bool bFirstTeleportSpawn ); + + virtual ActionResult< CTFBot > OnStart( CTFBot *me, Action< CTFBot > *priorAction ); + virtual ActionResult< CTFBot > Update( CTFBot *me, float interval ); + + virtual const char *GetName( void ) const { return "MvMEngineerTeleportSpawn"; }; + +private: + CountdownTimer m_teleportDelay; + CHandle< CBaseTFBotHintEntity > m_hintEntity; + bool m_bFirstTeleportSpawn; +}; + +#endif // TF_BOT_MVM_ENGINEER_TELEPORT_SPAWN_H diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.cpp new file mode 100644 index 0000000..e6d586e --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.cpp @@ -0,0 +1,118 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_build.cpp +// Engineer building his buildings +// Michael Booth, February 2009 + +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_obj.h" +#include "tf_obj_sentrygun.h" +#include "tf_obj_dispenser.h" +#include "tf_gamerules.h" +#include "tf_weapon_builder.h" +#include "bot/tf_bot.h" +#include "bot/behavior/engineer/tf_bot_engineer_build.h" +#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h" +#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h" + + +#include "raid/tf_raid_logic.h" + +// this was useful when engineers build at their normal (slow) rate to make sure initial sentries get built in time +ConVar tf_raid_engineer_infinte_metal( "tf_raid_engineer_infinte_metal", "1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); + + +//--------------------------------------------------------------------------------------------- +Action< CTFBot > *CTFBotEngineerBuild::InitialContainedAction( CTFBot *me ) +{ + if ( TFGameRules()->IsPVEModeActive() ) + { + return new CTFBotEngineerMoveToBuild; + } + + return new CTFBotEngineerBuildTeleportEntrance; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuild::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuild::Update( CTFBot *me, float interval ) +{ + if ( TFGameRules()->IsPVEModeActive() && tf_raid_engineer_infinte_metal.GetBool() ) + { + // infinite ammo + me->GiveAmmo( 1000, TF_AMMO_METAL, true ); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuild::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotEngineerBuild::OnTerritoryLost( CTFBot *me, int territoryID ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +// Hack to disable ammo/health gathering elsewhere +QueryResultType CTFBotEngineerBuild::ShouldHurry( const INextBot *meBot ) const +{ + CTFBot *me = (CTFBot *)meBot->GetEntity(); + + CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN ); + CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER ); + + if ( mySentry && myDispenser && !mySentry->IsBuilding() && !myDispenser->IsBuilding() && me->GetActiveTFWeapon() && me->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_WRENCH ) + { + if ( me->IsAmmoLow() && myDispenser->GetAvailableMetal() <= 0 ) + { + // we're totally out of metal - collect some nearby + return ANSWER_NO; + } + + // by being in a "hurry" we wont collect health and ammo + return ANSWER_YES; + } + + return ANSWER_UNDEFINED; +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotEngineerBuild::ShouldAttack( const INextBot *meBot, const CKnownEntity *them ) const +{ + CTFBot *me = (CTFBot *)meBot->GetEntity(); + CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN ); + + CTFPlayer *themPlayer = ToTFPlayer( them->GetEntity() ); + + if ( themPlayer && themPlayer->IsPlayerClass( TF_CLASS_SPY ) ) + { + // Engineers hate Spies + return ANSWER_YES; + } + + if ( mySentry && me->IsRangeLessThan( mySentry, 100.0f ) ) + { + // focus on keeping our sentry alive + return ANSWER_NO; + } + + return ANSWER_UNDEFINED; +} diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.h new file mode 100644 index 0000000..2ff6265 --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build.h @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_build.h +// Engineer building his buildings +// Michael Booth, February 2009 + +#ifndef TF_BOT_ENGINEER_BUILD_H +#define TF_BOT_ENGINEER_BUILD_H + +class CTFBotHintTeleporterExit; + + +class CTFBotEngineerBuild : public Action< CTFBot > +{ +public: + virtual Action< CTFBot > *InitialContainedAction( CTFBot *me ); + + 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 > OnTerritoryLost( CTFBot *me, int territoryID ); + + virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry? + virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"? + + virtual const char *GetName( void ) const { return "EngineerBuild"; }; +}; + + +#endif // TF_BOT_ENGINEER_BUILD_H diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.cpp new file mode 100644 index 0000000..eabd9ba --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.cpp @@ -0,0 +1,212 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_build_dispenser.cpp +// Engineer building his Dispenser near his Sentry +// Michael Booth, May 2010 + +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_obj.h" +#include "tf_obj_sentrygun.h" +#include "tf_obj_dispenser.h" +#include "tf_gamerules.h" +#include "tf_weapon_builder.h" +#include "bot/tf_bot.h" +#include "bot/behavior/engineer/tf_bot_engineer_build_dispenser.h" +#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h" +#include "bot/behavior/tf_bot_get_ammo.h" + + +extern ConVar tf_bot_path_lookahead_range; + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuildDispenser::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_placementTriesLeft = 3; + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +class PressFireButtonIfValidBuildPositionReply : public INextBotReply +{ +public: + PressFireButtonIfValidBuildPositionReply( void ) + { + m_builder = NULL; + } + + void SetBuilder( CTFWeaponBuilder *builder ) + { + m_builder = builder; + } + + // invoked when process completed successfully + virtual void OnSuccess( INextBot *bot ) + { + if ( m_builder != NULL && m_builder->IsValidPlacement() ) + { + INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() ); + if ( playerInput ) + { + playerInput->PressFireButton(); + } + } + } + + CTFWeaponBuilder *m_builder; +}; + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuildDispenser::Update( CTFBot *me, float interval ) +{ + if ( me->GetTimeSinceLastInjury() < 1.0f ) + { + return Done( "Ouch! I'm under attack" ); + } + + CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN ); + if ( !mySentry ) + { + return Done( "No Sentry" ); + } + + if ( mySentry->GetTimeSinceLastInjury() < 1.0f || mySentry->GetHealth() < mySentry->GetMaxHealth() ) + { + return Done( "Need to repair my Sentry" ); + } + + CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER ); + if ( myDispenser ) + { + return Done( "Dispenser built" ); + } + + if ( m_placementTriesLeft <= 0 ) + { + return Done( "Can't find a place to build a Dispenser" ); + } + + if ( me->CanBuild( OBJ_DISPENSER ) == CB_NEED_RESOURCES ) + { + if ( m_getAmmoTimer.IsElapsed() && CTFBotGetAmmo::IsPossible( me ) ) + { + // need more metal - get some + m_getAmmoTimer.Start( 1.0f ); + return SuspendFor( new CTFBotGetAmmo, "Need more metal to build" ); + } +/* + else + { + // work on my sentry while I wait for ammo to show up + me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on sentry while I wait for ammo to show up" ); + me->PressFireButton(); + return Continue(); + } +*/ + } + + +/* + // if my sentry is under attack, forgo building a dispenser - focus on keeping the sentry alive + if ( mySentry->GetTimeSinceLastInjury() < 1.0f ) + { + CBaseCombatWeapon *wrench = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE ); + if ( wrench ) + { + me->Weapon_Switch( wrench ); + } + + me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Focusing on keeping my besieged sentry alive" ); + me->PressFireButton(); + + return Continue(); + } +*/ + + + // move behind the Sentry (our chosen build location) + Vector buildSpot = mySentry->GetAbsOrigin() - 75.0f * mySentry->BodyDirection2D(); + + // the ground might be steeply sloped (ie: stairs), so find the actual ground + buildSpot.z += HumanHeight; + TheNavMesh->GetSimpleGroundHeight( buildSpot, &buildSpot.z ); + + if ( me->IsDistanceBetweenLessThan( buildSpot, 100.0f ) ) + { + // crouch as we get close so we slow down and hit our mark + me->PressCrouchButton(); + } + + // if too far away from our build location, move closer + if ( me->IsDistanceBetweenGreaterThan( buildSpot, 25.0f ) ) + { + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_path.Compute( me, buildSpot, cost ); + } + + m_path.Update( me ); + + return Continue(); + } + + // we're at our build spot behind our sentry now - build a Dispenser + CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( me->GetActiveTFWeapon() ); + if ( !builder || builder->GetType() != OBJ_DISPENSER || builder->m_hObjectBeingBuilt == NULL ) + { + // at home position, build the object + me->StartBuildingObjectOfType( OBJ_DISPENSER ); + } + else if ( m_searchTimer.IsElapsed() ) + { + // rotate to find valid spot + Vector toSentry = mySentry->GetAbsOrigin() - me->GetAbsOrigin(); + toSentry.NormalizeInPlace(); + + Vector forward = -toSentry; + + float angle = RandomFloat( -M_PI/2.0f, M_PI/2.0f ); + float s, c; + FastSinCos( angle, &s, &c ); + + forward.x = toSentry.x * c - toSentry.y * s; + forward.y = toSentry.x * s + toSentry.y * c; + forward.z = 0.0f; + + static PressFireButtonIfValidBuildPositionReply buildReply; + + buildReply.SetBuilder( builder ); + me->GetBodyInterface()->AimHeadTowards( me->EyePosition() - 100.0f * forward, IBody::CRITICAL, 1.0f, &buildReply, "Trying to place my dispenser" ); + + m_searchTimer.Start( 1.0f ); + + --m_placementTriesLeft; + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +void CTFBotEngineerBuildDispenser::OnEnd( CTFBot *me, Action< CTFBot > *nextAction ) +{ + me->GetBodyInterface()->ClearPendingAimReply(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuildDispenser::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + m_path.Invalidate(); + m_repathTimer.Invalidate(); + me->GetBodyInterface()->ClearPendingAimReply(); + + return Continue(); +} + diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.h new file mode 100644 index 0000000..12e5dbf --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_dispenser.h @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_build_dispenser.h +// Engineer building his Dispenser near his Sentry +// Michael Booth, May 2010 + +#ifndef TF_BOT_ENGINEER_BUILD_DISPENSER_H +#define TF_BOT_ENGINEER_BUILD_DISPENSER_H + + +class CTFBotEngineerBuildDispenser : 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 ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ); + + virtual const char *GetName( void ) const { return "EngineerBuildDispenser"; }; + +private: + CountdownTimer m_searchTimer; + CountdownTimer m_getAmmoTimer; + CountdownTimer m_repathTimer; + + int m_placementTriesLeft; + PathFollower m_path; +}; + + +#endif // TF_BOT_ENGINEER_BUILD_DISPENSER_H diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.cpp new file mode 100644 index 0000000..b1beb35 --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.cpp @@ -0,0 +1,194 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_build_sentrygun.cpp +// Engineer building his Sentry gun +// Michael Booth, May 2010 + +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_obj.h" +#include "tf_obj_sentrygun.h" +#include "tf_obj_dispenser.h" +#include "tf_gamerules.h" +#include "tf_weapon_builder.h" +#include "bot/tf_bot.h" +#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h" +#include "bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h" +#include "bot/behavior/tf_bot_get_ammo.h" +#include "bot/map_entities/tf_bot_hint_sentrygun.h" + +extern ConVar tf_bot_path_lookahead_range; + + +//--------------------------------------------------------------------------------------------- +CTFBotEngineerBuildSentryGun::CTFBotEngineerBuildSentryGun( void ) +{ + m_sentryBuildHint = NULL; +} + + +//--------------------------------------------------------------------------------------------- +CTFBotEngineerBuildSentryGun::CTFBotEngineerBuildSentryGun( CTFBotHintSentrygun *sentryBuildHint ) +{ + m_sentryBuildHint = sentryBuildHint; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuildSentryGun::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_sentryTriesLeft = 5; + m_giveUpTimer.Invalidate(); + + m_searchTimer.Invalidate(); + m_wanderWay = 1; + m_needToAimSentry = true; + + m_sentryBuildLocation = ( m_sentryBuildHint == NULL ) ? me->GetAbsOrigin() : m_sentryBuildHint->GetAbsOrigin(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuildSentryGun::Update( CTFBot *me, float interval ) +{ + if ( me->GetTimeSinceLastInjury() < 1.0f ) + { + return Done( "Ouch! I'm under attack" ); + } + + CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN ); + if ( mySentry ) + { + return Done( "Sentry built" ); + } + + // collect metal as we move to our build location + if ( me->CanBuild( OBJ_SENTRYGUN ) == CB_NEED_RESOURCES ) + { + if ( m_getAmmoTimer.IsElapsed() && CTFBotGetAmmo::IsPossible( me ) ) + { + // need more metal - get some + m_getAmmoTimer.Start( 1.0f ); + return SuspendFor( new CTFBotGetAmmo, "Need more metal to build my Sentry" ); + } + } + + // various interruptions could mean we're away from our build location - move to it + if ( me->IsRangeGreaterThan( m_sentryBuildLocation, 25.0f ) ) + { + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_path.Compute( me, m_sentryBuildLocation, cost ); + } + + m_path.Update( me ); + + if ( !m_path.IsValid() ) + { + return Done( "Path failed" ); + } + + return Continue(); + } + + // we are at our build location + if ( m_sentryTriesLeft <= 0 ) + { + // couldn't build here + return Done( "Couldn't find a place to build" ); + } + + // attempt to build a Sentry here + if ( m_sentryBuildHint != NULL ) + { + // directly create a sentry gun at the precise position and orientation desired + CObjectSentrygun *mySentry = (CObjectSentrygun *)CreateEntityByName( "obj_sentrygun" ); + if ( mySentry ) + { + m_sentryBuildHint->IncrementUseCount(); + + mySentry->SetAbsOrigin( m_sentryBuildHint->GetAbsOrigin() ); + mySentry->SetAbsAngles( QAngle( 0, m_sentryBuildHint->GetAbsAngles().y, 0 ) ); + mySentry->Spawn(); + + mySentry->StartPlacement( me ); + mySentry->StartBuilding( me ); + } + } + else + { + // no precise build location - go through the normal build process + + CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( me->GetActiveTFWeapon() ); + if ( !builder || builder->GetType() != OBJ_SENTRYGUN || builder->m_hObjectBeingBuilt == NULL ) + { + // at home position, build a sentry (switch to the sentry builder "gun") + me->StartBuildingObjectOfType( OBJ_SENTRYGUN ); + return Continue(); + } + + // orient sentry towards where enemies enter this region + if ( m_needToAimSentry ) + { + CTFNavArea *myArea = (CTFNavArea *)me->GetLastKnownArea(); + if ( myArea ) + { + CUtlVector< CTFNavArea * > invasionVector; + myArea->GetEnemyInvasionAreaVector( me->GetTeamNumber() ); + + if ( invasionVector.Count() > 0 ) + { + // orient sentry towards where enemies enter this region + int which = RandomInt( 0, invasionVector.Count()-1 ); + me->GetBodyInterface()->AimHeadTowards( invasionVector[ which ]->GetCenter(), IBody::CRITICAL, 1.0f, NULL, "Sentry build orientation" ); + m_needToAimSentry = false; + } + } + } + + if ( me->GetBodyInterface()->IsHeadSteady() ) + { + if ( builder->IsValidPlacement() ) + { + // build the sentry + me->PressFireButton(); + } + else + { + // move around a bit to find valid spot + if ( m_searchTimer.IsElapsed() ) + { + m_wanderWay = RandomInt( 0, 3 ); + m_needToAimSentry = true; + m_searchTimer.Start( RandomFloat( 0.1f, 0.25f ) ); + --m_sentryTriesLeft; + } + + switch( m_wanderWay ) + { + case 0: me->PressForwardButton(); break; + case 1: me->PressBackwardButton(); break; + case 2: me->PressRightButton(); break; + case 3: me->PressLeftButton(); break; + } + } + } + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuildSentryGun::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + m_path.Invalidate(); + m_repathTimer.Invalidate(); + + return Continue(); +} diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h new file mode 100644 index 0000000..774b7ca --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h @@ -0,0 +1,44 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_build_sentrygun.h +// Engineer building his Sentry gun +// Michael Booth, May 2010 + +#ifndef TF_BOT_ENGINEER_BUILD_SENTRYGUN_H +#define TF_BOT_ENGINEER_BUILD_SENTRYGUN_H + +class CTFBotHintSentrygun; + + +class CTFBotEngineerBuildSentryGun : public Action< CTFBot > +{ +public: + CTFBotEngineerBuildSentryGun( void ); + CTFBotEngineerBuildSentryGun( CTFBotHintSentrygun *sentryBuildHint ); + + 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 "EngineerBuildSentryGun"; }; + +private: + CountdownTimer m_searchTimer; + CountdownTimer m_giveUpTimer; + CountdownTimer m_getAmmoTimer; + CountdownTimer m_repathTimer; + CountdownTimer m_buildTeleporterExitTimer; + + int m_sentryTriesLeft; + PathFollower m_path; + + CTFBotHintSentrygun *m_sentryBuildHint; + Vector m_sentryBuildLocation; + + int m_wanderWay; + bool m_needToAimSentry; + Vector m_sentryBuildAimTarget; +}; + + +#endif // TF_BOT_ENGINEER_BUILD_SENTRYGUN_H diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.cpp new file mode 100644 index 0000000..c6924aa --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.cpp @@ -0,0 +1,112 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_build_teleport_entrance.cpp +// Engineer building a teleport entrance right outside of the spawn room +// Michael Booth, May 2009 + +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_obj.h" +#include "tf_weapon_builder.h" +#include "bot/tf_bot.h" +#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h" +#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h" +#include "bot/behavior/tf_bot_get_ammo.h" + +extern ConVar tf_bot_path_lookahead_range; + +ConVar tf_bot_max_teleport_entrance_travel( "tf_bot_max_teleport_entrance_travel", "1500", FCVAR_CHEAT, "Don't plant teleport entrances farther than this travel distance from our spawn room" ); +ConVar tf_bot_teleport_build_surface_normal_limit( "tf_bot_teleport_build_surface_normal_limit", "0.99", FCVAR_CHEAT, "If the ground normal Z component is less that this value, Engineer bots won't place their entrance teleporter" ); + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuildTeleportEntrance::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuildTeleportEntrance::Update( CTFBot *me, float interval ) +{ + CTeamControlPoint *point = me->GetMyControlPoint(); + if ( !point ) + { + // wait until a control point becomes available + return Continue(); + } + + CTFNavArea *myArea = me->GetLastKnownArea(); + + if ( !myArea ) + { + return Done( "No nav mesh!" ); + } + + if ( myArea->GetIncursionDistance( me->GetTeamNumber() ) > tf_bot_max_teleport_entrance_travel.GetFloat() ) + { + return ChangeTo( new CTFBotEngineerMoveToBuild, "Too far from our spawn room to build teleporter entrance" ); + } + + // make sure we go back to our resupply cabinet after planting the teleporter entrance before we move on + if ( !me->IsAmmoFull() && CTFBotGetAmmo::IsPossible( me ) ) + { + return SuspendFor( new CTFBotGetAmmo, "Refilling ammo" ); + } + + CBaseObject *myTeleportEntrance = me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE ); + if ( myTeleportEntrance ) + { + // successfully built + return ChangeTo( new CTFBotEngineerMoveToBuild, "Teleport entrance built" ); + } + + // head towards the control point and build as soon as we can + if ( !m_path.IsValid() ) + { + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_path.Compute( me, point->GetAbsOrigin(), cost ); + } + + m_path.Update( me ); + + // build + CTFWeaponBase *myGun = me->GetActiveTFWeapon(); + if ( myGun ) + { + CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( myGun ); + if ( builder ) + { + // don't build on slopes - causes player blockages + Vector forward; + me->EyeVectors( &forward ); + + const float placementRange = 30.0f; + forward *= placementRange; + + trace_t result; + UTIL_TraceLine( me->WorldSpaceCenter() + Vector( forward.x, forward.y, 0.0f ), me->WorldSpaceCenter() + Vector( forward.x, forward.y, -200.0f ), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result ); + + if ( builder->IsValidPlacement() && result.DidHit() && result.plane.normal.z > tf_bot_teleport_build_surface_normal_limit.GetFloat() ) + { + // place it down + me->PressFireButton(); + } + } + else + { + // switch to teleporter builder + me->StartBuildingObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE ); + } + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotEngineerBuildTeleportEntrance::OnStuck( CTFBot *me ) +{ + m_path.Invalidate(); + + return TryContinue(); +} diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h new file mode 100644 index 0000000..cf59418 --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_entrance.h @@ -0,0 +1,23 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_build_teleport_entrance.h +// Engineer building a teleport entrance right outside of the spawn room +// Michael Booth, May 2009 + +#ifndef TF_BOT_ENGINEER_BUILD_TELEPORT_ENTRANCE_H +#define TF_BOT_ENGINEER_BUILD_TELEPORT_ENTRANCE_H + +class CTFBotEngineerBuildTeleportEntrance : 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 const char *GetName( void ) const { return "EngineerBuildTeleportEntrance"; }; + +private: + PathFollower m_path; +}; + +#endif // TF_BOT_ENGINEER_BUILD_TELEPORT_ENTRANCE_H diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.cpp new file mode 100644 index 0000000..4c621a5 --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.cpp @@ -0,0 +1,190 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_build_teleport_exit.cpp +// Engineer building a teleport exit +// Michael Booth, May 2010 + +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_obj.h" +#include "tf_obj_sentrygun.h" +#include "tf_weapon_builder.h" +#include "bot/tf_bot.h" +#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h" +#include "bot/behavior/tf_bot_get_ammo.h" + +extern ConVar tf_bot_path_lookahead_range; + + +//--------------------------------------------------------------------------------------------- +CTFBotEngineerBuildTeleportExit::CTFBotEngineerBuildTeleportExit( void ) +{ + m_hasPreciseBuildLocation = false; +} + + +//--------------------------------------------------------------------------------------------- +CTFBotEngineerBuildTeleportExit::CTFBotEngineerBuildTeleportExit( const Vector &buildLocation, float buildAngle ) +{ + m_hasPreciseBuildLocation = true; + m_buildLocation = buildLocation; + m_buildAngle = buildAngle; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuildTeleportExit::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + if ( !m_hasPreciseBuildLocation ) + { + // if no specific build location given, just build right where we are + m_buildLocation = me->GetAbsOrigin(); + } + + m_giveUpTimer.Start( 3.1f ); + m_path.Invalidate(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuildTeleportExit::Update( CTFBot *me, float interval ) +{ + if ( me->GetTimeSinceLastInjury() < 1.0f ) + { + return Done( "Ouch! I'm under attack" ); + } + + CBaseObject *myTeleportEntrance = me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT ); + if ( myTeleportEntrance ) + { + // successfully built + return Done( "Teleport exit built" ); + } + + + // collect metal as we move to our build location + if ( me->CanBuild( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT ) == CB_NEED_RESOURCES ) + { + if ( m_getAmmoTimer.IsElapsed() && CTFBotGetAmmo::IsPossible( me ) ) + { + // need more metal - get some + m_getAmmoTimer.Start( 1.0f ); + return SuspendFor( new CTFBotGetAmmo, "Need more metal to build my Teleporter Exit" ); + } + } + + // move near our build position + const float buildRange = 50.0f; + if ( me->IsRangeGreaterThan( m_buildLocation, buildRange ) ) + { + // move into position + if ( !m_path.IsValid() || m_repathTimer.IsElapsed() ) + { + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_path.Compute( me, m_buildLocation, cost ); + + m_repathTimer.Start( RandomFloat( 2.0f, 3.0f ) ); + } + + m_path.Update( me ); + + // don't give up until we've reached our build location + m_giveUpTimer.Reset(); + + return Continue(); + } + + // in position to build + if ( m_giveUpTimer.IsElapsed() ) + { + return Done( "Taking too long - giving up" ); + } + + if ( m_hasPreciseBuildLocation ) + { + me->GetBodyInterface()->AimHeadTowards( m_buildLocation, IBody::CRITICAL, 1.0f, NULL, "Looking toward my precise build location" ); + + // directly create a teleporter exit at the precise position and orientation desired + CObjectTeleporter *myTeleporterExit = (CObjectTeleporter *)CreateEntityByName( "obj_teleporter" ); + if ( myTeleporterExit ) + { + myTeleporterExit->SetObjectMode( MODE_TELEPORTER_EXIT ); + myTeleporterExit->SetAbsOrigin( m_buildLocation ); + myTeleporterExit->SetAbsAngles( QAngle( 0, m_buildAngle, 0 ) ); + myTeleporterExit->Spawn(); + + myTeleporterExit->StartPlacement( me ); + myTeleporterExit->StartBuilding( me ); + myTeleporterExit->SetBuilder( me ); + + // teleporter exits are solid blockers - put engineer on top of exit or he'll be stuck + Vector myNewOrigin = me->GetAbsOrigin(); + myNewOrigin.z += me->GetLocomotionInterface()->GetStepHeight(); + + me->SetAbsOrigin( myNewOrigin ); + + return Done( "Teleport exit built at precise location" ); + } + + return Continue(); + } + + + // build exit roughly at this spot + CTFWeaponBase *myGun = me->GetActiveTFWeapon(); + if ( myGun ) + { + CTFWeaponBuilder *builder = dynamic_cast< CTFWeaponBuilder * >( myGun ); + if ( builder ) + { + if ( builder->IsValidPlacement() ) + { + // place it down + me->PressFireButton(); + } + else if ( m_searchTimer.IsElapsed() ) + { + // rotate to find valid spot + Vector forward; + float angle = RandomFloat( -M_PI, M_PI ); + FastSinCos( angle, &forward.y, &forward.x ); + + forward.z = 0.0f; + + me->GetBodyInterface()->AimHeadTowards( me->EyePosition() - 100.0f * forward, IBody::CRITICAL, 1.0f, NULL, "Trying to place my teleport exit" ); + + m_searchTimer.Start( 1.0f ); + } + } + else + { + // switch to teleporter builder + me->StartBuildingObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT ); + } + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuildTeleportExit::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + m_giveUpTimer.Reset(); + m_path.Invalidate(); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotEngineerBuildTeleportExit::OnStuck( CTFBot *me ) +{ + m_path.Invalidate(); + + return TryContinue(); +} + + diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h new file mode 100644 index 0000000..d64f34a --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h @@ -0,0 +1,36 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_build_teleport_exit.h +// Engineer building a teleport exit +// Michael Booth, May 2010 + +#ifndef TF_BOT_ENGINEER_BUILD_TELEPORT_EXIT_H +#define TF_BOT_ENGINEER_BUILD_TELEPORT_EXIT_H + +class CTFBotEngineerBuildTeleportExit : public Action< CTFBot > +{ +public: + CTFBotEngineerBuildTeleportExit( void ); + CTFBotEngineerBuildTeleportExit( const Vector &buildLocation, float buildAngle ); + + 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 const char *GetName( void ) const { return "EngineerBuildTeleportExit"; }; + +private: + PathFollower m_path; + + bool m_hasPreciseBuildLocation; + Vector m_buildLocation; + float m_buildAngle; + + CountdownTimer m_giveUpTimer; + CountdownTimer m_repathTimer; + CountdownTimer m_getAmmoTimer; + CountdownTimer m_searchTimer; +}; + +#endif // TF_BOT_ENGINEER_BUILD_TELEPORT_EXIT_H diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.cpp new file mode 100644 index 0000000..a0f84c0 --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.cpp @@ -0,0 +1,497 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_building.cpp +// At building location, constructing buildings +// Michael Booth, May 2010 + +#include "cbase.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "tf_obj.h" +#include "tf_obj_sentrygun.h" +#include "tf_obj_dispenser.h" +#include "tf_gamerules.h" +#include "tf_weapon_builder.h" +#include "team_train_watcher.h" +#include "bot/tf_bot.h" +#include "bot/behavior/engineer/tf_bot_engineer_building.h" +#include "bot/behavior/engineer/tf_bot_engineer_move_to_build.h" +#include "bot/behavior/engineer/tf_bot_engineer_build_teleport_exit.h" +#include "bot/behavior/engineer/tf_bot_engineer_build_sentrygun.h" +#include "bot/behavior/engineer/tf_bot_engineer_build_dispenser.h" +#include "bot/behavior/tf_bot_attack.h" +#include "bot/behavior/tf_bot_get_ammo.h" +#include "bot/map_entities/tf_bot_hint_teleporter_exit.h" +#include "bot/map_entities/tf_bot_hint_sentrygun.h" +#include "NextBotUtil.h" + + +ConVar tf_bot_engineer_retaliate_range( "tf_bot_engineer_retaliate_range", "750", FCVAR_CHEAT, "If attacker who destroyed sentry is closer than this, attack. Otherwise, retreat" ); +ConVar tf_bot_engineer_exit_near_sentry_range( "tf_bot_engineer_exit_near_sentry_range", "2500", FCVAR_CHEAT, "Maximum travel distance between a bot's Sentry gun and its Teleporter Exit" ); +ConVar tf_bot_engineer_max_sentry_travel_distance_to_point( "tf_bot_engineer_max_sentry_travel_distance_to_point", "2500", FCVAR_CHEAT, "Maximum travel distance between a bot's Sentry gun and the currently contested point" ); + +extern ConVar tf_bot_path_lookahead_range; + +const int MaxPlacementAttempts = 5; + + +//--------------------------------------------------------------------------------------------- +CTFBotEngineerBuilding::CTFBotEngineerBuilding( void ) +{ + m_sentryBuildHint = NULL; +} + + +//--------------------------------------------------------------------------------------------- +CTFBotEngineerBuilding::CTFBotEngineerBuilding( CTFBotHintSentrygun *sentryBuildHint ) +{ + m_sentryBuildHint = sentryBuildHint; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuilding::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_sentryTriesLeft = MaxPlacementAttempts; + + m_territoryRangeTimer.Invalidate(); + + m_hasBuiltSentry = false; + m_isSentryOutOfPosition = false; + m_nearbyMetalStatus = NEARBY_METAL_UNKNOWN; + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +// Everything is built, upgrade/maintain it +// TODO: Upgrade/maintain nearby friendly buildings, too. +void CTFBotEngineerBuilding::UpgradeAndMaintainBuildings( CTFBot *me ) +{ + CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN ); + CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER ); + + if ( !mySentry ) + { + return; + } + + CBaseCombatWeapon *wrench = me->Weapon_GetSlot( TF_WPN_TYPE_MELEE ); + if ( wrench ) + { + me->Weapon_Switch( wrench ); + } + + const float tooFarRange = 75.0f; + + if ( !myDispenser ) + { + // just work on our sentry + float rangeToSentry = me->GetDistanceBetween( mySentry ); + + if ( rangeToSentry < 1.2f * tooFarRange ) + { + // crouch both for cover behind our buildings, but also to slow us down so we hit our move goal more accurately + me->PressCrouchButton(); + } + + if ( rangeToSentry > tooFarRange ) + { + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_path.Compute( me, mySentry->GetAbsOrigin(), cost ); + } + + m_path.Update( me ); + } + else + { + // we are in position - work on our buildings + me->StopLookingAroundForEnemies(); + me->GetBodyInterface()->AimHeadTowards( mySentry->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my Sentry" ); + me->PressFireButton(); + } + + return; + } + + // sit near both buildings + Vector betweenMyBuildings = ( mySentry->GetAbsOrigin() + myDispenser->GetAbsOrigin() ) / 2.0f; + + // try to equalize distance between both + float rangeToSentry = me->GetDistanceBetween( mySentry ); + float rangeToDispenser = me->GetDistanceBetween( myDispenser ); + + const float equalTolerance = 25.0f; + + if ( rangeToSentry < 1.2f * tooFarRange && rangeToDispenser < 1.2f * tooFarRange ) + { + // crouch both for cover behind our buildings, but also to slow us down so we hit our move goal more accurately + me->PressCrouchButton(); + } + + if ( fabs( rangeToDispenser - rangeToSentry ) > equalTolerance || rangeToSentry > tooFarRange || rangeToDispenser > tooFarRange ) + { + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) ); + + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_path.Compute( me, betweenMyBuildings, cost ); + } + + m_path.Update( me ); + } + + if ( rangeToSentry < tooFarRange || rangeToDispenser < tooFarRange ) + { + // we are (nearly) in position - work on our buildings + m_searchTimer.Invalidate(); + + CBaseObject *workTarget = mySentry; + + if ( mySentry->HasSapper() || mySentry->IsPlasmaDisabled() ) + workTarget = mySentry; + else if ( myDispenser->HasSapper() || myDispenser->IsPlasmaDisabled() ) + workTarget = myDispenser; + else if ( mySentry->GetTimeSinceLastInjury() < 1.0f || mySentry->GetHealth() < mySentry->GetMaxHealth() ) + workTarget = mySentry; + else if ( mySentry->IsBuilding() ) + workTarget = mySentry; + else if ( myDispenser->IsBuilding() ) + workTarget = myDispenser; + else if ( mySentry->GetUpgradeLevel() < 3 ) + workTarget = mySentry; + else if ( myDispenser->GetHealth() < myDispenser->GetMaxHealth() ) + workTarget = myDispenser; + else if ( myDispenser->GetUpgradeLevel() < mySentry->GetUpgradeLevel() ) + workTarget = myDispenser; + + me->StopLookingAroundForEnemies(); + me->GetBodyInterface()->AimHeadTowards( workTarget->WorldSpaceCenter(), IBody::CRITICAL, 1.0f, NULL, "Work on my buildings" ); + me->PressFireButton(); + } +} + + +//--------------------------------------------------------------------------------------------- +bool CTFBotEngineerBuilding::IsMetalSourceNearby( CTFBot *me ) const +{ + CUtlVector< CNavArea * > nearbyVector; + CollectSurroundingAreas( &nearbyVector, me->GetLastKnownArea(), 2000.0f, me->GetLocomotionInterface()->GetStepHeight(), me->GetLocomotionInterface()->GetStepHeight() ); + + for( int i=0; i<nearbyVector.Count(); ++i ) + { + CTFNavArea *area = (CTFNavArea *)nearbyVector[i]; + if ( area->HasAttributeTF( TF_NAV_HAS_AMMO ) ) + { + return true; + } + + // this assumes all spawn rooms have resupply cabinets + if ( me->GetTeamNumber() == TF_TEAM_RED && area->HasAttributeTF( TF_NAV_SPAWN_ROOM_RED ) ) + { + return true; + } + + if ( me->GetTeamNumber() == TF_TEAM_BLUE && area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) ) + { + return true; + } + } + + return false; +} + + +//--------------------------------------------------------------------------------------------- +bool CTFBotEngineerBuilding::CheckIfSentryIsOutOfPosition( CTFBot *me ) const +{ + CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN ); + + if ( !mySentry ) + { + return false; + } + + // payload + if ( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT ) + { + CTeamTrainWatcher *trainWatcher; + + if ( me->GetTeamNumber() == TF_TEAM_BLUE ) + { + trainWatcher = TFGameRules()->GetPayloadToPush( me->GetTeamNumber() ); + } + else + { + trainWatcher = TFGameRules()->GetPayloadToBlock( me->GetTeamNumber() ); + } + + if ( trainWatcher ) + { + float sentryDistanceAlongPath; + trainWatcher->ProjectPointOntoPath( mySentry->GetAbsOrigin(), NULL, &sentryDistanceAlongPath ); + + const float behindTrainTolerance = SENTRY_MAX_RANGE; + return ( trainWatcher->GetTrainDistanceAlongTrack() > sentryDistanceAlongPath + behindTrainTolerance ); + } + } + + // control points + mySentry->UpdateLastKnownArea(); + CNavArea *sentryArea = mySentry->GetLastKnownArea(); + + CTeamControlPoint *point = me->GetMyControlPoint(); + if ( point ) + { + CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( point->GetPointIndex() ); + + if ( sentryArea && pointArea ) + { + CTFBotPathCost cost( me, FASTEST_ROUTE ); + if ( NavAreaTravelDistance( sentryArea, pointArea, cost, tf_bot_engineer_max_sentry_travel_distance_to_point.GetFloat() ) < 0 && + NavAreaTravelDistance( pointArea, sentryArea, cost, tf_bot_engineer_max_sentry_travel_distance_to_point.GetFloat() ) < 0 ) + { + return true; + } + } + } + + return false; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuilding::Update( CTFBot *me, float interval ) +{ + CObjectSentrygun *mySentry = (CObjectSentrygun *)me->GetObjectOfType( OBJ_SENTRYGUN ); + CObjectDispenser *myDispenser = (CObjectDispenser *)me->GetObjectOfType( OBJ_DISPENSER ); + CObjectTeleporter *myTeleportEntrance = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_ENTRANCE ); + CObjectTeleporter *myTeleportExit = (CObjectTeleporter *)me->GetObjectOfType( OBJ_TELEPORTER, MODE_TELEPORTER_EXIT ); + + bool isUnderAttack = ( me->GetTimeSinceLastInjury() < 1.0f ); + isUnderAttack |= ( mySentry && ( mySentry->HasSapper() || mySentry->IsPlasmaDisabled() ) ); + isUnderAttack |= ( myDispenser && ( myDispenser->HasSapper() || myDispenser->IsPlasmaDisabled() ) ); + + me->StartLookingAroundForEnemies(); + + // try to build a Sentry + if ( !mySentry ) + { + m_nearbyMetalStatus = NEARBY_METAL_UNKNOWN; + + // react to nearby threats if our sentry is down + const CKnownEntity *threat = me->GetVisionInterface()->GetPrimaryKnownThreat(); + if ( threat && threat->IsVisibleRecently() ) + { + me->EquipBestWeaponForThreat( threat ); + } + + if ( !m_hasBuiltSentry && m_sentryTriesLeft > 0 ) + { + --m_sentryTriesLeft; + + if ( m_sentryBuildHint ) + { + return SuspendFor( new CTFBotEngineerBuildSentryGun( m_sentryBuildHint ), "Building a Sentry at a hint location" ); + } + + return SuspendFor( new CTFBotEngineerBuildSentryGun, "Building a Sentry" ); + } + else + { + // can't build a Sentry here - pick a new place + return ChangeTo( new CTFBotEngineerMoveToBuild, "Couldn't find a place to build" ); + } + } + + // I have a Sentry + m_hasBuiltSentry = true; + + if ( m_sentryBuildHint != NULL && !m_sentryBuildHint->IsEnabled() ) + { + // our hint has been disabled and no longer has influence on our behavior + m_sentryBuildHint = NULL; + } + + // periodically check that our Sentry is still near the contested point + if ( m_sentryBuildHint == NULL || !m_sentryBuildHint->IsSticky() ) + { + if ( !m_isSentryOutOfPosition && m_territoryRangeTimer.IsElapsed() ) + { + m_territoryRangeTimer.Start( RandomFloat( 3.0f, 5.0f ) ); + + m_isSentryOutOfPosition = CheckIfSentryIsOutOfPosition( me ); + } + + m_isSentryOutOfPosition = false; + + if ( m_isSentryOutOfPosition ) + { + // the point has moved, only keep sentry as long as it keeps attacking + if ( mySentry->GetTimeSinceLastFired() > 10.0f ) + { + mySentry->DetonateObject(); + + // if we built here because of a hint, disable that hint so we don't use it and rebuild here again + if ( m_sentryBuildHint != NULL ) + { + inputdata_t dummy; + m_sentryBuildHint->InputDisable( dummy ); + + m_sentryBuildHint = NULL; + } + + if ( myDispenser ) + { + myDispenser->DetonateObject(); + } + + if ( myTeleportExit ) + { + myTeleportExit->DetonateObject(); + } + + me->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_MOVEUP ); + + return ChangeTo( new CTFBotEngineerMoveToBuild, "Need to move my gear closer to the point!" ); + } + } + } + + // if my dispenser is too far away from my sentry, destroy and rebuild it next update + // @TODO: Flag hint-built entities for a larger range + const float maxSeparation = 500.0f; + if ( myDispenser ) + { + if ( ( mySentry->GetAbsOrigin() - myDispenser->GetAbsOrigin() ).IsLengthGreaterThan( maxSeparation ) ) + { + myDispenser->DestroyObject(); + myDispenser = NULL; + } + } + + // build up the sentry all the way if there is a metal source nearby + if ( mySentry->GetUpgradeLevel() < 3 ) + { + if ( m_nearbyMetalStatus == NEARBY_METAL_UNKNOWN ) + { + m_nearbyMetalStatus = IsMetalSourceNearby( me ) ? NEARBY_METAL_EXISTS : NEARBY_METAL_NONE; + } + + if ( m_nearbyMetalStatus == NEARBY_METAL_EXISTS ) + { + UpgradeAndMaintainBuildings( me ); + return Continue(); + } + } + +/* + if ( myTeleportExit ) + { + // if my teleporter exit is too far away from my sentry, destroy and rebuild it next update + if ( ( mySentry->GetAbsOrigin() - myTeleportExit->GetAbsOrigin() ).IsLengthGreaterThan( maxSeparation ) ) + { + myTeleportExit->DestroyObject(); + myTeleportExit = NULL; + } + } +*/ + + // try to build a Dispenser (build after tele exit in training) + if ( !TFGameRules()->IsInTraining() || myTeleportExit ) + { + const float dispenserRebuildInterval = 10.0f; + if ( myDispenser ) + { + // don't rebuild immediately after building is destroyed + m_dispenserRetryTimer.Start( dispenserRebuildInterval ); + } + else if ( m_dispenserRetryTimer.IsElapsed() && !isUnderAttack ) + { + m_dispenserRetryTimer.Start( dispenserRebuildInterval ); + + return SuspendFor( new CTFBotEngineerBuildDispenser, "Building a Dispenser" ); + } + } + + // try to build a Teleporter Exit + const float exitRebuildInterval = TFGameRules()->IsInTraining() ? 5.0f : 30.0f; + if ( myTeleportExit ) + { + // don't rebuild immediately after building is destroyed + m_teleportExitRetryTimer.Start( exitRebuildInterval ); + } + else if ( m_teleportExitRetryTimer.IsElapsed() && myTeleportEntrance && !isUnderAttack ) + { + m_teleportExitRetryTimer.Start( exitRebuildInterval ); + + // we need to build a teleporter exit yet + if ( m_sentryBuildHint != NULL ) + { + // if there are teleporter exit hints, find the closest one to our sentry and use it + CUtlVector< CBaseEntity * > hintVector; + CTFBotHintTeleporterExit *hint = NULL; + while( ( hint = (CTFBotHintTeleporterExit *)gEntList.FindEntityByClassname( hint, "bot_hint_teleporter_exit" ) ) != NULL ) + { + if ( hint->IsEnabled() && hint->InSameTeam( me ) ) + { + hintVector.AddToTail( hint ); + } + } + + if ( hintVector.Count() > 0 ) + { + mySentry->UpdateLastKnownArea(); + CBaseEntity *closeHint = SelectClosestEntityByTravelDistance( me, hintVector, mySentry->GetLastKnownArea(), tf_bot_engineer_exit_near_sentry_range.GetFloat() ); + + if ( closeHint ) + { + return SuspendFor( new CTFBotEngineerBuildTeleportExit( closeHint->GetAbsOrigin(), closeHint->GetAbsAngles().y ), "Building teleporter exit at nearby hint" ); + } + } + } + else if ( me->IsRangeLessThan( mySentry, 300.0f ) ) + { + // drop a teleporter exit near our sentry + return SuspendFor( new CTFBotEngineerBuildTeleportExit(), "Building teleporter exit" ); + } + } + + // everything is built - maintain them + UpgradeAndMaintainBuildings( me ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +void CTFBotEngineerBuilding::OnEnd( CTFBot *me, Action< CTFBot > *nextAction ) +{ + me->StartLookingAroundForEnemies(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotEngineerBuilding::OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ) +{ + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotEngineerBuilding::OnTerritoryLost( CTFBot *me, int territoryID ) +{ + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotEngineerBuilding::OnTerritoryCaptured( CTFBot *me, int territoryID ) +{ + return TryContinue(); +} diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.h new file mode 100644 index 0000000..e5928ce --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_building.h @@ -0,0 +1,64 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_building.h +// At building location, constructing buildings +// Michael Booth, May 2010 + +#ifndef TF_BOT_ENGINEER_BUILDING_H +#define TF_BOT_ENGINEER_BUILDING_H + +class CTFBotHintSentrygun; + + +class CTFBotEngineerBuilding : public Action< CTFBot > +{ +public: + CTFBotEngineerBuilding( void ); + CTFBotEngineerBuilding( CTFBotHintSentrygun *sentryBuildHint ); + + 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 ActionResult< CTFBot > OnResume( CTFBot *me, Action< CTFBot > *interruptingAction ); + + virtual EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID ); + virtual EventDesiredResult< CTFBot > OnTerritoryCaptured( CTFBot *me, int territoryID ); + + virtual const char *GetName( void ) const { return "EngineerBuilding"; }; + +private: + CountdownTimer m_searchTimer; + CountdownTimer m_getAmmoTimer; + CountdownTimer m_repathTimer; + CountdownTimer m_buildTeleporterExitTimer; + + int m_sentryTriesLeft; + + CountdownTimer m_dispenserRetryTimer; + CountdownTimer m_teleportExitRetryTimer; + + PathFollower m_path; + + CHandle< CTFBotHintSentrygun > m_sentryBuildHint; + + bool m_hasBuiltSentry; + + enum NearbyMetalType + { + NEARBY_METAL_UNKNOWN, + NEARBY_METAL_NONE, + NEARBY_METAL_EXISTS + }; + + NearbyMetalType m_nearbyMetalStatus; + + CountdownTimer m_territoryRangeTimer; + bool m_isSentryOutOfPosition; + bool CheckIfSentryIsOutOfPosition( CTFBot *me ) const; + + void UpgradeAndMaintainBuildings( CTFBot *me ); + bool IsMetalSourceNearby( CTFBot *me ) const; +}; + + +#endif // TF_BOT_ENGINEER_BUILDING_H diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.cpp b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.cpp new file mode 100644 index 0000000..4ef28d6 --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.cpp @@ -0,0 +1,504 @@ +//========= 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(); +} diff --git a/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.h b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.h new file mode 100644 index 0000000..42f60c4 --- /dev/null +++ b/game/server/tf/bot/behavior/engineer/tf_bot_engineer_move_to_build.h @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_engineer_move_to_build.h +// Engineer moving into position to build +// Michael Booth, February 2009 + +#ifndef TF_BOT_ENGINEER_MOVE_TO_BUILD_H +#define TF_BOT_ENGINEER_MOVE_TO_BUILD_H + +#include "Path/NextBotPathFollow.h" + +class CTFBotHintSentrygun; + + +class CTFBotEngineerMoveToBuild : 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 EventDesiredResult< CTFBot > OnTerritoryLost( CTFBot *me, int territoryID ); + + virtual const char *GetName( void ) const { return "EngineerMoveToBuild"; }; + +private: + CHandle< CTFBotHintSentrygun > m_sentryBuildHint; + Vector m_sentryBuildLocation; + + PathFollower m_path; + CountdownTimer m_repathTimer; + + CUtlVector< CTFNavArea * > m_sentryAreaVector; + float m_totalSurfaceArea; + void CollectBuildAreas( CTFBot *me ); + + void SelectBuildLocation( CTFBot *me ); + CountdownTimer m_fallBackTimer; +}; + +#endif // TF_BOT_ENGINEER_MOVE_TO_BUILD_H |