diff options
Diffstat (limited to 'game/server/tf/bot/behavior/engineer/mvm_engineer')
8 files changed, 1124 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 |