diff options
Diffstat (limited to 'game/server/tf/bot/behavior/missions')
6 files changed, 1013 insertions, 0 deletions
diff --git a/game/server/tf/bot/behavior/missions/tf_bot_mission_destroy_sentries.cpp b/game/server/tf/bot/behavior/missions/tf_bot_mission_destroy_sentries.cpp new file mode 100644 index 0000000..8f5c8c4 --- /dev/null +++ b/game/server/tf/bot/behavior/missions/tf_bot_mission_destroy_sentries.cpp @@ -0,0 +1,104 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_mission_destroy_sentries.cpp +// Seek and destroy enemy sentries and ignore everything else +// Michael Booth, June 2011 + +#include "cbase.h" +#include "team.h" +#include "bot/tf_bot.h" +#include "bot/behavior/missions/tf_bot_mission_destroy_sentries.h" +#include "bot/behavior/spy/tf_bot_spy_sap.h" +#include "bot/behavior/tf_bot_destroy_enemy_sentry.h" +#include "bot/behavior/medic/tf_bot_medic_heal.h" +#include "bot/behavior/missions/tf_bot_mission_suicide_bomber.h" +#include "tf_obj_sentrygun.h" + +// +// NOTE: This behavior is deprecated and unused for now. +// The only sentry destroying mission is the Sentry Buster right now (suicide bomber). +// + +//--------------------------------------------------------------------------------------------- +CTFBotMissionDestroySentries::CTFBotMissionDestroySentries( CObjectSentrygun *goalSentry ) +{ + m_goalSentry = goalSentry; +} + + +//--------------------------------------------------------------------------------------------- +CObjectSentrygun *CTFBotMissionDestroySentries::SelectSentryTarget( CTFBot *me ) +{ + + return NULL; +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMissionDestroySentries::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + if ( me->IsPlayerClass( TF_CLASS_MEDIC ) ) + { + return ChangeTo( new CTFBotMedicHeal, "My job is to heal/uber the others in the mission" ); + } + + // focus only on the mission + me->SetAttribute( CTFBot::IGNORE_ENEMIES ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMissionDestroySentries::Update( CTFBot *me, float interval ) +{ + if ( m_goalSentry == NULL ) + { + // first destroy the sentry we were assigned to, or any sentry we discovered or that is attacking us + m_goalSentry = me->GetEnemySentry(); + + if ( m_goalSentry == NULL ) + { + // next destroy the most dangerous sentry + int iTeam = ( me->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED; + + if ( TFGameRules() && TFGameRules()->IsPVEModeActive() ) + { + iTeam = TF_TEAM_PVE_DEFENDERS; + } + + m_goalSentry = TFGameRules()->FindSentryGunWithMostKills( iTeam ); + } + } + + // for suicide bombers, we never want them to revert to normal behavior even if there is no sentry to kill + if ( me->IsPlayerClass( TF_CLASS_DEMOMAN ) ) + { + return SuspendFor( new CTFBotMissionSuicideBomber, "On a suicide mission to blow up a sentry" ); + } + + if ( m_goalSentry == NULL ) + { + // no sentries left to destroy - our mission is complete + me->SetMission( CTFBot::NO_MISSION, MISSION_DOESNT_RESET_BEHAVIOR_SYSTEM ); + return ChangeTo( GetParentAction()->InitialContainedAction( me ), "Mission complete - reverting to normal behavior" ); + } + + if ( m_goalSentry != me->GetEnemySentry() ) + { + me->RememberEnemySentry( m_goalSentry, m_goalSentry->WorldSpaceCenter() ); + } + + if ( me->IsPlayerClass( TF_CLASS_SPY ) ) + { + return SuspendFor( new CTFBotSpySap( m_goalSentry ), "On a mission to sap a sentry" ); + } + + return SuspendFor( new CTFBotDestroyEnemySentry, "On a mission to destroy a sentry" ); +} + + +//--------------------------------------------------------------------------------------------- +void CTFBotMissionDestroySentries::OnEnd( CTFBot *me, Action< CTFBot > *nextAction ) +{ + me->ClearAttribute( CTFBot::IGNORE_ENEMIES ); +} diff --git a/game/server/tf/bot/behavior/missions/tf_bot_mission_destroy_sentries.h b/game/server/tf/bot/behavior/missions/tf_bot_mission_destroy_sentries.h new file mode 100644 index 0000000..ad07721 --- /dev/null +++ b/game/server/tf/bot/behavior/missions/tf_bot_mission_destroy_sentries.h @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_mission_destroy_sentries.h +// Seek and destroy enemy sentries and ignore everything else +// Michael Booth, June 2011 + +#ifndef TF_BOT_MISSION_DESTROY_SENTRIES_H +#define TF_BOT_MISSION_DESTROY_SENTRIES_H + + +//----------------------------------------------------------------------------- +class CTFBotMissionDestroySentries : public Action< CTFBot > +{ +public: + CTFBotMissionDestroySentries( CObjectSentrygun *goalSentry = NULL ); + virtual ~CTFBotMissionDestroySentries() { } + + 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 "MissionDestroySentries"; }; + +private: + CHandle< CObjectSentrygun > m_goalSentry; + + CObjectSentrygun *SelectSentryTarget( CTFBot *me ); +}; + + +#endif // TF_BOT_MISSION_DESTROY_SENTRIES_H diff --git a/game/server/tf/bot/behavior/missions/tf_bot_mission_reprogrammed.cpp b/game/server/tf/bot/behavior/missions/tf_bot_mission_reprogrammed.cpp new file mode 100644 index 0000000..07fe474 --- /dev/null +++ b/game/server/tf/bot/behavior/missions/tf_bot_mission_reprogrammed.cpp @@ -0,0 +1,377 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_mission_reprogrammed.cpp +// Move to target and explode +// Michael Booth, October 2011 + +#include "cbase.h" +#include "tf_team.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "bot/tf_bot.h" +#include "bot/behavior/missions/tf_bot_mission_reprogrammed.h" +#include "particle_parse.h" +#include "tf_obj_sentrygun.h" + +#ifdef STAGING_ONLY + +extern ConVar tf_bot_path_lookahead_range; +extern ConVar tf_bot_suicide_bomb_range; + +ConVar tf_bot_reprogrammed_time( "tf_bot_reprogrammed_time", "8", FCVAR_CHEAT ); + +//--------------------------------------------------------------------------------------------- +CTFBotMissionReprogrammed::CTFBotMissionReprogrammed( void ) +{ +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMissionReprogrammed::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() ); + m_detonateTimer.Invalidate(); + m_detonateSeekTimer.Invalidate(); + m_reprogrammedTimer.Invalidate(); + m_hasDetonated = false; + m_consecutivePathFailures = 0; + m_wasSuccessful = false; + + if ( me->HasTheFlag() ) + me->DropFlag(); + me->SetFlagTarget( NULL ); + + m_victim = me->GetMissionTarget(); + + // Find nearest, accessible teammate + if ( !m_victim ) + { + CTFPlayer *pTarget = FindNearestEnemy( me ); + if ( pTarget ) + { + me->SetMissionTarget( pTarget ); + m_victim = pTarget; + } + } + + if ( m_victim ) + { + m_lastKnownVictimPosition = m_victim->GetAbsOrigin(); + } + + // We're guaranteed to stay alive for a period of time + me->SetHealth( 1 ); + me->m_takedamage = DAMAGE_NO; + + // Duration + m_reprogrammedTimer.Start( tf_bot_reprogrammed_time.GetFloat() ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMissionReprogrammed::Update( CTFBot *me, float interval ) +{ + bool bDetonate = false; + + // one we start detonating, there's no turning back + if ( m_detonateTimer.HasStarted() ) + { + if ( m_detonateTimer.IsElapsed() ) + { + m_vecDetLocation = me->GetAbsOrigin(); + Detonate( me ); + + return Done( "KABOOM!" ); + } + + return Continue(); + } + + // Find nearest, accessible teammate + if ( !m_victim || !m_victim->IsAlive() ) + { + m_victim.Set( NULL ); + + CTFPlayer *pTarget = FindNearestEnemy( me ); + if ( pTarget ) + { + me->SetMissionTarget( pTarget ); + m_victim = pTarget; + } + } + + if ( m_victim ) + { + me->m_Shared.RemoveCond( TF_COND_STUNNED ); + + // update chase destination + if ( m_victim->IsAlive() && !m_victim->IsEffectActive( EF_NODRAW ) ) + { + m_lastKnownVictimPosition = m_victim->GetAbsOrigin(); + } + } + + if ( m_talkTimer.IsElapsed() ) + { + m_talkTimer.Start( 4.0f ); + me->EmitSound( "MVM.SentryBusterIntro" ); + } + + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) ); + + CTFBotPathCost cost( me, FASTEST_ROUTE ); + m_path.Compute( me, m_lastKnownVictimPosition, cost ); + + if ( m_path.Compute( me, m_lastKnownVictimPosition, cost ) == false ) + { + ++m_consecutivePathFailures; + + if ( m_consecutivePathFailures >= 3 ) + { + bDetonate = true; + } + } + } + + // move to the victim + m_path.Update( me ); + + // Reprogramming time is up. Find nearest and detonate. + if ( m_reprogrammedTimer.IsElapsed() ) + { + // Limit this mode to 5 seconds + if ( !m_detonateSeekTimer.HasStarted() ) + { + m_detonateSeekTimer.Start( 5.f ); + } + else if ( m_detonateSeekTimer.IsElapsed() ) + { + bDetonate = true; + } + + // Get to a third of the damage range before detonating + const float detonateRange = tf_bot_suicide_bomb_range.GetFloat() / 3.0f; + if ( me->IsDistanceBetweenLessThan( m_lastKnownVictimPosition, detonateRange ) ) + { + if ( me->IsLineOfFireClear( m_lastKnownVictimPosition + Vector( 0, 0, StepHeight ) ) ) + { + bDetonate = true; + } + } + } + + if ( bDetonate ) + { + StartDetonate( me, true ); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +void CTFBotMissionReprogrammed::OnEnd( CTFBot *me, Action< CTFBot > *nextAction ) +{ + if ( me->IsAlive() ) + { + me->ForceChangeTeam( TEAM_SPECTATOR ); + } +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotMissionReprogrammed::OnKilled( CTFBot *me, const CTakeDamageInfo &info ) +{ + // Keep us alive, but run to nearest enemy + if ( !m_hasDetonated ) + { + if ( !m_detonateTimer.HasStarted() ) + { + StartDetonate( me ); + } + else + { + Detonate( me ); + } + } + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotMissionReprogrammed::OnStuck( CTFBot *me ) +{ + // we're stuck, decide to detonate now! + if ( !m_hasDetonated && !m_detonateTimer.HasStarted() ) + { + StartDetonate( me ); + } + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +void CTFBotMissionReprogrammed::StartDetonate( CTFBot *me, bool wasSuccessful /* = false */ ) +{ + if ( m_detonateTimer.HasStarted() ) + return; + + if ( !me->IsAlive() || me->GetHealth() < 1 ) + { + if ( me->GetTeamNumber() != TEAM_SPECTATOR) + { + me->m_lifeState = LIFE_ALIVE; + me->SetHealth( 1 ); + } + } + + m_wasSuccessful = wasSuccessful; + + me->Taunt( TAUNT_BASE_WEAPON ); + m_detonateTimer.Start( 2.f ); + me->EmitSound( "MvM.SentryBusterSpin" ); +} + + +//--------------------------------------------------------------------------------------------- +void CTFBotMissionReprogrammed::Detonate( CTFBot *me ) +{ + // BLAST! + m_hasDetonated = true; + + DispatchParticleEffect( "explosionTrail_seeds_mvm", me->GetAbsOrigin(), me->GetAbsAngles() ); + DispatchParticleEffect( "fluidSmokeExpl_ring_mvm", me->GetAbsOrigin(), me->GetAbsAngles() ); + + me->EmitSound( "MVM.SentryBusterExplode" ); + + UTIL_ScreenShake( me->GetAbsOrigin(), 25.0f, 5.0f, 5.0f, 1000.0f, SHAKE_START ); + + if ( !m_wasSuccessful ) + { + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_SENTRY_BUSTER_DOWN, TF_TEAM_PVE_DEFENDERS ); + } + } + + CUtlVector< CTFPlayer* > playerVector; + CUtlVector< CBaseCombatCharacter* > victimVector; + + // Only damage our original team (reprogramming switches team) + CTFTeam *pTeam = me->GetOpposingTFTeam(); + if ( pTeam ) + { + CollectPlayers( &playerVector, pTeam->GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS ); + + // objects + for ( int i = 0; i < pTeam->GetNumObjects(); ++i ) + { + CBaseObject *object = pTeam->GetObject( i ); + if ( object ) + { + victimVector.AddToTail( object ); + } + } + } + + // players + for ( int i = 0; i < playerVector.Count(); ++i ) + { + victimVector.AddToTail( playerVector[i] ); + } + + // non-player bots + CUtlVector< INextBot * > botVector; + TheNextBots().CollectAllBots( &botVector ); + for( int i = 0; i < botVector.Count(); ++i ) + { + CBaseCombatCharacter *bot = botVector[i]->GetEntity(); + + if ( !bot->IsPlayer() && bot->IsAlive() ) + { + victimVector.AddToTail( bot ); + } + } + + // Clear my mission before we have everyone take damage so I will die with the rest + me->SetMission( CTFBot::NO_MISSION, MISSION_DOESNT_RESET_BEHAVIOR_SYSTEM ); + me->m_takedamage = DAMAGE_YES; + + // kill victims (including me) + for( int i = 0; i < victimVector.Count(); ++i ) + { + CBaseCombatCharacter *victim = victimVector[i]; + + Vector toVictim = victim->WorldSpaceCenter() - me->WorldSpaceCenter(); + + if ( toVictim.IsLengthGreaterThan( tf_bot_suicide_bomb_range.GetFloat() ) ) + continue; + + if ( victim->IsPlayer() ) + { + color32 colorHit = { 255, 255, 255, 255 }; + UTIL_ScreenFade( victim, colorHit, 1.0f, 0.1f, FFADE_IN ); + } + + if ( me->IsLineOfFireClear( victim ) ) + { + toVictim.NormalizeInPlace(); + + const int nDamage = 1000; + CTakeDamageInfo info( me, me, nDamage, DMG_BLAST, TF_DMG_CUSTOM_NONE ); + + CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 1.0f ); + victim->TakeDamage( info ); + } + } + + // make sure we're removed (in case we detonated in our spawn area where we are invulnerable) + me->ForceChangeTeam( TEAM_SPECTATOR ); +} + +//--------------------------------------------------------------------------------------------- +CTFPlayer *CTFBotMissionReprogrammed::FindNearestEnemy( CTFBot *me ) +{ + CUtlVector< CTFPlayer* > playerVector; + + CollectPlayers( &playerVector, GetEnemyTeam( me->GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS ); + + CTFPlayer *pClosestPlayer = NULL; + float flClosestPlayerDist = FLT_MAX; + + FOR_EACH_VEC( playerVector, i ) + { + if ( !playerVector[i] ) + continue; + + if ( playerVector[i] == me ) + continue; + + me->GetDistanceBetween( playerVector[i] ); + + // Find closest + float flDist = me->GetDistanceBetween( playerVector[i] ); + if ( flDist < flClosestPlayerDist ) + { + pClosestPlayer = playerVector[i]; + flClosestPlayerDist = flDist; + } + } + + return pClosestPlayer; +} + + +//--------------------------------------------------------------------------------------------- +QueryResultType CTFBotMissionReprogrammed::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const +{ + return ( m_detonateTimer.HasStarted() ) ? ANSWER_NO : ANSWER_YES; +} + +#endif // STAGING_ONLY + diff --git a/game/server/tf/bot/behavior/missions/tf_bot_mission_reprogrammed.h b/game/server/tf/bot/behavior/missions/tf_bot_mission_reprogrammed.h new file mode 100644 index 0000000..7c63c7e --- /dev/null +++ b/game/server/tf/bot/behavior/missions/tf_bot_mission_reprogrammed.h @@ -0,0 +1,53 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_mission_suicide_bomber.h +// Move to target and explode +// Michael Booth, October 2011 + +#ifndef TF_BOT_MISSION_REPROGRAMMED_H +#define TF_BOT_MISSION_REPROGRAMMED_H + +#include "Path/NextBotPathFollow.h" + +class CTFBotMissionReprogrammed : public Action< CTFBot > +{ +#ifdef STAGING_ONLY +public: + CTFBotMissionReprogrammed( void ); + + 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 EventDesiredResult< CTFBot > OnStuck( CTFBot *me ); + virtual EventDesiredResult< CTFBot > OnKilled( CTFBot *me, const CTakeDamageInfo &info ); + + virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"? + + virtual const char *GetName( void ) const { return "MissionReprogrammed"; }; + +private: + CHandle< CBaseEntity > m_victim; // the victim we are trying to destroy + Vector m_lastKnownVictimPosition; + + PathFollower m_path; + CountdownTimer m_repathTimer; + + CountdownTimer m_talkTimer; + CountdownTimer m_detonateTimer; + CountdownTimer m_detonateSeekTimer; + CountdownTimer m_reprogrammedTimer; + + void StartDetonate( CTFBot *me, bool wasSuccessful = false ); + void Detonate( CTFBot *me ); + CTFPlayer *FindNearestEnemy( CTFBot *me ); + bool m_hasDetonated; + bool m_wasSuccessful; + + int m_consecutivePathFailures; + + Vector m_vecDetLocation; +#endif // STAGING_ONLY +}; + + +#endif // TF_BOT_MISSION_REPROGRAMMED_H diff --git a/game/server/tf/bot/behavior/missions/tf_bot_mission_suicide_bomber.cpp b/game/server/tf/bot/behavior/missions/tf_bot_mission_suicide_bomber.cpp new file mode 100644 index 0000000..aac9d6c --- /dev/null +++ b/game/server/tf/bot/behavior/missions/tf_bot_mission_suicide_bomber.cpp @@ -0,0 +1,400 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_mission_suicide_bomber.cpp +// Move to target and explode +// Michael Booth, October 2011 + +#include "cbase.h" +#include "tf_team.h" +#include "nav_mesh.h" +#include "tf_player.h" +#include "bot/tf_bot.h" +#include "bot/behavior/missions/tf_bot_mission_suicide_bomber.h" +#include "particle_parse.h" +#include "tf_obj_sentrygun.h" +#include "player_vs_environment/tf_populators.h" + +extern ConVar tf_bot_path_lookahead_range; + +ConVar tf_bot_suicide_bomb_range( "tf_bot_suicide_bomb_range", "300", FCVAR_CHEAT ); +ConVar tf_bot_suicide_bomb_friendly_fire( "tf_bot_suicide_bomb_friendly_fire", "1", FCVAR_CHEAT ); + +//--------------------------------------------------------------------------------------------- +CTFBotMissionSuicideBomber::CTFBotMissionSuicideBomber( void ) +{ +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMissionSuicideBomber::OnStart( CTFBot *me, Action< CTFBot > *priorAction ) +{ + m_path.SetMinLookAheadDistance( me->GetDesiredPathLookAheadRange() ); + m_detonateTimer.Invalidate(); + m_bHasDetonated = false; + m_consecutivePathFailures = 0; + m_bWasSuccessful = false; + m_bWasKilled = false; + + m_victim = me->GetMissionTarget(); + + if ( m_victim != NULL ) + { + m_lastKnownVictimPosition = m_victim->GetAbsOrigin(); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CTFBot > CTFBotMissionSuicideBomber::Update( CTFBot *me, float interval ) +{ + // one we start detonating, there's no turning back + if ( m_detonateTimer.HasStarted() ) + { + if ( m_detonateTimer.IsElapsed() ) + { + m_vecDetLocation = me->GetAbsOrigin(); + Detonate( me ); + + // Send out an event + if ( m_bWasSuccessful && m_victim && m_victim->IsBaseObject() ) + { + CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( m_victim.Get() ); + if ( sentry && sentry->GetOwner() ) + { + CTFPlayer *pOwner = ToTFPlayer( sentry->GetOwner() ); + if ( pOwner ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "mvm_sentrybuster_detonate" ); + if ( event ) + { + event->SetInt( "player", pOwner->entindex() ); + event->SetFloat( "det_x", m_vecDetLocation.x ); + event->SetFloat( "det_y", m_vecDetLocation.y ); + event->SetFloat( "det_z", m_vecDetLocation.z ); + gameeventmanager->FireEvent( event ); + } + } + } + } + + return Done( "KABOOM!" ); + } + + return Continue(); + } + + + if ( me->GetHealth() == 1 ) + { + // low on health - detonate where we are! + StartDetonate( me, false, true ); + + return Continue(); + } + + if ( m_victim != NULL ) + { + // update chase destination + if ( m_victim->IsAlive() && !m_victim->IsEffectActive( EF_NODRAW ) ) + { + m_lastKnownVictimPosition = m_victim->GetAbsOrigin(); + } + + // if the engineer is carrying his sentry, he becomes the victim + if ( m_victim->IsBaseObject() ) + { + CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( m_victim.Get() ); + if ( sentry && sentry->IsCarried() && sentry->GetOwner() ) + { + // path to the engineer carrying the sentry + m_lastKnownVictimPosition = sentry->GetOwner()->GetAbsOrigin(); + } + } + } + + // Get to a third of the damage range before detonating + const float detonateRange = tf_bot_suicide_bomb_range.GetFloat() / 3.0f; + if ( me->IsDistanceBetweenLessThan( m_lastKnownVictimPosition, detonateRange ) ) + { + if ( me->IsLineOfFireClear( m_lastKnownVictimPosition + Vector( 0, 0, StepHeight ) ) ) + { + StartDetonate( me, true ); + } + } + + if ( m_talkTimer.IsElapsed() ) + { + m_talkTimer.Start( 4.0f ); + me->EmitSound( "MVM.SentryBusterIntro" ); + } + + if ( m_repathTimer.IsElapsed() ) + { + m_repathTimer.Start( RandomFloat( 0.5f, 1.0f ) ); + + CTFBotPathCost cost( me, FASTEST_ROUTE ); + + if ( m_path.Compute( me, m_lastKnownVictimPosition, cost ) == false ) + { + ++m_consecutivePathFailures; + + if ( m_consecutivePathFailures >= 3 ) + { + // really can't reach my victim - detonate! + StartDetonate( me ); + } + } + else + { + m_consecutivePathFailures = 0; + } + } + + // move to the victim + m_path.Update( me ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +void CTFBotMissionSuicideBomber::OnEnd( CTFBot *me, Action< CTFBot > *nextAction ) +{ +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotMissionSuicideBomber::OnKilled( CTFBot *me, const CTakeDamageInfo &info ) +{ + if ( !m_bHasDetonated ) + { + if ( !m_detonateTimer.HasStarted() ) + { + StartDetonate( me ); + } + else if ( m_detonateTimer.IsElapsed() ) + { + Detonate( me ); + } + else + { + // We're in detonate mode, and something's trying to kill us. Prevent it. + if ( me->GetTeamNumber() != TEAM_SPECTATOR ) + { + me->m_lifeState = LIFE_ALIVE; + me->SetHealth( 1 ); + } + } + } + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CTFBot > CTFBotMissionSuicideBomber::OnStuck( CTFBot *me ) +{ + // we're stuck, decide to detonate now! + if ( !m_bHasDetonated && !m_detonateTimer.HasStarted() ) + { + StartDetonate( me ); + } + + return TryContinue(); +} + + +//--------------------------------------------------------------------------------------------- +void CTFBotMissionSuicideBomber::StartDetonate( CTFBot *me, bool bWasSuccessful /* = false */, bool bWasKilled /*= false*/ ) +{ + if ( m_detonateTimer.HasStarted() ) + return; + + if ( !me->IsAlive() || me->GetHealth() < 1 ) + { + if ( me->GetTeamNumber() != TEAM_SPECTATOR) + { + me->m_lifeState = LIFE_ALIVE; + me->SetHealth( 1 ); + } + } + + m_bWasSuccessful = bWasSuccessful; + m_bWasKilled = bWasKilled; + + me->m_takedamage = DAMAGE_NO; + + me->Taunt( TAUNT_BASE_WEAPON ); + m_detonateTimer.Start( 2.0f ); + me->EmitSound( "MvM.SentryBusterSpin" ); +} + + +//--------------------------------------------------------------------------------------------- +void CTFBotMissionSuicideBomber::Detonate( CTFBot *me ) +{ + // BLAST! + m_bHasDetonated = true; + + DispatchParticleEffect( "explosionTrail_seeds_mvm", me->GetAbsOrigin(), me->GetAbsAngles() ); + DispatchParticleEffect( "fluidSmokeExpl_ring_mvm", me->GetAbsOrigin(), me->GetAbsAngles() ); + + me->EmitSound( "MVM.SentryBusterExplode" ); + + UTIL_ScreenShake( me->GetAbsOrigin(), 25.0f, 5.0f, 5.0f, 1000.0f, SHAKE_START ); + + if ( !m_bWasSuccessful ) + { + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_SENTRY_BUSTER_DOWN, TF_TEAM_PVE_DEFENDERS ); + + // ACHIEVEMENT_TF_MVM_KILL_SENTRY_BUSTER + for ( int iDamager = 0 ; iDamager < MAX_ACHIEVEMENT_HISTORY_SLOTS ; iDamager ++ ) + { + EntityHistory_t *damagerHistory = me->m_AchievementData.GetDamagerHistory( iDamager ); + if ( damagerHistory ) + { + if ( damagerHistory->hEntity && ( gpGlobals->curtime - damagerHistory->flTimeDamage <= 5.0f ) ) + { + CTFPlayer *pRecentDamager = ToTFPlayer( damagerHistory->hEntity ); + if ( pRecentDamager ) + { + pRecentDamager->AwardAchievement( ACHIEVEMENT_TF_MVM_KILL_SENTRY_BUSTER ); + } + } + } + } + } + } + + CUtlVector< CTFPlayer * > playerVector; + CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS ); + CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS ); + + CUtlVector< CBaseCombatCharacter * > victimVector; + + int i; + + // players + for ( i=0; i<playerVector.Count(); ++i ) + { + victimVector.AddToTail( playerVector[i] ); + } + + // objects + CTFTeam *team = GetGlobalTFTeam( TF_TEAM_BLUE ); + if ( team ) + { + for ( i=0; i<team->GetNumObjects(); ++i ) + { + CBaseObject *object = team->GetObject( i ); + if ( object ) + { + victimVector.AddToTail( object ); + } + } + } + + team = GetGlobalTFTeam( TF_TEAM_RED ); + if ( team ) + { + for ( i=0; i<team->GetNumObjects(); ++i ) + { + CBaseObject *object = team->GetObject( i ); + if ( object ) + { + victimVector.AddToTail( object ); + } + } + } + + // non-player bots + CUtlVector< INextBot * > botVector; + TheNextBots().CollectAllBots( &botVector ); + for( i=0; i<botVector.Count(); ++i ) + { + CBaseCombatCharacter *bot = botVector[i]->GetEntity(); + + if ( !bot->IsPlayer() && bot->IsAlive() ) + { + victimVector.AddToTail( bot ); + } + } + + // Send out an event whenever players damaged us to the point where we had to detonate + if ( m_bWasKilled ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "mvm_sentrybuster_killed" ); + if ( event ) + { + event->SetInt( "sentry_buster", me->entindex() ); + gameeventmanager->FireEvent( event ); + } + } + + // Clear my mission before we have everyone take damage so I will die with the rest + me->SetMission( CTFBot::NO_MISSION, MISSION_DOESNT_RESET_BEHAVIOR_SYSTEM ); + me->m_takedamage = DAMAGE_YES; + + // kill victims (including me) + for( int i=0; i<victimVector.Count(); ++i ) + { + CBaseCombatCharacter *victim = victimVector[i]; + + Vector toVictim = victim->WorldSpaceCenter() - me->WorldSpaceCenter(); + + if ( toVictim.IsLengthGreaterThan( tf_bot_suicide_bomb_range.GetFloat() ) ) + continue; + + if ( victim->IsPlayer() ) + { + color32 colorHit = { 255, 255, 255, 255 }; + UTIL_ScreenFade( victim, colorHit, 1.0f, 0.1f, FFADE_IN ); + } + + if ( me->IsLineOfFireClear( victim ) ) + { + toVictim.NormalizeInPlace(); + + int damage = MAX( victim->GetMaxHealth(), victim->GetHealth() ); + + CTakeDamageInfo info( me, me, 4 * damage, DMG_BLAST, TF_DMG_CUSTOM_NONE ); + if ( tf_bot_suicide_bomb_friendly_fire.GetBool() ) + { + info.SetForceFriendlyFire( true ); + } + + CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 1.0f ); + victim->TakeDamage( info ); + } + } + + // make sure we're removed (in case we detonated in our spawn area where we are invulnerable) + me->CommitSuicide( false, true ); + if ( me->IsAlive() ) + { + me->ForceChangeTeam( TEAM_SPECTATOR ); + } + + if ( m_bWasKilled ) + { + // increment num sentry killed this wave + CWave *pWave = g_pPopulationManager ? g_pPopulationManager->GetCurrentWave() : NULL; + if ( pWave ) + { + pWave->IncrementSentryBustersKilled(); + } + } +} + + +// Should we attack "them"? +QueryResultType CTFBotMissionSuicideBomber::ShouldAttack( const INextBot *me, const CKnownEntity *them ) const +{ + // buster never "attacks", just approaches and self-detonates + return ANSWER_NO; +} + + diff --git a/game/server/tf/bot/behavior/missions/tf_bot_mission_suicide_bomber.h b/game/server/tf/bot/behavior/missions/tf_bot_mission_suicide_bomber.h new file mode 100644 index 0000000..6105bea --- /dev/null +++ b/game/server/tf/bot/behavior/missions/tf_bot_mission_suicide_bomber.h @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_mission_suicide_bomber.h +// Move to target and explode +// Michael Booth, October 2011 + +#ifndef TF_BOT_MISSION_SUICIDE_BOMBER_H +#define TF_BOT_MISSION_SUICIDE_BOMBER_H + +#include "Path/NextBotPathFollow.h" + +class CTFBotMissionSuicideBomber : public Action< CTFBot > +{ +public: + CTFBotMissionSuicideBomber( void ); + + 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 EventDesiredResult< CTFBot > OnStuck( CTFBot *me ); + virtual EventDesiredResult< CTFBot > OnKilled( CTFBot *me, const CTakeDamageInfo &info ); + + virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const; // should we attack "them"? + + virtual const char *GetName( void ) const { return "MissionSuicideBomber"; }; + +private: + CHandle< CBaseEntity > m_victim; // the victim we are trying to destroy + Vector m_lastKnownVictimPosition; + + PathFollower m_path; + CountdownTimer m_repathTimer; + + CountdownTimer m_talkTimer; + CountdownTimer m_detonateTimer; + + void StartDetonate( CTFBot *me, bool bWasSuccessful = false, bool bWasKilled = false ); + void Detonate( CTFBot *me ); + bool m_bHasDetonated; + bool m_bWasSuccessful; + bool m_bWasKilled; + + int m_consecutivePathFailures; + + Vector m_vecDetLocation; +}; + + +#endif // TF_BOT_MISSION_SUICIDE_BOMBER_H |