You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
400 lines
10 KiB
400 lines
10 KiB
//========= 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; |
|
} |
|
|
|
|
|
|