Team Fortress 2 Source Code as on 22/4/2020
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.

3604 lines
100 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. // bot_npc.cpp
  3. // A NextBot non-player derived actor
  4. // Michael Booth, November 2010
  5. #include "cbase.h"
  6. #ifdef OBSOLETE_USE_BOSS_ALPHA
  7. #ifdef TF_RAID_MODE
  8. #include "tf_player.h"
  9. #include "tf_gamerules.h"
  10. #include "tf_team.h"
  11. #include "tf_projectile_arrow.h"
  12. #include "tf_projectile_rocket.h"
  13. #include "tf_weapon_grenade_pipebomb.h"
  14. #include "tf_ammo_pack.h"
  15. #include "tf_obj_sentrygun.h"
  16. #include "nav_mesh/tf_nav_area.h"
  17. #include "bot_npc.h"
  18. #include "NextBot/Path/NextBotChasePath.h"
  19. #include "econ_wearable.h"
  20. #include "team_control_point_master.h"
  21. #include "particle_parse.h"
  22. #include "CRagdollMagnet.h"
  23. #include "nav_mesh/tf_path_follower.h"
  24. #include "bot_npc_minion.h"
  25. #include "player_vs_environment/monster_resource.h"
  26. #include "bot/map_entities/tf_bot_generator.h"
  27. #include "player_vs_environment/tf_population_manager.h"
  28. //#define USE_BOSS_SENTRY
  29. ConVar tf_bot_npc_health( "tf_bot_npc_health", "100000"/*, FCVAR_CHEAT*/ ); // 50000
  30. ConVar tf_bot_npc_speed( "tf_bot_npc_speed", "300"/*, FCVAR_CHEAT*/ );
  31. ConVar tf_bot_npc_attack_range( "tf_bot_npc_attack_range", "300"/*, FCVAR_CHEAT*/ );
  32. ConVar tf_bot_npc_melee_damage( "tf_bot_npc_melee_damage", "150"/*, FCVAR_CHEAT*/ );
  33. ConVar tf_bot_npc_threat_tolerance( "tf_bot_npc_threat_tolerance", "100"/*, FCVAR_CHEAT*/ );
  34. ConVar tf_bot_npc_shoot_interval( "tf_bot_npc_shoot_interval", "15"/*, FCVAR_CHEAT*/ ); // 2
  35. ConVar tf_bot_npc_aim_time( "tf_bot_npc_aim_time", "1"/*, FCVAR_CHEAT*/ );
  36. ConVar tf_bot_npc_chase_range( "tf_bot_npc_chase_range", "300"/*, FCVAR_CHEAT*/ );
  37. ConVar tf_bot_npc_grenade_launch_range( "tf_bot_npc_grenade_launch_range", "300"/*, FCVAR_CHEAT*/ );
  38. ConVar tf_bot_npc_grenade_damage( "tf_bot_npc_grenade_damage", "25"/*, FCVAR_CHEAT*/ );
  39. ConVar tf_bot_npc_minion_launch_count_initial( "tf_bot_npc_minion_launch_count_initial", "5"/*, FCVAR_CHEAT*/ );
  40. ConVar tf_bot_npc_minion_launch_count_increase_interval( "tf_bot_npc_minion_launch_count_increase_interval", "999999999"/*, FCVAR_CHEAT*/ ); // 30
  41. ConVar tf_bot_npc_minion_launch_initial_interval( "tf_bot_npc_minion_launch_initial_interval", "20"/*, FCVAR_CHEAT*/ );
  42. ConVar tf_bot_npc_minion_launch_interval( "tf_bot_npc_minion_launch_interval", "30"/*, FCVAR_CHEAT*/ );
  43. ConVar tf_bot_npc_chase_duration( "tf_bot_npc_chase_duration", "30"/*, FCVAR_CHEAT*/ );
  44. ConVar tf_bot_npc_quit_range( "tf_bot_npc_quit_range", "2500"/*, FCVAR_CHEAT*/ );
  45. ConVar tf_bot_npc_reaction_time( "tf_bot_npc_reaction_time", "0.5"/*, FCVAR_CHEAT*/ );
  46. ConVar tf_bot_npc_charge_interval( "tf_bot_npc_charge_interval", "10"/*, FCVAR_CHEAT*/ );
  47. ConVar tf_bot_npc_charge_pushaway_force( "tf_bot_npc_charge_pushaway_force", "500"/*, FCVAR_CHEAT*/ );
  48. ConVar tf_bot_npc_charge_damage( "tf_bot_npc_charge_damage", "150"/*, FCVAR_CHEAT*/ );
  49. ConVar tf_bot_npc_nuke_charge_time( "tf_bot_npc_nuke_charge_time", "5" );
  50. ConVar tf_bot_npc_nuke_interval( "tf_bot_npc_nuke_interval", "20" );
  51. ConVar tf_bot_npc_nuke_lethal_time( "tf_bot_npc_nuke_lethal_time", "999999999" ); // 300
  52. ConVar tf_bot_npc_block_dps_react( "tf_bot_npc_block_dps_react", "150" );
  53. ConVar tf_bot_npc_become_stunned_damage( "tf_bot_npc_become_stunned_damage", "500" );
  54. ConVar tf_bot_npc_stunned_injury_multiplier( "tf_bot_npc_stunned_injury_multiplier", "10" );
  55. ConVar tf_bot_npc_stunned_duration( "tf_bot_npc_stunned_duration", "5" );
  56. ConVar tf_bot_npc_head_radius( "tf_bot_npc_head_radius", "75" ); // 50
  57. ConVar tf_bot_npc_stun_rocket_reflect_count( "tf_bot_npc_stun_rocket_reflect_count", "2"/*, FCVAR_CHEAT */ );
  58. ConVar tf_bot_npc_stun_rocket_reflect_duration( "tf_bot_npc_stun_rocket_reflect_duration", "1"/*, FCVAR_CHEAT */ );
  59. ConVar tf_bot_npc_grenade_interval( "tf_bot_npc_grenade_interval", "10" );
  60. ConVar tf_bot_npc_hate_taunt_cooldown( "tf_bot_npc_hate_taunt_cooldown", "10"/*, FCVAR_CHEAT*/ );
  61. ConVar tf_bot_npc_debug_damage( "tf_bot_npc_debug_damage", "0"/*, FCVAR_CHEAT*/ );
  62. ConVar tf_bot_npc_always_stun( "tf_bot_npc_always_stun", "0"/*, FCVAR_CHEAT*/ );
  63. ConVar tf_bot_npc_min_nuke_after_stun_time( "tf_bot_npc_min_nuke_after_stun_time", "5" /*, FCVAR_CHEAT */ );
  64. //-----------------------------------------------------------------------------------------------------
  65. // The Bot NPC
  66. //-----------------------------------------------------------------------------------------------------
  67. LINK_ENTITY_TO_CLASS( bot_boss, CBotNPC );
  68. PRECACHE_REGISTER( bot_boss );
  69. IMPLEMENT_SERVERCLASS_ST( CBotNPC, DT_BotNPC )
  70. SendPropEHandle( SENDINFO( m_laserTarget ) ),
  71. SendPropBool( SENDINFO( m_isNuking ) ),
  72. END_SEND_TABLE()
  73. //------------------------------------------------------------------------------
  74. void CBotNPC::InputSpawn( inputdata_t &inputdata )
  75. {
  76. DispatchSpawn( this );
  77. }
  78. //-----------------------------------------------------------------------------------------------------
  79. CBotNPC::CBotNPC()
  80. {
  81. m_intention = new CBotNPCIntention( this );
  82. m_locomotor = new CBotNPCLocomotion( this );
  83. m_body = new CBotNPCBody( this );
  84. m_vision = new CBotNPCVision( this );
  85. m_conditionFlags = 0;
  86. m_laserTarget = NULL;
  87. m_isNuking = false;
  88. m_ageTimer.Invalidate();
  89. m_spawner = NULL;
  90. ClearStunDamage();
  91. }
  92. //-----------------------------------------------------------------------------------------------------
  93. CBotNPC::~CBotNPC()
  94. {
  95. if ( m_intention )
  96. delete m_intention;
  97. if ( m_locomotor )
  98. delete m_locomotor;
  99. if ( m_body )
  100. delete m_body;
  101. if ( m_vision )
  102. delete m_vision;
  103. }
  104. //-----------------------------------------------------------------------------------------------------
  105. void CBotNPC::Precache()
  106. {
  107. BaseClass::Precache();
  108. #ifdef USE_BOSS_SENTRY
  109. int model = PrecacheModel( "models/bots/boss_sentry/boss_sentry.mdl" );
  110. #else
  111. int model = PrecacheModel( "models/bots/knight/knight.mdl" );
  112. #endif
  113. PrecacheGibsForModel( model );
  114. PrecacheModel( "models/weapons/c_models/c_bigsword/c_bigsword.mdl" );
  115. PrecacheModel( "models/weapons/c_models/c_bigshield/c_bigshield.mdl" );
  116. PrecacheModel( "models/weapons/c_models/c_big_mean_mother_hubbard/c_big_mean.mdl" );
  117. PrecacheScriptSound( "Weapon_Sword.Swing" );
  118. PrecacheScriptSound( "Weapon_Sword.HitFlesh" );
  119. PrecacheScriptSound( "Weapon_Sword.HitWorld" );
  120. PrecacheScriptSound( "DemoCharge.HitWorld" );
  121. PrecacheScriptSound( "TFPlayer.Pain" );
  122. PrecacheScriptSound( "Halloween.HeadlessBossAttack" );
  123. PrecacheScriptSound( "RobotBoss.StunStart" );
  124. PrecacheScriptSound( "RobotBoss.Stunned" );
  125. PrecacheScriptSound( "RobotBoss.StunRecover" );
  126. PrecacheScriptSound( "RobotBoss.Acquire" );
  127. PrecacheScriptSound( "RobotBoss.Vocalize" );
  128. PrecacheScriptSound( "RobotBoss.Footstep" );
  129. PrecacheScriptSound( "RobotBoss.LaunchGrenades" );
  130. PrecacheScriptSound( "RobotBoss.LaunchRockets" );
  131. PrecacheScriptSound( "RobotBoss.Hurt" );
  132. PrecacheScriptSound( "RobotBoss.Vulnerable" );
  133. PrecacheScriptSound( "RobotBoss.ChargeUpNukeAttack" );
  134. PrecacheScriptSound( "RobotBoss.NukeAttack" );
  135. PrecacheScriptSound( "RobotBoss.Scanning" );
  136. PrecacheScriptSound( "RobotBoss.ReinforcementsArrived" );
  137. PrecacheScriptSound( "Cart.Explode" );
  138. PrecacheParticleSystem( "asplode_hoodoo_embers" );
  139. PrecacheParticleSystem( "charge_up" );
  140. PrecacheArmorParts();
  141. }
  142. //-----------------------------------------------------------------------------------------------------
  143. void CBotNPC::PrecacheArmorParts( void )
  144. {
  145. CUtlBuffer fileBuffer( 4096, 1024*1024, CUtlBuffer::TEXT_BUFFER );
  146. // filename is local to game dir for Steam, so we need to prepend game dir
  147. char gamePath[256];
  148. engine->GetGameDir( gamePath, 256 );
  149. char filename[256];
  150. Q_snprintf( filename, sizeof( filename ), "%s\\models\\bots\\knight\\armor_parts.txt", gamePath );
  151. if ( !filesystem->ReadFile( filename, "MOD", fileBuffer ) )
  152. {
  153. Warning( "Unable to read %s\n", filename );
  154. }
  155. else
  156. {
  157. while( true )
  158. {
  159. char partName[256];
  160. if ( fileBuffer.Scanf( "%s", partName ) <= 0 )
  161. {
  162. break;
  163. }
  164. // Make sure we have a valid string before trying to precache it.
  165. if ( Q_strlen( partName ) > 0 )
  166. {
  167. PrecacheModel( partName );
  168. }
  169. }
  170. }
  171. }
  172. //-----------------------------------------------------------------------------------------------------
  173. void CBotNPC::InstallArmorParts( void )
  174. {
  175. if ( IsMiniBoss() )
  176. return;
  177. CUtlBuffer fileBuffer( 4096, 1024*1024, CUtlBuffer::TEXT_BUFFER );
  178. // filename is local to game dir for Steam, so we need to prepend game dir
  179. char gamePath[256];
  180. engine->GetGameDir( gamePath, 256 );
  181. char filename[256];
  182. Q_snprintf( filename, sizeof( filename ), "%s\\models\\bots\\knight\\armor_parts.txt", gamePath );
  183. if ( !filesystem->ReadFile( filename, "MOD", fileBuffer ) )
  184. {
  185. Warning( "Unable to read %s\n", filename );
  186. }
  187. else
  188. {
  189. while( true )
  190. {
  191. char partName[256];
  192. if ( fileBuffer.Scanf( "%s", partName ) <= 0 )
  193. {
  194. break;
  195. }
  196. CBaseAnimating *part = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" );
  197. if ( part )
  198. {
  199. part->SetModel( partName );
  200. // bonemerge into our model
  201. part->FollowEntity( this, true );
  202. m_armorPartVector.AddToTail( part );
  203. }
  204. }
  205. }
  206. }
  207. //-----------------------------------------------------------------------------------------------------
  208. void CBotNPC::Spawn( void )
  209. {
  210. BaseClass::Spawn();
  211. #ifdef USE_BOSS_SENTRY
  212. SetModel( "models/bots/boss_sentry/boss_sentry.mdl" );
  213. #else
  214. SetModel( "models/bots/knight/knight.mdl" );
  215. #endif
  216. InstallArmorParts();
  217. ModifyMaxHealth( tf_bot_npc_health.GetInt() );
  218. // show Boss' health meter on HUD
  219. if ( g_pMonsterResource )
  220. {
  221. g_pMonsterResource->SetBossHealthPercentage( 1.0f );
  222. }
  223. m_damagePoseParameter = -1;
  224. m_conditionFlags = 0;
  225. // randomize initial check
  226. m_nearestVisibleEnemy = NULL;
  227. m_nearestVisibleEnemyTimer.Start( RandomFloat( 0.0f, tf_bot_npc_reaction_time.GetFloat() ) );
  228. m_homePos = GetAbsOrigin();
  229. m_currentDamagePerSecond = 0.0f;
  230. m_lastDamagePerSecond = 0.0f;
  231. m_attackTarget = NULL;
  232. m_attackTargetTimer.Invalidate();
  233. m_isAttackTargetLocked = false;
  234. m_nukeTimer.Start( tf_bot_npc_nuke_interval.GetFloat() );
  235. m_isNuking = false;
  236. m_grenadeTimer.Start( GetGrenadeInterval() );
  237. m_ageTimer.Start();
  238. ChangeTeam( TF_TEAM_RED );
  239. TFGameRules()->SetActiveBoss( this );
  240. }
  241. //-----------------------------------------------------------------------------------------------------
  242. ConVar tf_bot_npc_dmg_mult_sniper( "tf_bot_npc_dmg_mult_sniper", "1.5"/*, FCVAR_CHEAT*/ );
  243. ConVar tf_bot_npc_dmg_mult_minigun( "tf_bot_npc_dmg_mult_minigun", "0.5"/*, FCVAR_CHEAT*/ );
  244. ConVar tf_bot_npc_dmg_mult_flamethrower( "tf_bot_npc_dmg_mult_flamethrower", "1"/*, FCVAR_CHEAT*/ );
  245. ConVar tf_bot_npc_dmg_mult_sentrygun( "tf_bot_npc_dmg_mult_sentrygun", "0.5"/*, FCVAR_CHEAT*/ );
  246. ConVar tf_bot_npc_dmg_mult_grenade( "tf_bot_npc_dmg_mult_grenade", "2"/*, FCVAR_CHEAT*/ );
  247. float ModifyBossDamage( const CTakeDamageInfo &info )
  248. {
  249. CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( info.GetWeapon() );
  250. if ( pWeapon )
  251. {
  252. switch( pWeapon->GetWeaponID() )
  253. {
  254. case TF_WEAPON_SNIPERRIFLE:
  255. case TF_WEAPON_SNIPERRIFLE_DECAP:
  256. case TF_WEAPON_SNIPERRIFLE_CLASSIC:
  257. case TF_WEAPON_COMPOUND_BOW:
  258. return info.GetDamage() * tf_bot_npc_dmg_mult_sniper.GetFloat();
  259. case TF_WEAPON_MINIGUN:
  260. return info.GetDamage() * tf_bot_npc_dmg_mult_minigun.GetFloat();
  261. case TF_WEAPON_FLAMETHROWER:
  262. return info.GetDamage() * tf_bot_npc_dmg_mult_flamethrower.GetFloat();
  263. case TF_WEAPON_SENTRY_BULLET:
  264. return info.GetDamage() * tf_bot_npc_dmg_mult_sentrygun.GetFloat();
  265. case TF_WEAPON_GRENADE_DEMOMAN:
  266. return info.GetDamage() * tf_bot_npc_dmg_mult_grenade.GetFloat();
  267. }
  268. }
  269. // unmodified
  270. return info.GetDamage();
  271. }
  272. //-----------------------------------------------------------------------------------------------------
  273. int CBotNPC::OnTakeDamage_Alive( const CTakeDamageInfo &rawInfo )
  274. {
  275. CTakeDamageInfo info = rawInfo;
  276. // don't take damage from myself
  277. if ( info.GetAttacker() == this )
  278. {
  279. return 0;
  280. }
  281. if ( IsInCondition( INVULNERABLE ) )
  282. {
  283. return 0;
  284. }
  285. if ( IsInCondition( SHIELDED ) )
  286. {
  287. // no damage from the front
  288. CBaseEntity *inflictor = info.GetInflictor();
  289. if ( inflictor )
  290. {
  291. Vector myForward;
  292. GetVectors( &myForward, NULL, NULL );
  293. Vector themForward;
  294. inflictor->GetVectors( &themForward, NULL, NULL );
  295. if ( DotProduct( themForward, myForward ) < -0.7071f )
  296. {
  297. // blocked by my shield
  298. EmitSound( "FX_RicochetSound.Ricochet" );
  299. DispatchParticleEffect( "asplode_hoodoo_embers", info.GetDamagePosition(), GetAbsAngles() );
  300. return 0;
  301. }
  302. }
  303. }
  304. // weapon-specific damage modification
  305. info.SetDamage( ModifyBossDamage( info ) );
  306. if ( IsInCondition( VULNERABLE_TO_STUN ) )
  307. {
  308. // Heavies can't deal stun damage (too high DPS)
  309. //CTFPlayer *playerAttacker = ToTFPlayer( info.GetAttacker() );
  310. if ( true ) // !playerAttacker ) || !playerAttacker->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
  311. {
  312. // track head damage when vulnerable
  313. Vector headPos;
  314. QAngle headAngles;
  315. if ( GetAttachment( "head", headPos, headAngles ) )
  316. {
  317. Vector damagePos = info.GetDamagePosition();
  318. /*
  319. const trace_t &pTrace = CBaseEntity::GetTouchTrace();
  320. damagePos = pTrace.endpos;
  321. */
  322. /*
  323. CBaseEntity *inflictor = info.GetInflictor();
  324. if ( inflictor )
  325. {
  326. damagePos = inflictor->GetAbsOrigin() + 3.0f * gpGlobals->frametime * inflictor->GetAbsVelocity();
  327. }
  328. */
  329. if ( tf_bot_npc_debug_damage.GetBool() )
  330. {
  331. NDebugOverlay::Cross3D( headPos, 5.0f, 255, 0, 0, true, 5.0f );
  332. NDebugOverlay::Cross3D( damagePos, 5.0f, 0, 255, 0, true, 5.0f );
  333. NDebugOverlay::Line( damagePos, headPos, 255, 255, 0, true, 5.0f );
  334. }
  335. bool isHeadHit = ( damagePos - headPos ).IsLengthLessThan( tf_bot_npc_head_radius.GetFloat() );
  336. if ( isHeadHit )
  337. {
  338. // hit the head
  339. AccumulateStunDamage( info.GetDamage() );
  340. DispatchParticleEffect( "asplode_hoodoo_embers", info.GetDamagePosition(), GetAbsAngles() );
  341. if ( tf_bot_npc_debug_damage.GetBool() )
  342. {
  343. DevMsg( "Stun dmg = %f\n", GetStunDamage() );
  344. NDebugOverlay::Circle( headPos, tf_bot_npc_head_radius.GetFloat(), 255, 0, 0, 255, true, 5.0f );
  345. }
  346. }
  347. else if ( tf_bot_npc_debug_damage.GetBool() )
  348. {
  349. NDebugOverlay::Circle( headPos, tf_bot_npc_head_radius.GetFloat(), 255, 255, 0, 255, true, 5.0f );
  350. }
  351. }
  352. }
  353. }
  354. // take extra damage when stunned
  355. if ( IsInCondition( STUNNED ) )
  356. {
  357. info.SetDamage( info.GetDamage() * tf_bot_npc_stunned_injury_multiplier.GetFloat() );
  358. if ( m_ouchTimer.IsElapsed() )
  359. {
  360. m_ouchTimer.Start( 1.0f );
  361. EmitSound( "RobotBoss.Hurt" );
  362. }
  363. }
  364. else if ( info.GetDamageType() & DMG_CRITICAL )
  365. {
  366. // do the critical damage increase
  367. info.SetDamage( info.GetDamage() * TF_DAMAGE_CRIT_MULTIPLIER );
  368. }
  369. // keep a list of everyone who hurt me, and when
  370. if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() && !InSameTeam( info.GetAttacker() ) )
  371. {
  372. CBaseCombatCharacter *attacker = info.GetAttacker()->MyCombatCharacterPointer();
  373. // sentry guns are first class attackers
  374. if ( info.GetInflictor() )
  375. {
  376. CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( info.GetInflictor() );
  377. if ( sentry )
  378. {
  379. attacker = sentry;
  380. }
  381. }
  382. RememberAttacker( attacker, info.GetDamage(), ( info.GetDamageType() & DMG_CRITICAL ) ? true : false );
  383. CTFPlayer *playerAttacker = ToTFPlayer( attacker );
  384. if ( playerAttacker )
  385. {
  386. for( int i=0; i<playerAttacker->m_Shared.GetNumHealers(); ++i )
  387. {
  388. CTFPlayer *medic = ToTFPlayer( playerAttacker->m_Shared.GetHealerByIndex( i ) );
  389. if ( medic )
  390. {
  391. // medics healing my attacker are also considered attackers
  392. RememberAttacker( medic, 0, 0 );
  393. }
  394. }
  395. }
  396. // if we don't have an attack target yet, we do now
  397. if ( !HasAttackTarget() )
  398. {
  399. SetAttackTarget( attacker );
  400. }
  401. }
  402. EmitSound( "TFPlayer.Pain" );
  403. // fire event for client combat text, beep, etc.
  404. IGameEvent *event = gameeventmanager->CreateEvent( "npc_hurt" );
  405. if ( event )
  406. {
  407. event->SetInt( "entindex", entindex() );
  408. event->SetInt( "health", MAX( 0, GetHealth() ) );
  409. event->SetInt( "damageamount", info.GetDamage() );
  410. event->SetBool( "crit", ( info.GetDamageType() & DMG_CRITICAL ) ? true : false );
  411. CTFPlayer *attackerPlayer = ToTFPlayer( info.GetAttacker() );
  412. if ( attackerPlayer )
  413. {
  414. event->SetInt( "attacker_player", attackerPlayer->GetUserID() );
  415. if ( attackerPlayer->GetActiveTFWeapon() )
  416. {
  417. event->SetInt( "weaponid", attackerPlayer->GetActiveTFWeapon()->GetWeaponID() );
  418. }
  419. else
  420. {
  421. event->SetInt( "weaponid", 0 );
  422. }
  423. }
  424. else
  425. {
  426. // hurt by world
  427. event->SetInt( "attacker_player", 0 );
  428. event->SetInt( "weaponid", 0 );
  429. }
  430. gameeventmanager->FireEvent( event );
  431. }
  432. int result = BaseClass::OnTakeDamage_Alive( info );
  433. if ( g_pMonsterResource )
  434. {
  435. g_pMonsterResource->SetBossHealthPercentage( (float)GetHealth() / (float)GetMaxHealth() );
  436. }
  437. return result;
  438. }
  439. //---------------------------------------------------------------------------------------------
  440. // Returns true if we're in a condition that means we can't start another action
  441. bool CBotNPC::IsBusy( void ) const
  442. {
  443. return IsInCondition( (Condition)( CHARGING | STUNNED | VULNERABLE_TO_STUN | BUSY ) );
  444. }
  445. //---------------------------------------------------------------------------------------------
  446. void CBotNPC::RememberAttacker( CBaseCombatCharacter *attacker, float damage, bool wasCritical )
  447. {
  448. AttackerInfo attackerInfo;
  449. attackerInfo.m_attacker = attacker;
  450. attackerInfo.m_timestamp = gpGlobals->curtime;
  451. attackerInfo.m_damage = damage;
  452. attackerInfo.m_wasCritical = wasCritical;
  453. m_attackerVector.AddToHead( attackerInfo );
  454. }
  455. //----------------------------------------------------------------------------------
  456. CTFPlayer *CBotNPC::GetClosestMinionPrisoner( void )
  457. {
  458. CUtlVector< CBotNPCMinion * > minionVector;
  459. CBotNPCMinion *minion = NULL;
  460. while( ( minion = (CBotNPCMinion *)gEntList.FindEntityByClassname( minion, "bot_npc_minion" ) ) != NULL )
  461. {
  462. minionVector.AddToTail( minion );
  463. }
  464. CTFPlayer *closeCapture = NULL;
  465. float captureRangeSq = FLT_MAX;
  466. for( int m=0; m<minionVector.Count(); ++m )
  467. {
  468. minion = minionVector[m];
  469. if ( minion->HasTarget() )
  470. {
  471. CTFPlayer *victim = minion->GetTarget();
  472. if ( victim->m_Shared.InCond( TF_COND_STUNNED ) )
  473. {
  474. // they've got one!
  475. float rangeSq = GetRangeSquaredTo( victim );
  476. if ( rangeSq < captureRangeSq )
  477. {
  478. closeCapture = victim;
  479. captureRangeSq = rangeSq;
  480. }
  481. }
  482. }
  483. }
  484. return closeCapture;
  485. }
  486. //----------------------------------------------------------------------------------
  487. bool CBotNPC::IsPrisonerOfMinion( CBaseCombatCharacter *victim )
  488. {
  489. if ( !victim->IsPlayer() )
  490. {
  491. return false;
  492. }
  493. CUtlVector< CBotNPCMinion * > minionVector;
  494. CBotNPCMinion *minion = NULL;
  495. while( ( minion = (CBotNPCMinion *)gEntList.FindEntityByClassname( minion, "bot_npc_minion" ) ) != NULL )
  496. {
  497. minionVector.AddToTail( minion );
  498. }
  499. for( int m=0; m<minionVector.Count(); ++m )
  500. {
  501. minion = minionVector[m];
  502. if ( minion->HasTarget() && minion->GetTarget() == victim )
  503. {
  504. if ( minion->GetTarget()->m_Shared.InCond( TF_COND_STUNNED ) )
  505. {
  506. return true;
  507. }
  508. }
  509. }
  510. return false;
  511. }
  512. //----------------------------------------------------------------------------------
  513. void CBotNPC::UpdateDamagePerSecond( void )
  514. {
  515. m_lastDamagePerSecond = m_currentDamagePerSecond;
  516. m_currentDamagePerSecond = 0.0f;
  517. const float windowDuration = 10.0f; // 5.0f;
  518. int i;
  519. m_threatVector.RemoveAll();
  520. for( i=0; i<m_attackerVector.Count(); ++i )
  521. {
  522. float age = gpGlobals->curtime - m_attackerVector[i].m_timestamp;
  523. if ( age > windowDuration )
  524. {
  525. // too old
  526. break;
  527. }
  528. float decayedDamage = ( ( windowDuration - age ) / windowDuration ) * m_attackerVector[i].m_damage;
  529. m_currentDamagePerSecond += decayedDamage;
  530. CBaseCombatCharacter *attacker = m_attackerVector[i].m_attacker;
  531. if ( attacker && attacker->IsAlive() )
  532. {
  533. int j;
  534. for( j=0; j<m_threatVector.Count(); ++j )
  535. {
  536. if ( m_threatVector[j].m_who == attacker )
  537. {
  538. m_threatVector[j].m_threat += decayedDamage;
  539. break;
  540. }
  541. }
  542. if ( j >= m_threatVector.Count() )
  543. {
  544. // new threat
  545. ThreatInfo threat;
  546. threat.m_who = attacker;
  547. threat.m_threat = decayedDamage;
  548. m_threatVector.AddToTail( threat );
  549. }
  550. }
  551. }
  552. // if ( m_currentDamagePerSecond > 0.0001f )
  553. // {
  554. // DevMsg( "%3.2f: dps = %3.2f\n", gpGlobals->curtime, m_currentDamagePerSecond );
  555. // }
  556. }
  557. //----------------------------------------------------------------------------------
  558. const CBotNPC::ThreatInfo *CBotNPC::GetMaxThreat( void ) const
  559. {
  560. int maxThreatIndex = -1;
  561. for( int i=0; i<m_threatVector.Count(); ++i )
  562. {
  563. if ( maxThreatIndex < 0 || m_threatVector[i].m_threat > m_threatVector[ maxThreatIndex ].m_threat )
  564. {
  565. maxThreatIndex = i;
  566. }
  567. }
  568. if ( maxThreatIndex < 0 )
  569. {
  570. // no threat yet
  571. return NULL;
  572. }
  573. return &m_threatVector[ maxThreatIndex ];
  574. }
  575. //----------------------------------------------------------------------------------
  576. const CBotNPC::ThreatInfo *CBotNPC::GetThreat( CBaseCombatCharacter *who ) const
  577. {
  578. for( int i=0; i<m_threatVector.Count(); ++i )
  579. {
  580. if ( m_threatVector[i].m_who == who )
  581. {
  582. return &m_threatVector[i];
  583. }
  584. }
  585. return NULL;
  586. }
  587. //----------------------------------------------------------------------------------
  588. void CBotNPC::UpdateAttackTarget( void )
  589. {
  590. if ( m_isAttackTargetLocked && HasAttackTarget() )
  591. {
  592. return;
  593. }
  594. // who is most dangerous to me at the moment
  595. const ThreatInfo *maxThreat = GetMaxThreat();
  596. if ( !maxThreat )
  597. {
  598. // nobody is hurting me at the moment
  599. if ( HasAttackTarget() )
  600. {
  601. // stay focused on current target
  602. return;
  603. }
  604. // we have no current target, either
  605. // if my minions have captured someone, go get them
  606. CTFPlayer *closeCapture = GetClosestMinionPrisoner();
  607. if ( closeCapture )
  608. {
  609. SetAttackTarget( closeCapture );
  610. return;
  611. }
  612. // if we see an enemy, attack them
  613. CBaseCombatCharacter *visible = GetNearestVisibleEnemy();
  614. if ( visible )
  615. {
  616. SetAttackTarget( visible );
  617. }
  618. return;
  619. }
  620. // we are under attack, if we don't have a target, attack the highest threat
  621. if ( !HasAttackTarget() )
  622. {
  623. SetAttackTarget( maxThreat->m_who );
  624. return;
  625. }
  626. if ( IsAttackTarget( maxThreat->m_who ) )
  627. {
  628. // our current target is still dealing the most damage to us
  629. return;
  630. }
  631. // switch to new threat if is is more dangerous
  632. const ThreatInfo *attackTargetThreat = GetThreat( GetAttackTarget() );
  633. if ( !attackTargetThreat || maxThreat->m_threat > attackTargetThreat->m_threat + tf_bot_npc_threat_tolerance.GetFloat() )
  634. {
  635. // change threats
  636. SetAttackTarget( maxThreat->m_who );
  637. }
  638. }
  639. //----------------------------------------------------------------------------------
  640. void CBotNPC::RemoveCondition( Condition c )
  641. {
  642. if ( c == STUNNED )
  643. {
  644. // reset the accumulator
  645. ClearStunDamage();
  646. }
  647. m_conditionFlags &= ~c;
  648. }
  649. //----------------------------------------------------------------------------------
  650. void CBotNPC::SwingAxe( void )
  651. {
  652. if ( !IsSwingingAxe() )
  653. {
  654. AddGesture( ACT_MP_ATTACK_STAND_ITEM1 );
  655. m_axeSwingTimer.Start( 0.58f );
  656. EmitSound( "Weapon_Sword.Swing" );
  657. }
  658. }
  659. //----------------------------------------------------------------------------------
  660. void CBotNPC::UpdateAxeSwing( void )
  661. {
  662. if ( !m_axeSwingTimer.HasStarted() )
  663. {
  664. return;
  665. }
  666. // continue axe swing
  667. if ( !m_axeSwingTimer.IsElapsed() )
  668. {
  669. return;
  670. }
  671. // moment of impact - did axe swing hit?
  672. m_axeSwingTimer.Invalidate();
  673. CBaseCombatCharacter *victim = GetAttackTarget();
  674. if ( victim )
  675. {
  676. Vector forward;
  677. GetVectors( &forward, NULL, NULL );
  678. Vector toVictim = victim->WorldSpaceCenter() - WorldSpaceCenter();
  679. toVictim.NormalizeInPlace();
  680. if ( DotProduct( forward, toVictim ) > 0.7071f )
  681. {
  682. if ( IsRangeLessThan( victim, 0.9f * tf_bot_npc_attack_range.GetFloat() ) )
  683. {
  684. if ( IsLineOfSightClear( victim ) )
  685. {
  686. // CHOP!
  687. CTakeDamageInfo info( this, this, tf_bot_npc_melee_damage.GetFloat(), DMG_SLASH, TF_DMG_CUSTOM_NONE );
  688. CalculateMeleeDamageForce( &info, toVictim, WorldSpaceCenter(), 1.0f );
  689. victim->TakeDamage( info );
  690. EmitSound( "Weapon_Sword.HitFlesh" );
  691. return;
  692. }
  693. }
  694. }
  695. }
  696. EmitSound( "Weapon_Sword.HitWorld" );
  697. }
  698. //----------------------------------------------------------------------------------
  699. bool CBotNPC::IsSwingingAxe( void ) const
  700. {
  701. return const_cast< CBotNPC * >( this )->IsPlayingGesture( ACT_MP_ATTACK_STAND_ITEM1 );
  702. }
  703. //---------------------------------------------------------------------------------------------
  704. void CBotNPC::Update( void )
  705. {
  706. BaseClass::Update();
  707. UpdateNearestVisibleEnemy();
  708. UpdateAxeSwing();
  709. UpdateDamagePerSecond();
  710. UpdateAttackTarget();
  711. if ( m_damagePoseParameter < 0 )
  712. {
  713. m_damagePoseParameter = LookupPoseParameter( "damage" );
  714. }
  715. if ( m_damagePoseParameter >= 0 )
  716. {
  717. SetPoseParameter( m_damagePoseParameter, 1.0f - ( (float)GetHealth() / (float)GetMaxHealth() ) );
  718. }
  719. // chase down players who taunt me
  720. if ( m_hateTauntTimer.IsElapsed() )
  721. {
  722. CUtlVector< CTFPlayer * > playerVector;
  723. CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS );
  724. for( int i=0; i<playerVector.Count(); ++i )
  725. {
  726. if ( playerVector[i]->IsTaunting() )
  727. {
  728. m_hateTauntTimer.Start( tf_bot_npc_hate_taunt_cooldown.GetFloat() );
  729. if ( IsLineOfSightClear( playerVector[i], IGNORE_ACTORS ) )
  730. {
  731. // the taunter becomes our new attack target
  732. SetAttackTarget( playerVector[i], tf_bot_npc_hate_taunt_cooldown.GetFloat() );
  733. }
  734. }
  735. }
  736. }
  737. }
  738. //---------------------------------------------------------------------------------------------
  739. bool CBotNPC::IsPotentiallyChaseable( CTFPlayer *victim )
  740. {
  741. if ( !victim )
  742. {
  743. return false;
  744. }
  745. if ( !victim->IsAlive() )
  746. {
  747. // victim is dead - pick a new one
  748. return false;
  749. }
  750. CTFNavArea *victimArea = (CTFNavArea *)victim->GetLastKnownArea();
  751. if ( !victimArea || victimArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
  752. {
  753. // unreachable - pick a new victim
  754. return false;
  755. }
  756. if ( victim->GetGroundEntity() != NULL )
  757. {
  758. Vector victimAreaPos;
  759. victimArea->GetClosestPointOnArea( victim->GetAbsOrigin(), &victimAreaPos );
  760. if ( ( victim->GetAbsOrigin() - victimAreaPos ).AsVector2D().IsLengthGreaterThan( 50.0f ) )
  761. {
  762. // off the mesh and unreachable - pick a new victim
  763. return false;
  764. }
  765. }
  766. if ( victim->m_Shared.IsInvulnerable() )
  767. {
  768. // invulnerable - pick a new victim
  769. return false;
  770. }
  771. Vector toHome = m_homePos - victim->GetAbsOrigin();
  772. if ( toHome.IsLengthGreaterThan( tf_bot_npc_quit_range.GetFloat() ) )
  773. {
  774. // too far from home - pick a new victim
  775. return false;
  776. }
  777. return true;
  778. }
  779. //---------------------------------------------------------------------------------------------
  780. bool CBotNPC::IsIgnored( CTFPlayer *player ) const
  781. {
  782. if ( player->m_Shared.IsStealthed() )
  783. {
  784. if ( player->m_Shared.GetPercentInvisible() < 0.75f )
  785. {
  786. // spy is partially cloaked, and therefore attracts our attention
  787. return false;
  788. }
  789. if ( player->m_Shared.InCond( TF_COND_BURNING ) ||
  790. player->m_Shared.InCond( TF_COND_URINE ) ||
  791. player->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
  792. player->m_Shared.InCond( TF_COND_BLEEDING ) )
  793. {
  794. // always notice players with these conditions
  795. return false;
  796. }
  797. // invisible!
  798. return true;
  799. }
  800. return false;
  801. }
  802. //---------------------------------------------------------------------------------------------
  803. void CBotNPC::UpdateNearestVisibleEnemy( void )
  804. {
  805. if ( !m_nearestVisibleEnemyTimer.IsElapsed() )
  806. {
  807. return;
  808. }
  809. m_nearestVisibleEnemyTimer.Start( tf_bot_npc_reaction_time.GetFloat() );
  810. // collect everyone
  811. CUtlVector< CTFPlayer * > playerVector;
  812. //CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
  813. CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
  814. Vector myForward;
  815. GetVectors( &myForward, NULL, NULL );
  816. m_nearestVisibleEnemy = NULL;
  817. float victimRangeSq = FLT_MAX;
  818. for( int i=0; i<playerVector.Count(); ++i )
  819. {
  820. CTFPlayer *victim = playerVector[i];
  821. if ( IsIgnored( victim ) )
  822. {
  823. continue;
  824. }
  825. float rangeSq = GetRangeSquaredTo( playerVector[i] );
  826. if ( rangeSq < victimRangeSq )
  827. {
  828. // FOV check
  829. Vector to = playerVector[i]->WorldSpaceCenter() - WorldSpaceCenter();
  830. to.NormalizeInPlace();
  831. if ( DotProduct( to, myForward ) > -0.7071f )
  832. {
  833. if ( IsLineOfSightClear( playerVector[i] ) )
  834. {
  835. m_nearestVisibleEnemy = playerVector[i];
  836. victimRangeSq = rangeSq;
  837. }
  838. }
  839. }
  840. }
  841. }
  842. //---------------------------------------------------------------------------------------------
  843. void CBotNPC::SetAttackTarget( CBaseCombatCharacter *target, float duration )
  844. {
  845. if ( target && m_attackTarget != NULL && m_attackTarget->IsAlive() && m_attackTargetTimer.HasStarted() && !m_attackTargetTimer.IsElapsed() )
  846. {
  847. // can't switch away from our still valid target yet
  848. return;
  849. }
  850. if ( m_attackTarget != target )
  851. {
  852. if ( target )
  853. {
  854. EmitSound( "RobotBoss.Acquire" );
  855. AddGesture( ACT_MP_GESTURE_FLINCH_CHEST );
  856. }
  857. TFGameRules()->SetIT( m_attackTarget );
  858. m_attackTarget = target;
  859. }
  860. if ( duration > 0.0f )
  861. {
  862. m_attackTargetTimer.Start( duration );
  863. }
  864. else
  865. {
  866. m_attackTargetTimer.Invalidate();
  867. }
  868. }
  869. //---------------------------------------------------------------------------------------------
  870. CBaseCombatCharacter *CBotNPC::GetAttackTarget( void ) const
  871. {
  872. if ( m_attackTarget != NULL && m_attackTarget->IsAlive() )
  873. {
  874. return m_attackTarget;
  875. }
  876. return NULL;
  877. }
  878. //---------------------------------------------------------------------------------------------
  879. void CBotNPC::Break( void )
  880. {
  881. CPVSFilter filter( GetAbsOrigin() );
  882. UserMessageBegin( filter, "BreakModel" );
  883. WRITE_SHORT( GetModelIndex() );
  884. WRITE_VEC3COORD( GetAbsOrigin() );
  885. WRITE_ANGLES( GetAbsAngles() );
  886. WRITE_SHORT( GetSkin() );
  887. MessageEnd();
  888. }
  889. //---------------------------------------------------------------------------------------------
  890. void CBotNPC::CollectPlayersStandingOnMe( CUtlVector< CTFPlayer * > *playerVector )
  891. {
  892. CUtlVector< CTFPlayer * > allPlayerVector;
  893. CollectPlayers( &allPlayerVector, TEAM_ANY, COLLECT_ONLY_LIVING_PLAYERS );
  894. for( int i=0; i<allPlayerVector.Count(); ++i )
  895. {
  896. CTFPlayer *player = allPlayerVector[i];
  897. if ( player->GetGroundEntity() == this )
  898. {
  899. playerVector->AddToTail( player );
  900. }
  901. }
  902. }
  903. //---------------------------------------------------------------------------------------------
  904. //---------------------------------------------------------------------------------------------
  905. class CBotNPCStunned : public Action< CBotNPC >
  906. {
  907. public:
  908. CBotNPCStunned( float duration, Action< CBotNPC > *nextAction = NULL );
  909. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  910. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  911. virtual void OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction );
  912. virtual EventDesiredResult< CBotNPC > OnInjured( CBotNPC *me, const CTakeDamageInfo &info );
  913. virtual const char *GetName( void ) const { return "Stunned"; } // return name of this action
  914. private:
  915. CountdownTimer m_timer;
  916. enum StunStateType
  917. {
  918. BECOMING_STUNNED,
  919. STUNNED,
  920. RECOVERING
  921. }
  922. m_state;
  923. int m_layerUsed;
  924. Action< CBotNPC > *m_nextAction;
  925. };
  926. //---------------------------------------------------------------------------------------------
  927. CBotNPCStunned::CBotNPCStunned( float duration, Action< CBotNPC > *nextAction )
  928. {
  929. m_timer.Start( duration );
  930. m_nextAction = nextAction;
  931. }
  932. //---------------------------------------------------------------------------------------------
  933. ConVar tf_bot_npc_stun_ammo_count( "tf_bot_npc_stun_ammo_count", "3"/*, FCVAR_CHEAT*/ );
  934. ConVar tf_bot_npc_stun_ammo_amount( "tf_bot_npc_stun_ammo_amount", "100"/*, FCVAR_CHEAT*/ );
  935. ConVar tf_bot_npc_stun_ammo_velocity( "tf_bot_npc_stun_ammo_velocity", "100"/*, FCVAR_CHEAT*/ );
  936. void TossAmmoPack( CBotNPC *me )
  937. {
  938. int iPrimary = tf_bot_npc_stun_ammo_amount.GetInt();
  939. int iSecondary = tf_bot_npc_stun_ammo_amount.GetInt();
  940. int iMetal = tf_bot_npc_stun_ammo_amount.GetInt();
  941. // Create the ammo pack.
  942. CTFAmmoPack *pAmmoPack = CTFAmmoPack::Create( me->GetAbsOrigin(), me->GetAbsAngles(), NULL, "models/items/ammopack_medium.mdl" );
  943. if ( pAmmoPack )
  944. {
  945. /*
  946. Vector vel;
  947. vel.x = RandomFloat( -1.0f, 1.0f ) * tf_bot_npc_stun_ammo_velocity.GetFloat();
  948. vel.y = RandomFloat( -1.0f, 1.0f ) * tf_bot_npc_stun_ammo_velocity.GetFloat();
  949. vel.z = tf_bot_npc_stun_ammo_velocity.GetFloat();
  950. pAmmoPack->SetInitialVelocity( vel );
  951. */
  952. pAmmoPack->m_nSkin = 0;
  953. // Give the ammo pack some health, so that trains can destroy it.
  954. pAmmoPack->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  955. pAmmoPack->m_takedamage = DAMAGE_YES;
  956. pAmmoPack->SetHealth( 900 );
  957. pAmmoPack->SetBodygroup( 1, 1 );
  958. pAmmoPack->ApplyLocalAngularVelocityImpulse( AngularImpulse( 600, random->RandomInt( -1200, 1200 ), 0 ) );
  959. DispatchSpawn( pAmmoPack );
  960. // Fill up the ammo pack.
  961. pAmmoPack->GiveAmmo( iPrimary, TF_AMMO_PRIMARY );
  962. pAmmoPack->GiveAmmo( iSecondary, TF_AMMO_SECONDARY );
  963. pAmmoPack->GiveAmmo( iMetal, TF_AMMO_METAL );
  964. }
  965. }
  966. //---------------------------------------------------------------------------------------------
  967. ActionResult< CBotNPC > CBotNPCStunned::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  968. {
  969. // start animation
  970. me->GetBodyInterface()->StartActivity( ACT_MP_STAND_MELEE );
  971. m_layerUsed = me->AddLayeredSequence( me->LookupSequence( "PRIMARY_Stun_begin" ), 0 );
  972. m_state = BECOMING_STUNNED;
  973. m_timer.Reset();
  974. me->AddCondition( CBotNPC::STUNNED );
  975. me->EmitSound( "RobotBoss.StunStart" );
  976. // throw out some ammo
  977. for( int i=0; i<tf_bot_npc_stun_ammo_count.GetInt(); ++i )
  978. {
  979. TossAmmoPack( me );
  980. }
  981. me->m_outputOnStunned.FireOutput( me, me );
  982. // relay the event to the map logic
  983. CTFSpawnerBoss *spawner = me->GetSpawner();
  984. if ( spawner )
  985. {
  986. spawner->OnBotStunned( me );
  987. }
  988. return Continue();
  989. }
  990. //---------------------------------------------------------------------------------------------
  991. ActionResult< CBotNPC > CBotNPCStunned::Update( CBotNPC *me, float interval )
  992. {
  993. switch( m_state )
  994. {
  995. case BECOMING_STUNNED:
  996. if ( me->IsSequenceFinished() )
  997. {
  998. me->FastRemoveLayer( m_layerUsed );
  999. m_state = STUNNED;
  1000. m_layerUsed = me->AddLayeredSequence( me->LookupSequence( "PRIMARY_stun_middle" ), 0 );
  1001. me->SetLayerLooping( m_layerUsed, true );
  1002. me->EmitSound( "RobotBoss.Stunned" );
  1003. }
  1004. break;
  1005. case STUNNED:
  1006. if ( m_timer.IsElapsed() )
  1007. {
  1008. me->FastRemoveLayer( m_layerUsed );
  1009. m_state = RECOVERING;
  1010. m_layerUsed = me->AddLayeredSequence( me->LookupSequence( "PRIMARY_stun_end" ), 0 );
  1011. me->StopSound( "RobotBoss.Stunned" );
  1012. me->EmitSound( "RobotBoss.StunRecover" );
  1013. }
  1014. break;
  1015. case RECOVERING:
  1016. if ( me->IsSequenceFinished() )
  1017. {
  1018. me->FastRemoveLayer( m_layerUsed );
  1019. if ( m_nextAction )
  1020. {
  1021. return ChangeTo( m_nextAction, "Stun finished" );
  1022. }
  1023. return Done( "Stun finished" );
  1024. }
  1025. break;
  1026. }
  1027. return Continue();
  1028. }
  1029. //---------------------------------------------------------------------------------------------
  1030. EventDesiredResult< CBotNPC > CBotNPCStunned::OnInjured( CBotNPC *me, const CTakeDamageInfo &info )
  1031. {
  1032. return TryToSustain( RESULT_CRITICAL );
  1033. }
  1034. //---------------------------------------------------------------------------------------------
  1035. void CBotNPCStunned::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  1036. {
  1037. me->RemoveCondition( CBotNPC::STUNNED );
  1038. if ( me->HasAbility( CBotNPC::CAN_ENRAGE ) )
  1039. {
  1040. // being stunned makes the boss ANGRY!
  1041. me->AddCondition( CBotNPC::ENRAGED );
  1042. }
  1043. // make sure the boss attacks at least once before he starts a nuke
  1044. if ( me->GetNukeTimer()->GetRemainingTime() < tf_bot_npc_min_nuke_after_stun_time.GetFloat() )
  1045. {
  1046. me->GetNukeTimer()->Start( tf_bot_npc_min_nuke_after_stun_time.GetFloat() );
  1047. }
  1048. }
  1049. //---------------------------------------------------------------------------------------------
  1050. //---------------------------------------------------------------------------------------------
  1051. class CBotNPCBigJump : public Action< CBotNPC >
  1052. {
  1053. public:
  1054. CBotNPCBigJump( const Vector &destination, Action< CBotNPC > *nextAction = NULL );
  1055. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  1056. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  1057. virtual void OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction );
  1058. virtual EventDesiredResult< CBotNPC > OnInjured( CBotNPC *me, const CTakeDamageInfo &info );
  1059. virtual const char *GetName( void ) const { return "Jump"; } // return name of this action
  1060. private:
  1061. enum StunStateType
  1062. {
  1063. JUMPING_UP,
  1064. FLOATING_UP,
  1065. FALLING_DOWN
  1066. }
  1067. m_state;
  1068. CountdownTimer m_timer;
  1069. Vector m_destination;
  1070. Action< CBotNPC > *m_nextAction;
  1071. };
  1072. //---------------------------------------------------------------------------------------------
  1073. CBotNPCBigJump::CBotNPCBigJump( const Vector &destination, Action< CBotNPC > *nextAction )
  1074. {
  1075. m_destination = destination;
  1076. m_nextAction = nextAction;
  1077. }
  1078. //---------------------------------------------------------------------------------------------
  1079. ActionResult< CBotNPC > CBotNPCBigJump::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  1080. {
  1081. // start animation
  1082. me->GetBodyInterface()->StartActivity( ACT_MP_JUMP_START_MELEE );
  1083. m_state = JUMPING_UP;
  1084. m_timer.Start( 3.0f );
  1085. // disconnect us from the ground
  1086. me->GetLocomotionInterface()->Jump();
  1087. return Continue();
  1088. }
  1089. //---------------------------------------------------------------------------------------------
  1090. ActionResult< CBotNPC > CBotNPCBigJump::Update( CBotNPC *me, float interval )
  1091. {
  1092. // animation state
  1093. switch( m_state )
  1094. {
  1095. case JUMPING_UP:
  1096. if ( me->IsSequenceFinished() )
  1097. {
  1098. me->GetBodyInterface()->StartActivity( ACT_MP_JUMP_FLOAT_MELEE );
  1099. m_state = FLOATING_UP;
  1100. }
  1101. break;
  1102. }
  1103. // movement
  1104. switch( m_state )
  1105. {
  1106. case JUMPING_UP:
  1107. case FLOATING_UP:
  1108. me->GetLocomotionInterface()->SetVelocity( Vector( 0, 0, 1200.0f ) );
  1109. if ( m_timer.IsElapsed() )
  1110. {
  1111. m_state = FALLING_DOWN;
  1112. // move so we fall on our destination point
  1113. me->SetAbsOrigin( m_destination + Vector( 0, 0, 1300.0f ) );
  1114. me->GetLocomotionInterface()->SetVelocity( vec3_origin );
  1115. }
  1116. break;
  1117. case FALLING_DOWN:
  1118. if ( me->GetLocomotionInterface()->IsOnGround() )
  1119. {
  1120. me->AddGesture( ACT_MP_JUMP_LAND_MELEE );
  1121. if ( m_nextAction )
  1122. {
  1123. return ChangeTo( m_nextAction, "Finished jump" );
  1124. }
  1125. return Done( "Finished jump" );
  1126. }
  1127. break;
  1128. }
  1129. return Continue();
  1130. }
  1131. //---------------------------------------------------------------------------------------------
  1132. EventDesiredResult< CBotNPC > CBotNPCBigJump::OnInjured( CBotNPC *me, const CTakeDamageInfo &info )
  1133. {
  1134. return TryToSustain( RESULT_CRITICAL );
  1135. }
  1136. //---------------------------------------------------------------------------------------------
  1137. void CBotNPCBigJump::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  1138. {
  1139. }
  1140. //---------------------------------------------------------------------------------------------
  1141. //---------------------------------------------------------------------------------------------
  1142. class CBotNPCLaunchMinions : public Action< CBotNPC >
  1143. {
  1144. public:
  1145. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  1146. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  1147. // if anything interrupts this action, abort it
  1148. virtual ActionResult< CBotNPC > OnSuspend( CBotNPC *me, Action< CBotNPC > *interruptingAction ) { return Done(); }
  1149. virtual const char *GetName( void ) const { return "LaunchMinions"; } // return name of this action
  1150. private:
  1151. CountdownTimer m_timer;
  1152. int m_minionsLeft;
  1153. bool SpawnMinion( CBotNPC *me );
  1154. };
  1155. //---------------------------------------------------------------------------------------------
  1156. ActionResult< CBotNPC > CBotNPCLaunchMinions::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  1157. {
  1158. // start animation
  1159. me->GetBodyInterface()->StartActivity( ACT_MP_STAND_SECONDARY );
  1160. me->AddGestureSequence( me->LookupSequence( "taunt01" ) );
  1161. m_timer.Start( 4.0f );
  1162. int bonus = (int)( me->GetAge() / tf_bot_npc_minion_launch_count_increase_interval.GetFloat() );
  1163. m_minionsLeft = tf_bot_npc_minion_launch_count_initial.GetInt() + bonus;
  1164. return Continue();
  1165. }
  1166. //---------------------------------------------------------------------------------------------
  1167. bool CBotNPCLaunchMinions::SpawnMinion( CBotNPC *me )
  1168. {
  1169. Vector spawnSpot = me->WorldSpaceCenter();
  1170. Vector headPos;
  1171. QAngle headAngles;
  1172. if ( me->GetAttachment( "head", headPos, headAngles ) )
  1173. {
  1174. spawnSpot = headPos + RandomVector( -10.0f, 10.0f );
  1175. }
  1176. CBaseCombatCharacter *minion = static_cast< CBaseCombatCharacter * >( CreateEntityByName( "bot_npc_minion" ) );
  1177. if ( minion )
  1178. {
  1179. minion->SetAbsAngles( me->GetAbsAngles() );
  1180. minion->SetAbsOrigin( spawnSpot );
  1181. minion->SetOwnerEntity( me );
  1182. DispatchSpawn( minion );
  1183. return true;
  1184. }
  1185. return false;
  1186. }
  1187. //---------------------------------------------------------------------------------------------
  1188. ActionResult< CBotNPC > CBotNPCLaunchMinions::Update( CBotNPC *me, float interval )
  1189. {
  1190. CBaseCombatCharacter *target = me->GetAttackTarget();
  1191. if ( target )
  1192. {
  1193. me->GetLocomotionInterface()->FaceTowards( target->WorldSpaceCenter() );
  1194. }
  1195. if ( m_timer.IsElapsed() )
  1196. {
  1197. while( m_minionsLeft-- )
  1198. {
  1199. SpawnMinion( me );
  1200. }
  1201. return Done();
  1202. }
  1203. return Continue();
  1204. }
  1205. //---------------------------------------------------------------------------------------------
  1206. //---------------------------------------------------------------------------------------------
  1207. class CBotNPCNukeAttack : public Action< CBotNPC >
  1208. {
  1209. public:
  1210. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  1211. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  1212. virtual void OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction );
  1213. virtual EventDesiredResult< CBotNPC > OnInjured( CBotNPC *me, const CTakeDamageInfo &info );
  1214. virtual const char *GetName( void ) const { return "NukeAttack"; } // return name of this action
  1215. private:
  1216. CountdownTimer m_shakeTimer;
  1217. CountdownTimer m_chargeUpTimer;
  1218. };
  1219. ConVar tf_bot_npc_nuke_damage( "tf_bot_npc_nuke_damage", "75"/*, FCVAR_CHEAT*/ );
  1220. ConVar tf_bot_npc_nuke_max_remaining_health( "tf_bot_npc_nuke_max_remaining_health", "60"/*, FCVAR_CHEAT*/ );
  1221. ConVar tf_bot_npc_nuke_afterburn_time( "tf_bot_npc_nuke_afterburn_time", "5"/*, FCVAR_CHEAT*/ );
  1222. //---------------------------------------------------------------------------------------------
  1223. ActionResult< CBotNPC > CBotNPCNukeAttack::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  1224. {
  1225. me->GetBodyInterface()->StartActivity( ACT_MP_JUMP_FLOAT_LOSERSTATE );
  1226. me->StartNukeEffect();
  1227. me->EmitSound( "RobotBoss.ChargeUpNukeAttack" );
  1228. me->AddCondition( CBotNPC::VULNERABLE_TO_STUN );
  1229. m_chargeUpTimer.Start( tf_bot_npc_nuke_charge_time.GetFloat() );
  1230. m_shakeTimer.Start( 0.25f );
  1231. return Continue();
  1232. }
  1233. //---------------------------------------------------------------------------------------------
  1234. ActionResult< CBotNPC > CBotNPCNukeAttack::Update( CBotNPC *me, float interval )
  1235. {
  1236. float stunRatio = me->GetStunDamage() / me->GetBecomeStunnedDamage();
  1237. if ( me->HasAbility( CBotNPC::CAN_BE_STUNNED ) && stunRatio >= 1.0f )
  1238. {
  1239. return ChangeTo( new CBotNPCStunned( tf_bot_npc_stunned_duration.GetFloat() ), "They got me" );
  1240. }
  1241. // update the client's HUD
  1242. if ( g_pMonsterResource )
  1243. {
  1244. g_pMonsterResource->SetBossStunPercentage( 1.0f - stunRatio );
  1245. }
  1246. if ( m_shakeTimer.IsElapsed() )
  1247. {
  1248. m_shakeTimer.Reset();
  1249. UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 3000.0f, SHAKE_START );
  1250. }
  1251. if ( m_chargeUpTimer.IsElapsed() )
  1252. {
  1253. // BLAST!
  1254. CUtlVector< CTFPlayer * > playerVector;
  1255. CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
  1256. CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
  1257. me->EmitSound( "RobotBoss.NukeAttack" );
  1258. CUtlVector< CBaseCombatCharacter * > victimVector;
  1259. int i;
  1260. // players
  1261. for ( i=0; i<playerVector.Count(); ++i )
  1262. {
  1263. CBasePlayer *player = playerVector[i];
  1264. if ( player && player->IsAlive() && player->GetTeamNumber() == TF_TEAM_BLUE )
  1265. {
  1266. victimVector.AddToTail( player );
  1267. }
  1268. }
  1269. // objects
  1270. CTFTeam *team = GetGlobalTFTeam( TF_TEAM_BLUE );
  1271. if ( team )
  1272. {
  1273. for ( i=0; i<team->GetNumObjects(); ++i )
  1274. {
  1275. CBaseObject *object = team->GetObject( i );
  1276. if ( object )
  1277. {
  1278. victimVector.AddToTail( object );
  1279. }
  1280. }
  1281. }
  1282. #ifdef SKIPME
  1283. team = GetGlobalTFTeam( TF_TEAM_RED );
  1284. if ( team )
  1285. {
  1286. for ( i=0; i<team->GetNumObjects(); ++i )
  1287. {
  1288. CBaseObject *object = team->GetObject( i );
  1289. if ( object )
  1290. {
  1291. victimVector.AddToTail( object );
  1292. }
  1293. }
  1294. }
  1295. // non-player bots
  1296. CUtlVector< INextBot * > botVector;
  1297. TheNextBots().CollectAllBots( &botVector );
  1298. for( i=0; i<botVector.Count(); ++i )
  1299. {
  1300. CBaseCombatCharacter *bot = botVector[i]->GetEntity();
  1301. if ( !bot->IsPlayer() && bot->IsAlive() )
  1302. {
  1303. victimVector.AddToTail( bot );
  1304. }
  1305. }
  1306. #endif // SKIPME
  1307. for( int i=0; i<victimVector.Count(); ++i )
  1308. {
  1309. CBaseCombatCharacter *victim = victimVector[i];
  1310. if ( me->IsSelf( victim ) )
  1311. continue;
  1312. if ( me->IsLineOfSightClear( victim ) )
  1313. {
  1314. Vector toVictim = victim->WorldSpaceCenter() - me->WorldSpaceCenter();
  1315. toVictim.NormalizeInPlace();
  1316. float damage = tf_bot_npc_nuke_damage.GetFloat();
  1317. if ( me->GetAge() > tf_bot_npc_nuke_lethal_time.GetFloat() )
  1318. {
  1319. // nuke is now lethal
  1320. damage = 999.9f;
  1321. }
  1322. else if ( tf_bot_npc_nuke_max_remaining_health.GetFloat() >= 0.0f )
  1323. {
  1324. // nuke slams everyone's health to this
  1325. if ( victim->GetHealth() > tf_bot_npc_nuke_max_remaining_health.GetFloat() )
  1326. {
  1327. damage = victim->GetHealth() - tf_bot_npc_nuke_max_remaining_health.GetFloat();
  1328. }
  1329. }
  1330. CTakeDamageInfo info( me, me, damage, DMG_ENERGYBEAM, TF_DMG_CUSTOM_NONE );
  1331. CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 1.0f );
  1332. victim->TakeDamage( info );
  1333. if ( victim->IsPlayer() )
  1334. {
  1335. CTFPlayer *playerVictim = ToTFPlayer( victim );
  1336. // catch them on fire (unless they are a Pyro)
  1337. if ( !playerVictim->IsPlayerClass( TF_CLASS_PYRO ) )
  1338. {
  1339. playerVictim->m_Shared.Burn( me, tf_bot_npc_nuke_afterburn_time.GetFloat() );
  1340. }
  1341. color32 colorHit = { 255, 255, 255, 255 };
  1342. UTIL_ScreenFade( victim, colorHit, 1.0f, 0.1f, FFADE_IN );
  1343. }
  1344. }
  1345. }
  1346. return Done();
  1347. }
  1348. return Continue();
  1349. }
  1350. //---------------------------------------------------------------------------------------------
  1351. void CBotNPCNukeAttack::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  1352. {
  1353. me->RemoveCondition( CBotNPC::VULNERABLE_TO_STUN );
  1354. me->StopNukeEffect();
  1355. me->ClearStunDamage();
  1356. me->GetNukeTimer()->Start( tf_bot_npc_nuke_interval.GetFloat() );
  1357. if ( g_pMonsterResource )
  1358. {
  1359. g_pMonsterResource->HideBossStunMeter();
  1360. }
  1361. }
  1362. //---------------------------------------------------------------------------------------------
  1363. EventDesiredResult< CBotNPC > CBotNPCNukeAttack::OnInjured( CBotNPC *me, const CTakeDamageInfo &info )
  1364. {
  1365. return TryToSustain( RESULT_CRITICAL );
  1366. }
  1367. //---------------------------------------------------------------------------------------------
  1368. //---------------------------------------------------------------------------------------------
  1369. class CBotNPCLaunchRockets : public Action< CBotNPC >
  1370. {
  1371. public:
  1372. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  1373. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  1374. virtual void OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction );
  1375. // if anything interrupts this action, abort it
  1376. virtual ActionResult< CBotNPC > OnSuspend( CBotNPC *me, Action< CBotNPC > *interruptingAction ) { return Done(); }
  1377. virtual const char *GetName( void ) const { return "LaunchRockets"; } // return name of this action
  1378. private:
  1379. CountdownTimer m_timer;
  1380. CountdownTimer m_launchTimer;
  1381. int m_rocketsLeft;
  1382. int m_animLayer;
  1383. CHandle< CBaseCombatCharacter > m_target;
  1384. Vector m_lastTargetPosition;
  1385. };
  1386. //---------------------------------------------------------------------------------------------
  1387. ActionResult< CBotNPC > CBotNPCLaunchRockets::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  1388. {
  1389. // start animation
  1390. me->GetBodyInterface()->StartActivity( ACT_MP_STAND_SECONDARY );
  1391. m_animLayer = me->AddLayeredSequence( me->LookupSequence( "taunt02" ), 0 );
  1392. m_timer.Start( 1.0f );
  1393. m_rocketsLeft = me->GetRocketLaunchCount();
  1394. me->AddCondition( CBotNPC::BUSY );
  1395. me->LockAttackTarget();
  1396. me->EmitSound( "RobotBoss.LaunchRockets" );
  1397. if ( me->GetAttackTarget() == NULL )
  1398. {
  1399. return Done( "No target" );
  1400. }
  1401. m_target = me->GetAttackTarget();
  1402. m_lastTargetPosition = m_target->WorldSpaceCenter();
  1403. return Continue();
  1404. }
  1405. //---------------------------------------------------------------------------------------------
  1406. ActionResult< CBotNPC > CBotNPCLaunchRockets::Update( CBotNPC *me, float interval )
  1407. {
  1408. if ( m_target != NULL )
  1409. {
  1410. m_lastTargetPosition = m_target->WorldSpaceCenter();
  1411. }
  1412. me->GetLocomotionInterface()->FaceTowards( m_lastTargetPosition );
  1413. if ( m_timer.IsElapsed() && m_launchTimer.IsElapsed() )
  1414. {
  1415. if ( !m_rocketsLeft )
  1416. {
  1417. return Done();
  1418. }
  1419. --m_rocketsLeft;
  1420. m_launchTimer.Start( me->GetRocketInterval() );
  1421. QAngle launchAngles = me->GetAbsAngles();
  1422. if ( m_target == NULL )
  1423. {
  1424. Vector to = m_lastTargetPosition - me->WorldSpaceCenter();
  1425. VectorAngles( to, launchAngles );
  1426. }
  1427. else
  1428. {
  1429. float range = me->GetRangeTo( m_target->EyePosition() );
  1430. const float rocketSpeed = me->GetRocketAimError() * 1100.0f; // 2000.0f; // 1100.0f; nerfing accuracy
  1431. float flightTime = range / rocketSpeed;
  1432. Vector aimSpot = m_target->EyePosition() + m_target->GetAbsVelocity() * flightTime;
  1433. Vector to = aimSpot - me->WorldSpaceCenter();
  1434. VectorAngles( to, launchAngles );
  1435. }
  1436. CTFProjectile_Rocket *pRocket = CTFProjectile_Rocket::Create( me, me->WorldSpaceCenter(), launchAngles, me, me );
  1437. if ( pRocket )
  1438. {
  1439. if ( me->IsInCondition( CBotNPC::ENRAGED ) )
  1440. {
  1441. pRocket->SetCritical( true );
  1442. pRocket->EmitSound( "Weapon_RPG.SingleCrit" );
  1443. }
  1444. else
  1445. {
  1446. me->EmitSound( me->GetRocketSoundEffect() );
  1447. }
  1448. pRocket->SetDamage( me->GetRocketDamage() );
  1449. }
  1450. }
  1451. return Continue();
  1452. }
  1453. //---------------------------------------------------------------------------------------------
  1454. void CBotNPCLaunchRockets::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  1455. {
  1456. me->RemoveCondition( CBotNPC::ENRAGED );
  1457. me->RemoveCondition( CBotNPC::BUSY );
  1458. me->FastRemoveLayer( m_animLayer );
  1459. me->UnlockAttackTarget();
  1460. }
  1461. //---------------------------------------------------------------------------------------------
  1462. //---------------------------------------------------------------------------------------------
  1463. class CBotNPCRush : public Action< CBotNPC >
  1464. {
  1465. public:
  1466. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  1467. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  1468. virtual void OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction );
  1469. // if anything interrupts this action, abort it
  1470. virtual ActionResult< CBotNPC > OnSuspend( CBotNPC *me, Action< CBotNPC > *interruptingAction ) { return Done(); }
  1471. virtual EventDesiredResult< CBotNPC > OnContact( CBotNPC *me, CBaseEntity *other, CGameTrace *result = NULL );
  1472. virtual const char *GetName( void ) const { return "Rush"; } // return name of this action
  1473. private:
  1474. CountdownTimer m_timer;
  1475. Vector m_chargeOrigin;
  1476. float m_maxAttainedSpeed;
  1477. float m_lastSpeed;
  1478. bool m_didHitVictim;
  1479. };
  1480. //---------------------------------------------------------------------------------------------
  1481. void PushawayPlayer( CTFPlayer *victim, const Vector &pushOrigin, float pushForce )
  1482. {
  1483. if ( !victim )
  1484. return;
  1485. if ( victim->GetFlags() & FL_ONGROUND )
  1486. {
  1487. // launching into the air
  1488. victim->SetAbsVelocity( vec3_origin );
  1489. const float stunTime = 0.5f;
  1490. victim->m_Shared.StunPlayer( stunTime, 1.0, TF_STUN_MOVEMENT );
  1491. victim->ApplyPunchImpulseX( RandomInt( 10, 15 ) );
  1492. victim->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:0,victim:1" );
  1493. }
  1494. victim->RemoveFlag( FL_ONGROUND );
  1495. Vector toVictim = victim->WorldSpaceCenter() - pushOrigin;
  1496. toVictim.z = 0.0f;
  1497. toVictim.NormalizeInPlace();
  1498. toVictim.z = 1.0f;
  1499. victim->ApplyAbsVelocityImpulse( pushForce * toVictim );
  1500. }
  1501. //---------------------------------------------------------------------------------------------
  1502. ActionResult< CBotNPC > CBotNPCRush::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  1503. {
  1504. m_timer.Start( 1.5f );
  1505. m_chargeOrigin = me->GetAbsOrigin();
  1506. m_maxAttainedSpeed = 0.0f;
  1507. m_lastSpeed = 0.0f;
  1508. m_didHitVictim = false;
  1509. me->AddCondition( CBotNPC::CHARGING );
  1510. me->AddCondition( CBotNPC::SHIELDED );
  1511. me->EmitSound( "Halloween.HeadlessBossAttack" );
  1512. return Continue();
  1513. }
  1514. //---------------------------------------------------------------------------------------------
  1515. ActionResult< CBotNPC > CBotNPCRush::Update( CBotNPC *me, float interval )
  1516. {
  1517. // pushaway/hit nearby players
  1518. CUtlVector< CTFPlayer * > playerVector;
  1519. CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
  1520. CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
  1521. Vector chargeVector = me->GetAbsOrigin() - m_chargeOrigin;
  1522. chargeVector.NormalizeInPlace();
  1523. const float chargeRadius = 150.0f;
  1524. for( int i=0; i<playerVector.Count(); ++i )
  1525. {
  1526. CTFPlayer *victim = playerVector[i];
  1527. if ( me->IsRangeGreaterThan( victim, chargeRadius ) )
  1528. continue;
  1529. Vector closestPointOnChargePath;
  1530. CalcClosestPointOnLine( victim->GetAbsOrigin(), m_chargeOrigin, me->GetAbsOrigin(), closestPointOnChargePath );
  1531. Vector fromChargePath = victim->GetAbsOrigin() - closestPointOnChargePath;
  1532. float range = fromChargePath.NormalizeInPlace();
  1533. if ( range >= chargeRadius )
  1534. continue;
  1535. if ( !me->IsLineOfSightClear( victim ) )
  1536. continue;
  1537. float nearness = 1.0f - ( range / chargeRadius );
  1538. // push 'em
  1539. float pushForce = tf_bot_npc_charge_pushaway_force.GetFloat() * nearness;
  1540. PushawayPlayer( victim, closestPointOnChargePath, pushForce );
  1541. // crunch 'em
  1542. CTakeDamageInfo info( me, me, tf_bot_npc_charge_damage.GetFloat() * nearness, DMG_CRUSH, TF_DMG_CUSTOM_NONE );
  1543. CalculateMeleeDamageForce( &info, fromChargePath, closestPointOnChargePath, 1.0f );
  1544. victim->TakeDamage( info );
  1545. color32 color = { 255, 0, 0, 255 };
  1546. UTIL_ScreenFade( victim, color, 0.5f, 0.1f, FFADE_IN );
  1547. if ( nearness > 0.5f )
  1548. {
  1549. m_didHitVictim = true;
  1550. }
  1551. }
  1552. float speed = me->GetLocomotionInterface()->GetVelocity().Length();
  1553. m_maxAttainedSpeed = MAX( m_maxAttainedSpeed, speed );
  1554. if ( m_timer.IsElapsed() )
  1555. {
  1556. return ChangeTo( new CBotNPCLaunchRockets, "Finished charge" );
  1557. }
  1558. else
  1559. {
  1560. // chaaarge!
  1561. me->GetLocomotionInterface()->Run();
  1562. Vector forward;
  1563. me->GetVectors( &forward, NULL, NULL );
  1564. me->GetLocomotionInterface()->Approach( 100.0f * forward + me->GetLocomotionInterface()->GetFeet() );
  1565. if ( !m_didHitVictim && m_maxAttainedSpeed > 350.0f && speed - m_lastSpeed < -200.0f )
  1566. {
  1567. // abrupt slowdown = bonk!
  1568. return ChangeTo( new CBotNPCStunned( 3.0f, new CBotNPCLaunchRockets ), "Smacked into the world" );
  1569. }
  1570. }
  1571. // animation
  1572. if ( !me->GetBodyInterface()->IsActivity( ACT_MP_CROUCHWALK_PRIMARY ) )
  1573. {
  1574. me->GetBodyInterface()->StartActivity( ACT_MP_CROUCHWALK_PRIMARY );
  1575. }
  1576. m_lastSpeed = speed;
  1577. return Continue();
  1578. }
  1579. //---------------------------------------------------------------------------------------------
  1580. void CBotNPCRush::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  1581. {
  1582. me->RemoveCondition( CBotNPC::SHIELDED );
  1583. me->RemoveCondition( CBotNPC::CHARGING );
  1584. }
  1585. //---------------------------------------------------------------------------------------------
  1586. EventDesiredResult< CBotNPC > CBotNPCRush::OnContact( CBotNPC *me, CBaseEntity *other, CGameTrace *result )
  1587. {
  1588. return TryContinue();
  1589. }
  1590. //---------------------------------------------------------------------------------------------
  1591. //---------------------------------------------------------------------------------------------
  1592. class CBotNPCBlock : public Action< CBotNPC >
  1593. {
  1594. public:
  1595. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  1596. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  1597. virtual void OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction );
  1598. virtual ActionResult< CBotNPC > OnSuspend( CBotNPC *me, Action< CBotNPC > *interruptingAction );
  1599. virtual const char *GetName( void ) const { return "Block"; } // return name of this action
  1600. private:
  1601. CountdownTimer m_timer;
  1602. };
  1603. //---------------------------------------------------------------------------------------------
  1604. ActionResult< CBotNPC > CBotNPCBlock::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  1605. {
  1606. // start animation
  1607. me->SetSequence( me->LookupSequence( "marketing_pose_001" ) );
  1608. me->SetPlaybackRate( 1.0f );
  1609. me->SetCycle( 0 );
  1610. me->ResetSequenceInfo();
  1611. m_timer.Start( 3.0f );
  1612. me->AddCondition( CBotNPC::SHIELDED );
  1613. return Continue();
  1614. }
  1615. //---------------------------------------------------------------------------------------------
  1616. ActionResult< CBotNPC > CBotNPCBlock::Update( CBotNPC *me, float interval )
  1617. {
  1618. if ( m_timer.IsElapsed() )
  1619. {
  1620. return Done();
  1621. }
  1622. if ( me->GetAttackTarget() )
  1623. {
  1624. me->GetLocomotionInterface()->FaceTowards( me->GetAttackTarget()->WorldSpaceCenter() );
  1625. }
  1626. return Continue();
  1627. }
  1628. //---------------------------------------------------------------------------------------------
  1629. void CBotNPCBlock::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  1630. {
  1631. me->RemoveCondition( CBotNPC::SHIELDED );
  1632. }
  1633. //---------------------------------------------------------------------------------------------
  1634. ActionResult< CBotNPC > CBotNPCBlock::OnSuspend( CBotNPC *me, Action< CBotNPC > *interruptingAction )
  1635. {
  1636. return Done();
  1637. }
  1638. //---------------------------------------------------------------------------------------------
  1639. //---------------------------------------------------------------------------------------------
  1640. class CBotNPCLaunchGrenades : public Action< CBotNPC >
  1641. {
  1642. public:
  1643. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  1644. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  1645. virtual void OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction );
  1646. // if anything interrupts this action, abort it
  1647. virtual ActionResult< CBotNPC > OnSuspend( CBotNPC *me, Action< CBotNPC > *interruptingAction ) { return Done(); }
  1648. virtual const char *GetName( void ) const { return "LaunchGrenades"; } // return name of this action
  1649. private:
  1650. CountdownTimer m_timer;
  1651. CountdownTimer m_detonateTimer;
  1652. CUtlVector< CHandle< CTFGrenadePipebombProjectile > > m_grenadeVector;
  1653. void LaunchGrenade( CBotNPC *me, const Vector &launchVel, CTFWeaponInfo *weaponInfo );
  1654. void LaunchGrenadeRings( CBotNPC *me );
  1655. void LaunchGrenadeSpokes( CBotNPC *me );
  1656. int m_animLayer;
  1657. };
  1658. ConVar tf_bot_npc_grenade_ring_min_horiz_vel( "tf_bot_npc_grenade_ring_min_horiz_vel", "100"/*, FCVAR_CHEAT*/ );
  1659. ConVar tf_bot_npc_grenade_ring_max_horiz_vel( "tf_bot_npc_grenade_ring_max_horiz_vel", "350"/*, FCVAR_CHEAT*/ );
  1660. ConVar tf_bot_npc_grenade_vert_vel( "tf_bot_npc_grenade_vert_vel", "750"/*, FCVAR_CHEAT*/ );
  1661. ConVar tf_bot_npc_grenade_det_time( "tf_bot_npc_grenade_det_time", "3"/*, FCVAR_CHEAT*/ );
  1662. //---------------------------------------------------------------------------------------------
  1663. ActionResult< CBotNPC > CBotNPCLaunchGrenades::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  1664. {
  1665. me->GetBodyInterface()->StartActivity( ACT_MP_STAND_SECONDARY );
  1666. m_animLayer = me->AddLayeredSequence( me->LookupSequence( "gesture_melee_cheer" ), 0 );
  1667. m_timer.Start( 1.0f );
  1668. m_detonateTimer.Invalidate();
  1669. me->AddCondition( CBotNPC::BUSY );
  1670. me->GetGrenadeTimer()->Start( me->GetGrenadeInterval() );
  1671. me->EmitSound( "RobotBoss.LaunchGrenades" );
  1672. return Continue();
  1673. }
  1674. //---------------------------------------------------------------------------------------------
  1675. void CBotNPCLaunchGrenades::LaunchGrenade( CBotNPC *me, const Vector &launchVel, CTFWeaponInfo *weaponInfo )
  1676. {
  1677. CTFGrenadePipebombProjectile *pProjectile = CTFGrenadePipebombProjectile::Create( me->WorldSpaceCenter(), vec3_angle, launchVel,
  1678. AngularImpulse( 600, random->RandomInt( -1200, 1200 ), 0 ),
  1679. me, *weaponInfo, TF_PROJECTILE_PIPEBOMB_REMOTE, 1 );
  1680. if ( pProjectile )
  1681. {
  1682. pProjectile->SetLauncher( me );
  1683. pProjectile->SetDamage( tf_bot_npc_grenade_damage.GetFloat() );
  1684. if ( me->IsInCondition( CBotNPC::ENRAGED ) )
  1685. {
  1686. pProjectile->SetCritical( true );
  1687. }
  1688. m_grenadeVector.AddToTail( pProjectile );
  1689. }
  1690. }
  1691. //---------------------------------------------------------------------------------------------
  1692. void CBotNPCLaunchGrenades::LaunchGrenadeRings( CBotNPC *me )
  1693. {
  1694. const char *weaponAlias = WeaponIdToAlias( TF_WEAPON_GRENADELAUNCHER );
  1695. if ( !weaponAlias )
  1696. return;
  1697. WEAPON_FILE_INFO_HANDLE weaponInfoHandle = LookupWeaponInfoSlot( weaponAlias );
  1698. if ( weaponInfoHandle == GetInvalidWeaponInfoHandle() )
  1699. return;
  1700. CTFWeaponInfo *weaponInfo = static_cast< CTFWeaponInfo * >( GetFileWeaponInfoFromHandle( weaponInfoHandle ) );
  1701. QAngle myAngles = me->EyeAngles();
  1702. // create rings of stickies
  1703. float deltaVel = tf_bot_npc_grenade_ring_max_horiz_vel.GetFloat() - tf_bot_npc_grenade_ring_min_horiz_vel.GetFloat();
  1704. const int ringCount = 2;
  1705. for( int r=0; r<ringCount; ++r )
  1706. {
  1707. float u = (float)r/(float)(ringCount-1);
  1708. float horizVel = tf_bot_npc_grenade_ring_min_horiz_vel.GetFloat() + u * deltaVel;
  1709. float angleDelta = 10.0f + 20.0f * ( 1.0f - u );
  1710. for( float angle=0.0f; angle<360.0f; angle += angleDelta )
  1711. {
  1712. Vector forward;
  1713. AngleVectors( myAngles, &forward );
  1714. Vector vecVelocity( horizVel * forward.x, horizVel * forward.y, tf_bot_npc_grenade_vert_vel.GetFloat() );
  1715. LaunchGrenade( me, vecVelocity, weaponInfo );
  1716. myAngles.y += angleDelta;
  1717. }
  1718. }
  1719. }
  1720. ConVar tf_bot_npc_grenade_spoke_angle( "tf_bot_npc_grenade_spoke_angle", "45"/*, FCVAR_CHEAT*/ );
  1721. ConVar tf_bot_npc_grenade_spoke_count( "tf_bot_npc_grenade_spoke_count", "15"/*, FCVAR_CHEAT*/ );
  1722. ConVar tf_bot_npc_grenade_spoke_min_horiz_vel( "tf_bot_npc_grenade_spoke_min_horiz_vel", "100"/*, FCVAR_CHEAT*/ );
  1723. ConVar tf_bot_npc_grenade_spoke_max_horiz_vel( "tf_bot_npc_grenade_spoke_max_horiz_vel", "750"/*, FCVAR_CHEAT*/ );
  1724. //---------------------------------------------------------------------------------------------
  1725. void CBotNPCLaunchGrenades::LaunchGrenadeSpokes( CBotNPC *me )
  1726. {
  1727. const char *weaponAlias = WeaponIdToAlias( TF_WEAPON_GRENADELAUNCHER );
  1728. if ( !weaponAlias )
  1729. return;
  1730. WEAPON_FILE_INFO_HANDLE weaponInfoHandle = LookupWeaponInfoSlot( weaponAlias );
  1731. if ( weaponInfoHandle == GetInvalidWeaponInfoHandle() )
  1732. return;
  1733. CTFWeaponInfo *weaponInfo = static_cast< CTFWeaponInfo * >( GetFileWeaponInfoFromHandle( weaponInfoHandle ) );
  1734. // create spokes of stickies
  1735. float deltaVel = tf_bot_npc_grenade_spoke_max_horiz_vel.GetFloat() - tf_bot_npc_grenade_spoke_min_horiz_vel.GetFloat();
  1736. float angleDelta = tf_bot_npc_grenade_spoke_angle.GetFloat();
  1737. QAngle myAngles = me->EyeAngles();
  1738. for( float angle=0.0f; angle<360.0f; angle += angleDelta )
  1739. {
  1740. Vector forward;
  1741. AngleVectors( myAngles, &forward );
  1742. int spokeCount = tf_bot_npc_grenade_spoke_count.GetInt();
  1743. for( int i=0; i<spokeCount; ++i )
  1744. {
  1745. float u = (float)i/(float)(spokeCount-1);
  1746. float horizVel = tf_bot_npc_grenade_spoke_min_horiz_vel.GetFloat() + u * deltaVel;
  1747. Vector vecVelocity( horizVel * forward.x, horizVel * forward.y, tf_bot_npc_grenade_vert_vel.GetFloat() );
  1748. LaunchGrenade( me, vecVelocity, weaponInfo );
  1749. }
  1750. myAngles.y += angleDelta;
  1751. }
  1752. }
  1753. //---------------------------------------------------------------------------------------------
  1754. ActionResult< CBotNPC > CBotNPCLaunchGrenades::Update( CBotNPC *me, float interval )
  1755. {
  1756. QAngle myAngles = me->EyeAngles();
  1757. if ( m_timer.HasStarted() && m_timer.IsElapsed() )
  1758. {
  1759. m_timer.Invalidate();
  1760. if ( RandomInt( 0, 100 ) < 50 )
  1761. {
  1762. LaunchGrenadeRings( me );
  1763. }
  1764. else
  1765. {
  1766. LaunchGrenadeSpokes( me );
  1767. }
  1768. me->EmitSound( "Weapon_Grenade_Normal.Single" );
  1769. m_detonateTimer.Start( tf_bot_npc_grenade_det_time.GetFloat() );
  1770. }
  1771. if ( m_detonateTimer.HasStarted() && m_detonateTimer.IsElapsed() )
  1772. {
  1773. // detonate the stickies
  1774. for( int i=0; i<m_grenadeVector.Count(); ++i )
  1775. {
  1776. if ( m_grenadeVector[i] )
  1777. {
  1778. m_grenadeVector[i]->Detonate();
  1779. }
  1780. }
  1781. return Done();
  1782. }
  1783. return Continue();
  1784. }
  1785. //---------------------------------------------------------------------------------------------
  1786. void CBotNPCLaunchGrenades::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  1787. {
  1788. // fizzle any outstanding stickies
  1789. for( int i=0; i<m_grenadeVector.Count(); ++i )
  1790. {
  1791. if ( m_grenadeVector[i] )
  1792. {
  1793. m_grenadeVector[i]->Fizzle();
  1794. m_grenadeVector[i]->Detonate();
  1795. }
  1796. }
  1797. me->RemoveCondition( CBotNPC::ENRAGED );
  1798. me->RemoveCondition( CBotNPC::BUSY );
  1799. me->FastRemoveLayer( m_animLayer );
  1800. }
  1801. //---------------------------------------------------------------------------------------------
  1802. //---------------------------------------------------------------------------------------------
  1803. class CBotNPCShootCrossbow : public Action< CBotNPC >
  1804. {
  1805. public:
  1806. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  1807. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  1808. // if anything interrupts this action, abort it
  1809. virtual ActionResult< CBotNPC > OnSuspend( CBotNPC *me, Action< CBotNPC > *interruptingAction ) { return Done(); }
  1810. virtual const char *GetName( void ) const { return "ShootCrossbow"; } // return name of this action
  1811. private:
  1812. CountdownTimer m_timer;
  1813. };
  1814. //---------------------------------------------------------------------------------------------
  1815. ActionResult< CBotNPC > CBotNPCShootCrossbow::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  1816. {
  1817. me->GetBodyInterface()->StartActivity( ACT_MP_STAND_SECONDARY );
  1818. m_timer.Start( tf_bot_npc_aim_time.GetFloat() );
  1819. return Continue();
  1820. }
  1821. //---------------------------------------------------------------------------------------------
  1822. ActionResult< CBotNPC > CBotNPCShootCrossbow::Update( CBotNPC *me, float interval )
  1823. {
  1824. CBaseCombatCharacter *target = me->GetAttackTarget();
  1825. if ( !target )
  1826. {
  1827. return Done( "No target" );
  1828. }
  1829. me->GetLocomotionInterface()->FaceTowards( target->WorldSpaceCenter() );
  1830. if ( m_timer.IsElapsed() )
  1831. {
  1832. // fire bolt
  1833. const float arrowSpeed = 4000.0f;
  1834. const float arrowGravity = 0.0f; // railgun
  1835. Vector muzzleOrigin;
  1836. QAngle muzzleAngles;
  1837. if ( me->GetWeapon()->GetAttachment( "muzzle", muzzleOrigin, muzzleAngles ) == false )
  1838. {
  1839. return Done( "No muzzle attachment!" );
  1840. }
  1841. // lead target
  1842. float range = me->GetRangeTo( target->EyePosition() );
  1843. float flightTime = range / arrowSpeed;
  1844. Vector aimSpot = target->EyePosition() + target->GetAbsVelocity() * flightTime;
  1845. Vector to = aimSpot - muzzleOrigin;
  1846. VectorAngles( to, muzzleAngles );
  1847. CTFProjectile_Arrow *arrow = CTFProjectile_Arrow::Create( muzzleOrigin, muzzleAngles, arrowSpeed, arrowGravity, TF_PROJECTILE_ARROW, me, me );
  1848. if ( arrow )
  1849. {
  1850. arrow->SetLauncher( me );
  1851. arrow->SetCritical( true );
  1852. // set damage to 5 points more than our target's max health so a Medic can save us
  1853. // arrow->SetDamage( ( target->GetMaxHealth() + 5.0f ) / TF_DAMAGE_CRIT_MULTIPLIER );
  1854. arrow->SetDamage( 200.0f );
  1855. me->EmitSound( "Weapon_CompoundBow.Single" );
  1856. }
  1857. return Done();
  1858. }
  1859. return Continue();
  1860. }
  1861. //---------------------------------------------------------------------------------------------
  1862. //---------------------------------------------------------------------------------------------
  1863. class CBotNPCLostVictim : public Action< CBotNPC >
  1864. {
  1865. public:
  1866. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  1867. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  1868. virtual void OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction );
  1869. virtual const char *GetName( void ) const { return "LostVictim"; } // return name of this action
  1870. private:
  1871. CountdownTimer m_timer;
  1872. float m_headTurn;
  1873. int m_headYawPoseParameter;
  1874. };
  1875. //---------------------------------------------------------------------------------------------
  1876. ActionResult< CBotNPC > CBotNPCLostVictim::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  1877. {
  1878. m_headTurn = 0.0f;
  1879. m_headYawPoseParameter = me->LookupPoseParameter( "body_yaw" );
  1880. m_timer.Start( RandomFloat( 3.0f, 5.0f ) );
  1881. me->EmitSound( "RobotBoss.Scanning" );
  1882. return Continue();
  1883. }
  1884. //---------------------------------------------------------------------------------------------
  1885. ActionResult< CBotNPC > CBotNPCLostVictim::Update( CBotNPC *me, float interval )
  1886. {
  1887. if ( m_timer.IsElapsed() )
  1888. {
  1889. return Done( "Giving up" );
  1890. }
  1891. CBaseCombatCharacter *target = me->GetAttackTarget();
  1892. if ( target )
  1893. {
  1894. if ( me->IsLineOfSightClear( target ) || me->IsPrisonerOfMinion( target ) )
  1895. {
  1896. me->EmitSound( "RobotBoss.Acquire" );
  1897. me->AddGesture( ACT_MP_GESTURE_FLINCH_CHEST );
  1898. return Done( "Ah hah!" );
  1899. }
  1900. }
  1901. const float rate = M_PI / 3.0f;
  1902. m_headTurn += rate * interval;
  1903. float s, c;
  1904. SinCos( m_headTurn, &s, &c );
  1905. me->SetPoseParameter( m_headYawPoseParameter, 40.0f * s );
  1906. return Continue();
  1907. }
  1908. //---------------------------------------------------------------------------------------------
  1909. void CBotNPCLostVictim::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  1910. {
  1911. me->SetPoseParameter( m_headYawPoseParameter, 0 );
  1912. }
  1913. //---------------------------------------------------------------------------------------------
  1914. //---------------------------------------------------------------------------------------------
  1915. class CBotNPCChaseVictim : public Action< CBotNPC >
  1916. {
  1917. public:
  1918. CBotNPCChaseVictim( CBaseCombatCharacter *chaseTarget );
  1919. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  1920. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  1921. virtual void OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction );
  1922. virtual EventDesiredResult< CBotNPC > OnStuck( CBotNPC *me );
  1923. virtual EventDesiredResult< CBotNPC > OnMoveToSuccess( CBotNPC *me, const Path *path );
  1924. virtual EventDesiredResult< CBotNPC > OnMoveToFailure( CBotNPC *me, const Path *path, MoveToFailureType reason );
  1925. virtual const char *GetName( void ) const { return "ChaseVictim"; } // return name of this action
  1926. private:
  1927. CTFPathFollower m_path;
  1928. IntervalTimer m_visibleTimer;
  1929. CHandle< CBaseCombatCharacter > m_lastTarget;
  1930. CHandle< CBaseCombatCharacter > m_chaseTarget;
  1931. Vector m_lastKnownTargetSpot;
  1932. };
  1933. //---------------------------------------------------------------------------------------------
  1934. CBotNPCChaseVictim::CBotNPCChaseVictim( CBaseCombatCharacter *chaseTarget )
  1935. {
  1936. m_chaseTarget = chaseTarget;
  1937. m_lastKnownTargetSpot = chaseTarget->GetAbsOrigin();
  1938. }
  1939. //---------------------------------------------------------------------------------------------
  1940. ActionResult< CBotNPC > CBotNPCChaseVictim::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  1941. {
  1942. if ( m_chaseTarget == NULL )
  1943. {
  1944. return Done( "Target is NULL" );
  1945. }
  1946. m_lastKnownTargetSpot = m_chaseTarget->GetAbsOrigin();
  1947. return Continue();
  1948. }
  1949. //---------------------------------------------------------------------------------------------
  1950. ActionResult< CBotNPC > CBotNPCChaseVictim::Update( CBotNPC *me, float interval )
  1951. {
  1952. if ( m_chaseTarget == NULL || !m_chaseTarget->IsAlive() )
  1953. {
  1954. return ChangeTo( new CBotNPCLostVictim, "No victim" );
  1955. }
  1956. if ( m_chaseTarget != me->GetAttackTarget() )
  1957. {
  1958. return Done( "Changing targets" );
  1959. }
  1960. Vector moveGoal = m_chaseTarget->GetAbsOrigin();
  1961. if ( me->IsLineOfSightClear( m_chaseTarget ) )
  1962. {
  1963. if ( !m_visibleTimer.HasStarted() )
  1964. {
  1965. m_visibleTimer.Start();
  1966. }
  1967. if ( me->HasAbility( CBotNPC::CAN_NUKE ) && me->GetNukeTimer()->IsElapsed() )
  1968. {
  1969. return SuspendFor( new CBotNPCNukeAttack, "Nuking!" );
  1970. }
  1971. m_lastKnownTargetSpot = m_chaseTarget->GetAbsOrigin();
  1972. if ( me->HasAbility( CBotNPC::CAN_LAUNCH_STICKIES ) )
  1973. {
  1974. if ( ( me->GetGrenadeTimer()->IsElapsed() && me->IsRangeLessThan( m_chaseTarget, tf_bot_npc_grenade_launch_range.GetFloat() ) ) ||
  1975. me->IsInCondition( CBotNPC::ENRAGED ) )
  1976. {
  1977. return SuspendFor( new CBotNPCLaunchGrenades, "Target is close (or I am enraged) - grenades!" );
  1978. }
  1979. }
  1980. // chase into line of sight a bit so they can't immediately get behind cover again
  1981. if ( me->HasAbility( CBotNPC::CAN_FIRE_ROCKETS ) )
  1982. {
  1983. if ( m_visibleTimer.IsGreaterThen( 1.0f ) ||
  1984. me->IsRangeLessThan( m_chaseTarget, tf_bot_npc_chase_range.GetFloat() ) )
  1985. {
  1986. return SuspendFor( new CBotNPCLaunchRockets, "Fire!" );
  1987. }
  1988. }
  1989. if ( me->IsRangeLessThan( m_chaseTarget, 150.0f ) )
  1990. {
  1991. // too close - stand still
  1992. if ( !me->GetBodyInterface()->IsActivity( ACT_MP_STAND_MELEE ) )
  1993. {
  1994. me->GetBodyInterface()->StartActivity( ACT_MP_STAND_MELEE );
  1995. }
  1996. return Continue();
  1997. }
  1998. }
  1999. else
  2000. {
  2001. m_visibleTimer.Invalidate();
  2002. // move to where we last saw our target
  2003. moveGoal = m_lastKnownTargetSpot;
  2004. if ( me->IsRangeLessThan( m_lastKnownTargetSpot, 20.0f ) )
  2005. {
  2006. // reached spot where we last saw our victim - give up
  2007. me->SetAttackTarget( NULL );
  2008. return ChangeTo( new CBotNPCLostVictim, "I lost my chase victim" );
  2009. }
  2010. }
  2011. // move into sight of target
  2012. if ( m_path.GetAge() > 1.0f )
  2013. {
  2014. CBotNPCPathCost cost( me );
  2015. m_path.Compute( me, moveGoal, cost );
  2016. }
  2017. me->GetLocomotionInterface()->Run();
  2018. m_path.Update( me );
  2019. // play running animation
  2020. if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
  2021. {
  2022. me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
  2023. }
  2024. return Continue();
  2025. }
  2026. //---------------------------------------------------------------------------------------------
  2027. EventDesiredResult< CBotNPC > CBotNPCChaseVictim::OnMoveToSuccess( CBotNPC *me, const Path *path )
  2028. {
  2029. return TryDone( RESULT_CRITICAL, "Reached move goal" );
  2030. }
  2031. //---------------------------------------------------------------------------------------------
  2032. EventDesiredResult< CBotNPC > CBotNPCChaseVictim::OnMoveToFailure( CBotNPC *me, const Path *path, MoveToFailureType reason )
  2033. {
  2034. return TryDone( RESULT_CRITICAL, "Path follow failed" );
  2035. }
  2036. //---------------------------------------------------------------------------------------------
  2037. void CBotNPCChaseVictim::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  2038. {
  2039. }
  2040. //---------------------------------------------------------------------------------------------
  2041. EventDesiredResult< CBotNPC > CBotNPCChaseVictim::OnStuck( CBotNPC *me )
  2042. {
  2043. // we're stuck - just warp to the our next path goal
  2044. if ( m_path.GetCurrentGoal() )
  2045. {
  2046. me->SetAbsOrigin( m_path.GetCurrentGoal()->pos + Vector( 0, 0, 10.0f ) );
  2047. }
  2048. return TryContinue();
  2049. }
  2050. //---------------------------------------------------------------------------------------------
  2051. //---------------------------------------------------------------------------------------------
  2052. class CBotNPCLaserBlast : public Action< CBotNPC >
  2053. {
  2054. public:
  2055. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  2056. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  2057. virtual void OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction );
  2058. virtual ActionResult< CBotNPC > OnSuspend( CBotNPC *me, Action< CBotNPC > *interruptingAction );
  2059. virtual EventDesiredResult< CBotNPC > OnStuck( CBotNPC *me );
  2060. virtual const char *GetName( void ) const { return "LaserBlast"; } // return name of this action
  2061. private:
  2062. CTFPathFollower m_path;
  2063. CountdownTimer m_laserTimer;
  2064. IntervalTimer m_visibleTimer;
  2065. CHandle< CBaseCombatCharacter > m_lastTarget;
  2066. };
  2067. ConVar tf_bot_npc_laser_damage_rate( "tf_bot_npc_laser_damage_rate", "40"/*, FCVAR_CHEAT*/ ); // 20
  2068. ConVar tf_bot_npc_laser_damage_gain_rate( "tf_bot_npc_laser_damage_gain_rate", "0"/*, FCVAR_CHEAT*/ ); // 0
  2069. ConVar tf_bot_npc_laser_damage_ignite_threshold( "tf_bot_npc_laser_damage_ignite_threshold", "999"/*, FCVAR_CHEAT*/ );
  2070. ConVar tf_bot_npc_laser_damage_ignite_time( "tf_bot_npc_laser_damage_ignite_time", "3"/*, FCVAR_CHEAT*/ );
  2071. ConVar tf_bot_npc_laser_afterburn_time( "tf_bot_npc_laser_afterburn_time", "10"/*, FCVAR_CHEAT*/ );
  2072. ConVar tf_bot_npc_laser_damage_building_multiplier( "tf_bot_npc_laser_damage_building_multiplier", "4"/*, FCVAR_CHEAT*/ );
  2073. ConVar tf_bot_npc_laser_duration( "tf_bot_npc_laser_duration", "8"/*, FCVAR_CHEAT*/ );
  2074. //----------------------------------------------------------------------------------
  2075. ActionResult< CBotNPC > CBotNPCLaserBlast::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  2076. {
  2077. m_laserTimer.Start( tf_bot_npc_laser_duration.GetFloat() );
  2078. m_visibleTimer.Invalidate();
  2079. m_lastTarget = NULL;
  2080. return Continue();
  2081. }
  2082. //----------------------------------------------------------------------------------
  2083. ActionResult< CBotNPC > CBotNPCLaserBlast::Update( CBotNPC *me, float interval )
  2084. {
  2085. CBaseCombatCharacter *target = me->GetAttackTarget();
  2086. if ( !target )
  2087. {
  2088. return Done( "No victim" );
  2089. }
  2090. if ( me->HasAbility( CBotNPC::CAN_NUKE ) && me->GetNukeTimer()->IsElapsed() )
  2091. {
  2092. return ChangeTo( new CBotNPCNukeAttack, "Nuking!" );
  2093. }
  2094. if ( target != m_lastTarget )
  2095. {
  2096. // new target, reset laser
  2097. m_laserTimer.Reset();
  2098. m_lastTarget = target;
  2099. }
  2100. if ( me->HasAbility( CBotNPC::CAN_FIRE_ROCKETS ) && m_laserTimer.IsElapsed() )
  2101. {
  2102. // laser not effective - try rockets!
  2103. return ChangeTo( new CBotNPCLaunchRockets, "Launching Rockets!" );
  2104. }
  2105. if ( me->IsLineOfSightClear( target ) )
  2106. {
  2107. if ( !m_visibleTimer.HasStarted() )
  2108. {
  2109. m_visibleTimer.Start();
  2110. }
  2111. me->GetLocomotionInterface()->FaceTowards( target->WorldSpaceCenter() );
  2112. // blast 'em
  2113. me->SetLaserTarget( target );
  2114. float damage = tf_bot_npc_laser_damage_rate.GetFloat() + m_laserTimer.GetElapsedTime() * tf_bot_npc_laser_damage_gain_rate.GetFloat();
  2115. // lasers do extra damage to buildings
  2116. if ( target->IsBaseObject() )
  2117. {
  2118. damage *= tf_bot_npc_laser_damage_building_multiplier.GetFloat();
  2119. }
  2120. CTakeDamageInfo info( me, me, damage * interval, DMG_ENERGYBEAM, TF_DMG_CUSTOM_NONE );
  2121. Vector toVictim = target->WorldSpaceCenter() - me->EyePosition();
  2122. toVictim.NormalizeInPlace();
  2123. CalculateMeleeDamageForce( &info, toVictim, me->EyePosition(), 1.0f );
  2124. target->TakeDamage( info );
  2125. if ( target->IsPlayer() && damage > tf_bot_npc_laser_damage_ignite_threshold.GetFloat() )
  2126. {
  2127. ToTFPlayer( target )->m_Shared.Burn( me, tf_bot_npc_laser_afterburn_time.GetFloat() );
  2128. }
  2129. if ( target->IsPlayer() && m_laserTimer.GetElapsedTime() > tf_bot_npc_laser_damage_ignite_time.GetFloat() )
  2130. {
  2131. ToTFPlayer( target )->m_Shared.Burn( me, tf_bot_npc_laser_afterburn_time.GetFloat() );
  2132. }
  2133. // me->EmitSound( "Weapon_Sword.HitFlesh" );
  2134. if ( !me->IsPlayingGesture( ACT_MP_GESTURE_FLINCH_CHEST ) )
  2135. {
  2136. me->AddGesture( ACT_MP_GESTURE_FLINCH_CHEST );
  2137. }
  2138. }
  2139. else
  2140. {
  2141. me->SetLaserTarget( NULL );
  2142. m_laserTimer.Reset();
  2143. m_visibleTimer.Invalidate();
  2144. }
  2145. // chase into line of sight a bit so they can't immediately get behind cover again
  2146. if ( !m_visibleTimer.HasStarted() || m_visibleTimer.IsLessThen( 1.0f ) )
  2147. {
  2148. // don't get too close to avoid penetration/stuck issues
  2149. if ( me->IsRangeGreaterThan( target, 100.0f ) )
  2150. {
  2151. // move into sight of target
  2152. if ( m_path.GetAge() > 1.0f )
  2153. {
  2154. CBotNPCPathCost cost( me );
  2155. m_path.Compute( me, target, cost );
  2156. }
  2157. me->GetLocomotionInterface()->Run();
  2158. m_path.Update( me );
  2159. }
  2160. }
  2161. if ( me->GetLocomotionInterface()->IsAttemptingToMove() )
  2162. {
  2163. // play running animation
  2164. if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
  2165. {
  2166. me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
  2167. }
  2168. }
  2169. else
  2170. {
  2171. // standing still
  2172. if ( !me->GetBodyInterface()->IsActivity( ACT_MP_STAND_ITEM1 ) )
  2173. {
  2174. me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM1 );
  2175. }
  2176. }
  2177. return Continue();
  2178. }
  2179. //---------------------------------------------------------------------------------------------
  2180. void CBotNPCLaserBlast::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  2181. {
  2182. me->SetLaserTarget( NULL );
  2183. }
  2184. //---------------------------------------------------------------------------------------------
  2185. ActionResult< CBotNPC > CBotNPCLaserBlast::OnSuspend( CBotNPC *me, Action< CBotNPC > *interruptingAction )
  2186. {
  2187. return Done();
  2188. }
  2189. //---------------------------------------------------------------------------------------------
  2190. EventDesiredResult< CBotNPC > CBotNPCLaserBlast::OnStuck( CBotNPC *me )
  2191. {
  2192. // we're stuck - just warp to the our next path goal
  2193. if ( m_path.GetCurrentGoal() )
  2194. {
  2195. me->SetAbsOrigin( m_path.GetCurrentGoal()->pos + Vector( 0, 0, 10.0f ) );
  2196. }
  2197. return TryContinue( RESULT_TRY );
  2198. }
  2199. //---------------------------------------------------------------------------------------------
  2200. //---------------------------------------------------------------------------------------------
  2201. class CBotNPCAttack : public Action< CBotNPC >
  2202. {
  2203. public:
  2204. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  2205. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  2206. virtual ActionResult< CBotNPC > OnResume( CBotNPC *me, Action< CBotNPC > *interruptingAction );
  2207. virtual EventDesiredResult< CBotNPC > OnStuck( CBotNPC *me );
  2208. virtual EventDesiredResult< CBotNPC > OnContact( CBotNPC *me, CBaseEntity *other, CGameTrace *result = NULL );
  2209. virtual const char *GetName( void ) const { return "Attack"; } // return name of this action
  2210. private:
  2211. CTFPathFollower m_path;
  2212. CountdownTimer m_chargeTimer;
  2213. CHandle< CTFPlayer > m_closestVisible;
  2214. CountdownTimer m_attackThrottleTimer;
  2215. void ValidateChaseVictim( CBotNPC *me );
  2216. CountdownTimer m_attackTargetFocusTimer;
  2217. };
  2218. //----------------------------------------------------------------------------------
  2219. ActionResult< CBotNPC > CBotNPCAttack::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  2220. {
  2221. m_attackThrottleTimer.Invalidate();
  2222. m_closestVisible = NULL;
  2223. m_attackTargetFocusTimer.Invalidate();
  2224. m_chargeTimer.Invalidate();
  2225. return Continue();
  2226. }
  2227. //----------------------------------------------------------------------------------
  2228. ActionResult< CBotNPC > CBotNPCAttack::Update( CBotNPC *me, float interval )
  2229. {
  2230. if ( !me->IsAlive() )
  2231. {
  2232. return Done();
  2233. }
  2234. CBaseCombatCharacter *target = me->GetAttackTarget();
  2235. if ( !target )
  2236. {
  2237. return Done( "No victim" );
  2238. }
  2239. me->GetLocomotionInterface()->FaceTowards( target->WorldSpaceCenter() );
  2240. // swing our axe at our attack target if they are in range
  2241. if ( !me->IsSwingingAxe() )
  2242. {
  2243. if ( me->IsRangeLessThan( target, tf_bot_npc_attack_range.GetFloat() ) )
  2244. {
  2245. me->SwingAxe();
  2246. }
  2247. }
  2248. if ( !me->IsSwingingAxe() )
  2249. {
  2250. if ( m_chargeTimer.IsElapsed() && me->IsLookingTowards( target->WorldSpaceCenter(), 0.9f ) )
  2251. {
  2252. m_chargeTimer.Start( tf_bot_npc_charge_interval.GetFloat() );
  2253. return SuspendFor( new CBotNPCRush, "Chaaarge!" );
  2254. }
  2255. if ( me->GetReceivedDamagePerSecond() > tf_bot_npc_block_dps_react.GetFloat() &&
  2256. target->IsPlayer() &&
  2257. ToTFPlayer( target )->GetTimeSinceWeaponFired() < 1.0f )
  2258. {
  2259. return SuspendFor( new CBotNPCBlock, "Blocking" );
  2260. }
  2261. }
  2262. // chase after our victim
  2263. const float standAndSwingRange = 0.5f * tf_bot_npc_attack_range.GetFloat();
  2264. if ( me->IsRangeGreaterThan( target, standAndSwingRange ) || !me->IsLineOfSightClear( target ) )
  2265. {
  2266. if ( m_path.GetAge() > 1.0f )
  2267. {
  2268. CBotNPCPathCost cost( me );
  2269. m_path.Compute( me, target, cost );
  2270. }
  2271. m_path.Update( me );
  2272. }
  2273. if ( me->GetLocomotionInterface()->IsAttemptingToMove() )
  2274. {
  2275. // play running animation
  2276. if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
  2277. {
  2278. me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
  2279. }
  2280. }
  2281. else
  2282. {
  2283. // standing still
  2284. if ( !me->GetBodyInterface()->IsActivity( ACT_MP_STAND_ITEM1 ) )
  2285. {
  2286. me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM1 );
  2287. }
  2288. }
  2289. return Continue();
  2290. }
  2291. //---------------------------------------------------------------------------------------------
  2292. ActionResult< CBotNPC > CBotNPCAttack::OnResume( CBotNPC *me, Action< CBotNPC > *interruptingAction )
  2293. {
  2294. me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
  2295. return Continue();
  2296. }
  2297. //---------------------------------------------------------------------------------------------
  2298. EventDesiredResult< CBotNPC > CBotNPCAttack::OnStuck( CBotNPC *me )
  2299. {
  2300. // we're stuck - just warp to the our next path goal
  2301. if ( m_path.GetCurrentGoal() )
  2302. {
  2303. me->SetAbsOrigin( m_path.GetCurrentGoal()->pos + Vector( 0, 0, 10.0f ) );
  2304. }
  2305. return TryContinue( RESULT_TRY );
  2306. }
  2307. //---------------------------------------------------------------------------------------------
  2308. EventDesiredResult< CBotNPC > CBotNPCAttack::OnContact( CBotNPC *me, CBaseEntity *other, CGameTrace *result )
  2309. {
  2310. return TryContinue( RESULT_TRY );
  2311. }
  2312. //---------------------------------------------------------------------------------------------
  2313. //---------------------------------------------------------------------------------------------
  2314. class CBotNPCGuardSpot : public Action< CBotNPC >
  2315. {
  2316. public:
  2317. //-----------------------------------------------------------------------------------------------------
  2318. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  2319. {
  2320. m_path.SetMinLookAheadDistance( 300.0f );
  2321. me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM1 );
  2322. me->SetHomePosition( me->GetAbsOrigin() );
  2323. m_lookAtSpot = vec3_origin;
  2324. return Continue();
  2325. }
  2326. //-----------------------------------------------------------------------------------------------------
  2327. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval )
  2328. {
  2329. CBaseCombatCharacter *target = me->GetAttackTarget();
  2330. if ( target )
  2331. {
  2332. if ( me->IsLineOfSightClear( target ) || me->IsPrisonerOfMinion( target ) )
  2333. {
  2334. return SuspendFor( new CBotNPCChaseVictim( me->GetAttackTarget() ), "Get 'em!" );
  2335. }
  2336. }
  2337. CBaseCombatCharacter *visible = me->GetNearestVisibleEnemy();
  2338. if ( visible )
  2339. {
  2340. // look at visible victim out of range
  2341. me->GetLocomotionInterface()->FaceTowards( visible->WorldSpaceCenter() );
  2342. }
  2343. const float atHomeRange = 50.0f;
  2344. if ( me->IsRangeGreaterThan( me->GetHomePosition(), atHomeRange ) )
  2345. {
  2346. if ( m_path.GetAge() > 3.0f )
  2347. {
  2348. CBotNPCPathCost cost( me );
  2349. if ( m_path.Compute( me, me->GetHomePosition(), cost ) == false )
  2350. {
  2351. // can't reach guard post - just jump there for now
  2352. me->Teleport( &me->GetHomePosition(), NULL, NULL );
  2353. }
  2354. }
  2355. m_path.Update( me );
  2356. }
  2357. else
  2358. {
  2359. // on guard spot - look around
  2360. if ( m_lookTimer.IsElapsed() )
  2361. {
  2362. m_lookTimer.Start( RandomFloat( 1.0f, 2.0f ) );
  2363. CTFNavArea *myArea = (CTFNavArea *)me->GetLastKnownArea();
  2364. if ( myArea )
  2365. {
  2366. const CUtlVector< CTFNavArea * > &invasionAreaVector = myArea->GetEnemyInvasionAreaVector( TF_TEAM_RED );
  2367. if ( invasionAreaVector.Count() > 0 )
  2368. {
  2369. // try to not look directly at walls
  2370. const float minGazeRange = 300.0f;
  2371. const int retryCount = 20.0f;
  2372. for( int r=0; r<retryCount; ++r )
  2373. {
  2374. int which = RandomInt( 0, invasionAreaVector.Count()-1 );
  2375. Vector gazeSpot = invasionAreaVector[ which ]->GetRandomPoint() + Vector( 0, 0, 0.75f * HumanHeight );
  2376. if ( me->IsRangeGreaterThan( gazeSpot, minGazeRange ) && me->GetVisionInterface()->IsLineOfSightClear( gazeSpot ) )
  2377. {
  2378. // use maxLookInterval so these looks override body aiming from path following
  2379. m_lookAtSpot = gazeSpot;
  2380. break;
  2381. }
  2382. }
  2383. }
  2384. }
  2385. }
  2386. me->GetLocomotionInterface()->FaceTowards( m_lookAtSpot );
  2387. }
  2388. if ( me->GetLocomotionInterface()->IsAttemptingToMove() )
  2389. {
  2390. // play running animation
  2391. if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
  2392. {
  2393. me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
  2394. }
  2395. }
  2396. else
  2397. {
  2398. // standing still
  2399. if ( !me->GetBodyInterface()->IsActivity( ACT_MP_STAND_ITEM1 ) )
  2400. {
  2401. me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM1 );
  2402. }
  2403. }
  2404. return Continue();
  2405. }
  2406. //-----------------------------------------------------------------------------------------------------
  2407. virtual EventDesiredResult< CBotNPC > OnInjured( CBotNPC *me, const CTakeDamageInfo &info )
  2408. {
  2409. CTFPlayer *attacker = ToTFPlayer( info.GetAttacker() );
  2410. if ( me->HasAbility( CBotNPC::CAN_BE_STUNNED ) && attacker )
  2411. {
  2412. if ( tf_bot_npc_always_stun.GetBool() )
  2413. {
  2414. return TrySuspendFor( new CBotNPCStunned( tf_bot_npc_stunned_duration.GetFloat() ), RESULT_CRITICAL, "CVar force stunned" );
  2415. }
  2416. bool isDeflectedRocket = false;
  2417. CTFBaseRocket *pBaseRocket = dynamic_cast< CTFBaseRocket * >( info.GetInflictor() );
  2418. if ( pBaseRocket && pBaseRocket->GetDeflected() )
  2419. {
  2420. isDeflectedRocket = true;
  2421. }
  2422. const float hardHit = 50.0f;
  2423. bool isPotentialStunHit = info.GetDamage() > hardHit || isDeflectedRocket;
  2424. if ( m_headStunTimer.IsElapsed() && isPotentialStunHit )
  2425. {
  2426. Vector headPos;
  2427. QAngle headAngles;
  2428. if ( me->GetAttachment( "head", headPos, headAngles ) )
  2429. {
  2430. if ( ( info.GetDamagePosition() - headPos ).IsLengthLessThan( tf_bot_npc_head_radius.GetFloat() ) )
  2431. {
  2432. // hit head
  2433. // deflecting consecutive Boss' rockets into his head == stun
  2434. if ( isDeflectedRocket )
  2435. {
  2436. if ( !m_consecutiveRocketTimer.HasStarted() || // first rocket hit
  2437. m_consecutiveRocketTimer.IsElapsed() ) // too much time between hits - treat as first hit
  2438. {
  2439. m_consecutiveRocketTimer.Start( tf_bot_npc_stun_rocket_reflect_duration.GetFloat() );
  2440. m_consecutiveRockets = 1;
  2441. }
  2442. else
  2443. {
  2444. // successive rocket hit
  2445. if ( ++m_consecutiveRockets >= tf_bot_npc_stun_rocket_reflect_count.GetInt() )
  2446. {
  2447. return TrySuspendFor( new CBotNPCStunned( tf_bot_npc_stunned_duration.GetFloat() ), RESULT_CRITICAL, "My own rockets reflected into my head!" );
  2448. }
  2449. }
  2450. me->EmitSound( "RobotBoss.Vulnerable" );
  2451. }
  2452. // look for hard hits from above
  2453. Vector toAttacker = attacker->EyePosition() - headPos;
  2454. toAttacker.NormalizeInPlace();
  2455. if ( toAttacker.z > 0.9f )
  2456. {
  2457. // just got hit in the head from an attacker above me - stun
  2458. m_headStunTimer.Start( 20.0f );
  2459. return TrySuspendFor( new CBotNPCStunned( tf_bot_npc_stunned_duration.GetFloat() ), RESULT_CRITICAL, "Hard head hit from above!" );
  2460. }
  2461. }
  2462. }
  2463. }
  2464. }
  2465. return TryContinue();
  2466. }
  2467. //-----------------------------------------------------------------------------------------------------
  2468. virtual const char *GetName( void ) const { return "GuardSpot"; } // return name of this action
  2469. private:
  2470. CTFPathFollower m_path;
  2471. CountdownTimer m_lookTimer;
  2472. Vector m_lookAtSpot;
  2473. CountdownTimer m_headStunTimer;
  2474. CountdownTimer m_consecutiveRocketTimer;
  2475. int m_consecutiveRockets;
  2476. };
  2477. //---------------------------------------------------------------------------------------------
  2478. ConVar tf_bot_npc_get_off_me_duration( "tf_bot_npc_get_off_me_duration", "3"/*, FCVAR_CHEAT */ );
  2479. ActionResult< CBotNPC > CBotNPCGetOffMe::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  2480. {
  2481. me->AddGestureSequence( me->LookupSequence( "gesture_melee_help" ) );
  2482. m_timer.Start( 0.5f );
  2483. me->AddCondition( CBotNPC::BUSY );
  2484. return Continue();
  2485. }
  2486. //---------------------------------------------------------------------------------------------
  2487. ActionResult< CBotNPC > CBotNPCGetOffMe::Update( CBotNPC *me, float interval )
  2488. {
  2489. if ( m_timer.IsElapsed() )
  2490. {
  2491. // blast players off of my head
  2492. CUtlVector< CTFPlayer * > onMeVector;
  2493. me->CollectPlayersStandingOnMe( &onMeVector );
  2494. Vector headPos;
  2495. QAngle headAngles;
  2496. if ( me->GetAttachment( "head", headPos, headAngles ) )
  2497. {
  2498. for( int i=0; i<onMeVector.Count(); ++i )
  2499. {
  2500. // push 'em off
  2501. PushawayPlayer( onMeVector[i], headPos, tf_bot_npc_charge_pushaway_force.GetFloat() );
  2502. }
  2503. }
  2504. me->EmitSound( "Weapon_FlameThrower.AirBurstAttack" );
  2505. return Done();
  2506. }
  2507. return Continue();
  2508. }
  2509. //---------------------------------------------------------------------------------------------
  2510. void CBotNPCGetOffMe::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  2511. {
  2512. me->RemoveCondition( CBotNPC::BUSY );
  2513. }
  2514. //---------------------------------------------------------------------------------------------
  2515. //---------------------------------------------------------------------------------------------
  2516. class CBotNPCWaitForPlayers : public Action< CBotNPC >
  2517. {
  2518. public:
  2519. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction );
  2520. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval );
  2521. virtual void OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction );
  2522. virtual EventDesiredResult< CBotNPC > OnInjured( CBotNPC *me, const CTakeDamageInfo &info );
  2523. virtual EventDesiredResult< CBotNPC > OnContact( CBotNPC *me, CBaseEntity *other, CGameTrace *result = NULL );
  2524. virtual const char *GetName( void ) const { return "WaitForPlayers"; } // return name of this action
  2525. };
  2526. //---------------------------------------------------------------------------------------------
  2527. ActionResult< CBotNPC > CBotNPCWaitForPlayers::OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  2528. {
  2529. me->AddCondition( CBotNPC::BUSY );
  2530. return Continue();
  2531. }
  2532. //---------------------------------------------------------------------------------------------
  2533. ActionResult< CBotNPC > CBotNPCWaitForPlayers::Update( CBotNPC *me, float interval )
  2534. {
  2535. CBaseCombatCharacter *target = me->GetAttackTarget();
  2536. if ( target )
  2537. {
  2538. return ChangeTo( new CBotNPCGuardSpot, "I see you..." );
  2539. }
  2540. return Continue();
  2541. }
  2542. //---------------------------------------------------------------------------------------------
  2543. void CBotNPCWaitForPlayers::OnEnd( CBotNPC *me, Action< CBotNPC > *nextAction )
  2544. {
  2545. me->RemoveCondition( CBotNPC::BUSY );
  2546. me->GetNukeTimer()->Start( tf_bot_npc_nuke_interval.GetFloat() );
  2547. me->GetGrenadeTimer()->Reset();
  2548. }
  2549. //---------------------------------------------------------------------------------------------
  2550. EventDesiredResult< CBotNPC > CBotNPCWaitForPlayers::OnInjured( CBotNPC *me, const CTakeDamageInfo &info )
  2551. {
  2552. return TryChangeTo( new CBotNPCGuardSpot, RESULT_CRITICAL, "Ouch!" );
  2553. }
  2554. //---------------------------------------------------------------------------------------------
  2555. EventDesiredResult< CBotNPC > CBotNPCWaitForPlayers::OnContact( CBotNPC *me, CBaseEntity *other, CGameTrace *result )
  2556. {
  2557. if ( other && other->IsPlayer() )
  2558. {
  2559. return TryChangeTo( new CBotNPCGuardSpot, RESULT_CRITICAL, "Don't touch me" );
  2560. }
  2561. return TryContinue();
  2562. }
  2563. //---------------------------------------------------------------------------------------------
  2564. //---------------------------------------------------------------------------------------------
  2565. class CBotNPCTacticalMonitor : public Action< CBotNPC >
  2566. {
  2567. public:
  2568. virtual Action< CBotNPC > *InitialContainedAction( CBotNPC *me )
  2569. {
  2570. if ( TFGameRules()->IsBossBattleMode() )
  2571. {
  2572. return new CBotNPCWaitForPlayers;
  2573. }
  2574. return NULL;
  2575. }
  2576. virtual ActionResult< CBotNPC > OnStart( CBotNPC *me, Action< CBotNPC > *priorAction )
  2577. {
  2578. m_getOffMeTimer.Invalidate();
  2579. return Continue();
  2580. }
  2581. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval )
  2582. {
  2583. // HACK: If we fell off the ledge, jump back
  2584. /*
  2585. if ( me->GetLocomotionInterface()->IsOnGround() &&
  2586. me->GetAbsOrigin().z < me->GetHomePosition().z - 200.0f )
  2587. {
  2588. return SuspendFor( new CBotNPCBigJump( me->GetHomePosition(), new CBotNPCLaunchRockets ), "Jumping home" );
  2589. }
  2590. */
  2591. if ( !m_getOffMeTimer.HasStarted() )
  2592. {
  2593. CUtlVector< CTFPlayer * > onMeVector;
  2594. me->CollectPlayersStandingOnMe( &onMeVector );
  2595. if ( onMeVector.Count() )
  2596. {
  2597. // someone is standing on me - push them off soon
  2598. m_getOffMeTimer.Start( tf_bot_npc_get_off_me_duration.GetFloat() );
  2599. }
  2600. }
  2601. else if ( m_getOffMeTimer.IsElapsed() )
  2602. {
  2603. if ( !me->IsBusy() )
  2604. {
  2605. m_getOffMeTimer.Invalidate();
  2606. // if someone is still on me, push them off
  2607. CUtlVector< CTFPlayer * > onMeVector;
  2608. me->CollectPlayersStandingOnMe( &onMeVector );
  2609. if ( onMeVector.Count() )
  2610. {
  2611. return SuspendFor( new CBotNPCGetOffMe, "Get offa me!" );
  2612. }
  2613. }
  2614. }
  2615. return Continue();
  2616. }
  2617. virtual const char *GetName( void ) const { return "TacticalMonitor"; } // return name of this action
  2618. private:
  2619. CountdownTimer m_backOffCooldownTimer;
  2620. CountdownTimer m_getOffMeTimer;
  2621. };
  2622. //---------------------------------------------------------------------------------------------
  2623. //---------------------------------------------------------------------------------------------
  2624. class CBotNPCBehavior : public Action< CBotNPC >
  2625. {
  2626. public:
  2627. virtual Action< CBotNPC > *InitialContainedAction( CBotNPC *me )
  2628. {
  2629. return new CBotNPCTacticalMonitor;
  2630. }
  2631. virtual ActionResult< CBotNPC > Update( CBotNPC *me, float interval )
  2632. {
  2633. if ( m_vocalTimer.IsElapsed() )
  2634. {
  2635. m_vocalTimer.Start( RandomFloat( 3.0f, 5.0f ) );
  2636. if ( !me->IsBusy() )
  2637. {
  2638. me->EmitSound( "RobotBoss.Vocalize" );
  2639. }
  2640. }
  2641. return Continue();
  2642. }
  2643. virtual EventDesiredResult< CBotNPC > OnKilled( CBotNPC *me, const CTakeDamageInfo &info )
  2644. {
  2645. // relay the event to the map logic
  2646. CTFSpawnerBoss *spawner = me->GetSpawner();
  2647. if ( spawner )
  2648. {
  2649. spawner->OnBotKilled( me );
  2650. }
  2651. // Calculate death force
  2652. Vector forceVector = me->CalcDamageForceVector( info );
  2653. // See if there's a ragdoll magnet that should influence our force.
  2654. CRagdollMagnet *magnet = CRagdollMagnet::FindBestMagnet( me );
  2655. if ( magnet )
  2656. {
  2657. forceVector += magnet->GetForceVector( me );
  2658. }
  2659. if ( me->IsMiniBoss() )
  2660. {
  2661. me->EmitSound( "Cart.Explode" );
  2662. me->BecomeRagdoll( info, forceVector );
  2663. if ( g_pMonsterResource )
  2664. {
  2665. g_pMonsterResource->HideBossHealthMeter();
  2666. }
  2667. }
  2668. else
  2669. {
  2670. // full end-of-game boss
  2671. UTIL_Remove( me );
  2672. if ( TFGameRules()->IsBossBattleMode() )
  2673. {
  2674. // check that ALL bosses are dead
  2675. bool isBossBattleWon = true;
  2676. CBotNPC *boss = NULL;
  2677. while( ( boss = (CBotNPC *)gEntList.FindEntityByClassname( boss, "bot_boss" ) ) != NULL )
  2678. {
  2679. if ( !me->IsSelf( boss ) && boss->IsAlive() && !boss->IsMiniBoss() )
  2680. {
  2681. isBossBattleWon = false;
  2682. }
  2683. }
  2684. if ( isBossBattleWon )
  2685. {
  2686. TFGameRules()->SetWinningTeam( TF_TEAM_BLUE, WINREASON_OPPONENTS_DEAD );
  2687. if ( g_pMonsterResource )
  2688. {
  2689. g_pMonsterResource->HideBossHealthMeter();
  2690. }
  2691. }
  2692. }
  2693. }
  2694. return TryDone();
  2695. }
  2696. virtual EventDesiredResult< CBotNPC > OnContact( CBotNPC *me, CBaseEntity *other, CGameTrace *result = NULL )
  2697. {
  2698. return TryContinue();
  2699. }
  2700. virtual const char *GetName( void ) const { return "Behavior"; } // return name of this action
  2701. private:
  2702. CountdownTimer m_vocalTimer;
  2703. };
  2704. //---------------------------------------------------------------------------------------------
  2705. //---------------------------------------------------------------------------------------------
  2706. CBotNPCIntention::CBotNPCIntention( CBotNPC *me ) : IIntention( me )
  2707. {
  2708. m_behavior = new Behavior< CBotNPC >( new CBotNPCBehavior );
  2709. }
  2710. CBotNPCIntention::~CBotNPCIntention()
  2711. {
  2712. delete m_behavior;
  2713. }
  2714. void CBotNPCIntention::Reset( void )
  2715. {
  2716. delete m_behavior;
  2717. m_behavior = new Behavior< CBotNPC >( new CBotNPCBehavior );
  2718. }
  2719. void CBotNPCIntention::Update( void )
  2720. {
  2721. m_behavior->Update( static_cast< CBotNPC * >( GetBot() ), GetUpdateInterval() );
  2722. }
  2723. // is the a place we can be?
  2724. QueryResultType CBotNPCIntention::IsPositionAllowed( const INextBot *meBot, const Vector &pos ) const
  2725. {
  2726. return ANSWER_YES;
  2727. }
  2728. //---------------------------------------------------------------------------------------------
  2729. //---------------------------------------------------------------------------------------------
  2730. CBotNPCLocomotion::CBotNPCLocomotion( INextBot *bot ) : NextBotGroundLocomotion( bot )
  2731. {
  2732. CBotNPC *me = (CBotNPC *)GetBot()->GetEntity();
  2733. m_runSpeed = me->GetMoveSpeed();
  2734. }
  2735. //---------------------------------------------------------------------------------------------
  2736. float CBotNPCLocomotion::GetRunSpeed( void ) const
  2737. {
  2738. CBotNPC *me = (CBotNPC *)GetBot()->GetEntity();
  2739. return me->IsInCondition( CBotNPC::CHARGING ) ? 1000.0f : m_runSpeed;
  2740. }
  2741. //---------------------------------------------------------------------------------------------
  2742. // if delta Z is greater than this, we have to jump to get up
  2743. float CBotNPCLocomotion::GetStepHeight( void ) const
  2744. {
  2745. return 18.0f;
  2746. }
  2747. //---------------------------------------------------------------------------------------------
  2748. // return maximum height of a jump
  2749. float CBotNPCLocomotion::GetMaxJumpHeight( void ) const
  2750. {
  2751. return 18.0f;
  2752. }
  2753. //---------------------------------------------------------------------------------------------
  2754. // Return true to completely ignore this entity (may not be in sight when this is called)
  2755. bool CBotNPCVision::IsIgnored( CBaseEntity *subject ) const
  2756. {
  2757. if ( subject->IsPlayer() )
  2758. {
  2759. CTFPlayer *enemy = static_cast< CTFPlayer * >( subject );
  2760. if ( enemy->m_Shared.InCond( TF_COND_BURNING ) ||
  2761. enemy->m_Shared.InCond( TF_COND_URINE ) ||
  2762. enemy->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
  2763. enemy->m_Shared.InCond( TF_COND_BLEEDING ) )
  2764. {
  2765. // always notice players with these conditions
  2766. return false;
  2767. }
  2768. if ( enemy->m_Shared.IsStealthed() )
  2769. {
  2770. if ( enemy->m_Shared.GetPercentInvisible() < 0.75f )
  2771. {
  2772. // spy is partially cloaked, and therefore attracts our attention
  2773. return false;
  2774. }
  2775. // invisible!
  2776. return true;
  2777. }
  2778. if ( enemy->IsPlacingSapper() )
  2779. {
  2780. return false;
  2781. }
  2782. if ( enemy->m_Shared.InCond( TF_COND_DISGUISING ) )
  2783. {
  2784. return false;
  2785. }
  2786. if ( enemy->m_Shared.InCond( TF_COND_DISGUISED ) && enemy->m_Shared.GetDisguiseTeam() == GetBot()->GetEntity()->GetTeamNumber() )
  2787. {
  2788. // spy is disguised as a member of my team
  2789. return true;
  2790. }
  2791. }
  2792. return false;
  2793. }
  2794. #endif // TF_RAID_MODE
  2795. #endif // OBSOLETE_USE_BOSS_ALPHA