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.

482 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. // tf_bot_vision.cpp
  3. // Team Fortress NextBot vision interface
  4. // Michael Booth, May 2009
  5. #include "cbase.h"
  6. #include "vprof.h"
  7. #include "tf_bot.h"
  8. #include "tf_bot_vision.h"
  9. #include "tf_player.h"
  10. #include "tf_gamerules.h"
  11. #include "tf_obj_sentrygun.h"
  12. ConVar tf_bot_choose_target_interval( "tf_bot_choose_target_interval", "0.3f", FCVAR_CHEAT, "How often, in seconds, a TFBot can reselect his target" );
  13. ConVar tf_bot_sniper_choose_target_interval( "tf_bot_sniper_choose_target_interval", "3.0f", FCVAR_CHEAT, "How often, in seconds, a zoomed-in Sniper can reselect his target" );
  14. //------------------------------------------------------------------------------------------
  15. // Update internal state
  16. void CTFBotVision::Update( void )
  17. {
  18. if ( TFGameRules()->IsMannVsMachineMode() )
  19. {
  20. // Throttle vision update rate of robots in MvM for perf at the expense of reaction times
  21. if ( !m_scanTimer.IsElapsed() )
  22. {
  23. return;
  24. }
  25. m_scanTimer.Start( RandomFloat( 0.9f, 1.1f ) );
  26. }
  27. IVision::Update();
  28. CTFBot *me = (CTFBot *)GetBot()->GetEntity();
  29. if ( !me )
  30. return;
  31. // forget spies we have lost sight of
  32. CUtlVector< CTFPlayer * > playerVector;
  33. CollectPlayers( &playerVector, GetEnemyTeam( me->GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS );
  34. for( int i=0; i<playerVector.Count(); ++i )
  35. {
  36. if ( !playerVector[i]->IsPlayerClass( TF_CLASS_SPY ) )
  37. continue;
  38. const CKnownEntity *known = GetKnown( playerVector[i] );
  39. if ( !known || !known->IsVisibleRecently() )
  40. {
  41. // if a hidden spy changes disguises, we no longer recognize him
  42. if ( playerVector[i]->m_Shared.InCond( TF_COND_DISGUISING ) )
  43. {
  44. me->ForgetSpy( playerVector[i] );
  45. }
  46. }
  47. }
  48. }
  49. //------------------------------------------------------------------------------------------
  50. void CTFBotVision::CollectPotentiallyVisibleEntities( CUtlVector< CBaseEntity * > *potentiallyVisible )
  51. {
  52. VPROF_BUDGET( "CTFBotVision::CollectPotentiallyVisibleEntities", "NextBot" );
  53. potentiallyVisible->RemoveAll();
  54. // include all players
  55. for( int i=1; i<=gpGlobals->maxClients; ++i )
  56. {
  57. CBasePlayer *player = UTIL_PlayerByIndex( i );
  58. if ( player == NULL )
  59. continue;
  60. if ( FNullEnt( player->edict() ) )
  61. continue;
  62. if ( !player->IsPlayer() )
  63. continue;
  64. if ( !player->IsConnected() )
  65. continue;
  66. if ( !player->IsAlive() )
  67. continue;
  68. potentiallyVisible->AddToTail( player );
  69. }
  70. // include sentry guns
  71. UpdatePotentiallyVisibleNPCVector();
  72. FOR_EACH_VEC( m_potentiallyVisibleNPCVector, it )
  73. {
  74. potentiallyVisible->AddToTail( m_potentiallyVisibleNPCVector[ it ] );
  75. }
  76. }
  77. //------------------------------------------------------------------------------------------
  78. void CTFBotVision::UpdatePotentiallyVisibleNPCVector( void )
  79. {
  80. if ( m_potentiallyVisibleUpdateTimer.IsElapsed() )
  81. {
  82. m_potentiallyVisibleUpdateTimer.Start( RandomFloat( 3.0f, 4.0f ) );
  83. // collect list of active buildings
  84. m_potentiallyVisibleNPCVector.RemoveAll();
  85. bool bShouldSeeTeleporter = !TFGameRules()->IsMannVsMachineMode() || GetBot()->GetEntity()->GetTeamNumber() != TF_TEAM_PVE_INVADERS;
  86. for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
  87. {
  88. CBaseObject* pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
  89. if ( pObj->ObjectType() == OBJ_SENTRYGUN )
  90. {
  91. m_potentiallyVisibleNPCVector.AddToTail( pObj );
  92. }
  93. else if ( pObj->ObjectType() == OBJ_DISPENSER && pObj->ClassMatches( "obj_dispenser" ) )
  94. {
  95. m_potentiallyVisibleNPCVector.AddToTail( pObj );
  96. }
  97. else if ( bShouldSeeTeleporter && pObj->ObjectType() == OBJ_TELEPORTER )
  98. {
  99. m_potentiallyVisibleNPCVector.AddToTail( pObj );
  100. }
  101. }
  102. CUtlVector< INextBot * > botVector;
  103. TheNextBots().CollectAllBots( &botVector );
  104. for( int i=0; i<botVector.Count(); ++i )
  105. {
  106. CBaseCombatCharacter *botEntity = botVector[i]->GetEntity();
  107. if ( botEntity && !botEntity->IsPlayer() )
  108. {
  109. // NPC
  110. m_potentiallyVisibleNPCVector.AddToTail( botEntity );
  111. }
  112. }
  113. }
  114. }
  115. //------------------------------------------------------------------------------------------
  116. /**
  117. * Return true to completely ignore this entity.
  118. * This is mostly for enemy spies. If we don't ignore them, we will look at them.
  119. */
  120. bool CTFBotVision::IsIgnored( CBaseEntity *subject ) const
  121. {
  122. CTFBot *me = (CTFBot *)GetBot()->GetEntity();
  123. #ifdef TF_RAID_MODE
  124. if ( TFGameRules()->IsRaidMode() )
  125. {
  126. if ( me->IsPlayerClass( TF_CLASS_SCOUT ) )
  127. {
  128. // Scouts are wandering defenders, and aggro purely on proximity or damage, not vision
  129. return true;
  130. }
  131. }
  132. #endif // TF_RAID_MODE
  133. if ( me->IsAttentionFocused() )
  134. {
  135. // our attention is restricted to certain subjects
  136. if ( !me->IsAttentionFocusedOn( subject ) )
  137. {
  138. return false;
  139. }
  140. }
  141. if ( !me->IsEnemy( subject ) )
  142. {
  143. // don't ignore friends
  144. return false;
  145. }
  146. if ( subject->IsEffectActive( EF_NODRAW ) )
  147. {
  148. return true;
  149. }
  150. if ( subject->IsPlayer() )
  151. {
  152. CTFPlayer *enemy = static_cast< CTFPlayer * >( subject );
  153. // test for designer-defined ignorance
  154. switch( enemy->GetPlayerClass()->GetClassIndex() )
  155. {
  156. case TF_CLASS_MEDIC:
  157. if ( me->IsBehaviorFlagSet( TFBOT_IGNORE_ENEMY_MEDICS ) )
  158. {
  159. return true;
  160. }
  161. break;
  162. case TF_CLASS_ENGINEER:
  163. if ( me->IsBehaviorFlagSet( TFBOT_IGNORE_ENEMY_ENGINEERS ) )
  164. {
  165. return true;
  166. }
  167. break;
  168. case TF_CLASS_SNIPER:
  169. if ( me->IsBehaviorFlagSet( TFBOT_IGNORE_ENEMY_SNIPERS ) )
  170. {
  171. return true;
  172. }
  173. break;
  174. case TF_CLASS_SCOUT:
  175. if ( me->IsBehaviorFlagSet( TFBOT_IGNORE_ENEMY_SCOUTS ) )
  176. {
  177. return true;
  178. }
  179. break;
  180. case TF_CLASS_SPY:
  181. if ( me->IsBehaviorFlagSet( TFBOT_IGNORE_ENEMY_SPIES ) )
  182. {
  183. return true;
  184. }
  185. break;
  186. case TF_CLASS_DEMOMAN:
  187. if ( me->IsBehaviorFlagSet( TFBOT_IGNORE_ENEMY_DEMOMEN ) )
  188. {
  189. return true;
  190. }
  191. break;
  192. case TF_CLASS_SOLDIER:
  193. if ( me->IsBehaviorFlagSet( TFBOT_IGNORE_ENEMY_SOLDIERS ) )
  194. {
  195. return true;
  196. }
  197. break;
  198. case TF_CLASS_HEAVYWEAPONS:
  199. if ( me->IsBehaviorFlagSet( TFBOT_IGNORE_ENEMY_HEAVIES ) )
  200. {
  201. return true;
  202. }
  203. break;
  204. case TF_CLASS_PYRO:
  205. if ( me->IsBehaviorFlagSet( TFBOT_IGNORE_ENEMY_PYROS ) )
  206. {
  207. return true;
  208. }
  209. break;
  210. }
  211. #ifdef STAGING_ONLY
  212. if ( enemy->m_Shared.InCond( TF_COND_REPROGRAMMED ) )
  213. {
  214. return true;
  215. }
  216. #endif // STAGING_ONLY
  217. if ( me->IsKnownSpy( enemy ) )
  218. {
  219. // don't ignore revealed spies
  220. return false;
  221. }
  222. if ( enemy->m_Shared.InCond( TF_COND_BURNING ) ||
  223. enemy->m_Shared.InCond( TF_COND_URINE ) ||
  224. enemy->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
  225. enemy->m_Shared.InCond( TF_COND_BLEEDING ) )
  226. {
  227. // always notice players with these conditions
  228. return false;
  229. }
  230. // An upgrade in MvM grants AE stealth where the player can fire
  231. // while in stealth, and for a short period after it drops
  232. if ( enemy->m_Shared.InCond( TF_COND_STEALTHED_USER_BUFF_FADING ) )
  233. {
  234. return true;
  235. }
  236. if ( enemy->m_Shared.IsStealthed() )
  237. {
  238. if ( enemy->m_Shared.GetPercentInvisible() < 0.75f )
  239. {
  240. // spy is partially cloaked, and therefore attracts our attention
  241. return false;
  242. }
  243. // invisible!
  244. return true;
  245. }
  246. if ( enemy->IsPlacingSapper() )
  247. {
  248. return false;
  249. }
  250. if ( enemy->m_Shared.InCond( TF_COND_DISGUISING ) )
  251. {
  252. return false;
  253. }
  254. if ( enemy->m_Shared.InCond( TF_COND_DISGUISED ) && enemy->m_Shared.GetDisguiseTeam() == me->GetTeamNumber() )
  255. {
  256. // spy is disguised as a member of my team
  257. return true;
  258. }
  259. }
  260. else if ( subject->IsBaseObject() ) // not a player
  261. {
  262. CBaseObject *object = assert_cast< CBaseObject * >( subject );
  263. if ( object )
  264. {
  265. // ignore sapped enemy objects
  266. if ( object->HasSapper() )
  267. {
  268. // unless we're in MvM where buildings can have really large health pools,
  269. // so an engineer can die and run back in time to repair their stuff
  270. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  271. {
  272. return false;
  273. }
  274. return true;
  275. }
  276. // ignore carried objects
  277. if ( object->IsPlacing() || object->IsCarried() )
  278. {
  279. return true;
  280. }
  281. if ( object->GetType() == OBJ_SENTRYGUN && me->IsBehaviorFlagSet( TFBOT_IGNORE_ENEMY_SENTRY_GUNS ) )
  282. {
  283. return true;
  284. }
  285. }
  286. }
  287. return false;
  288. }
  289. //------------------------------------------------------------------------------------------
  290. // Return true if we 'notice' the subject, even though we have LOS to it
  291. bool CTFBotVision::IsVisibleEntityNoticed( CBaseEntity *subject ) const
  292. {
  293. CTFBot *me = (CTFBot *)GetBot()->GetEntity();
  294. if ( subject->IsPlayer() && me->IsEnemy( subject ) )
  295. {
  296. CTFPlayer *player = static_cast< CTFPlayer * >( subject );
  297. if ( player->m_Shared.InCond( TF_COND_BURNING ) ||
  298. player->m_Shared.InCond( TF_COND_URINE ) ||
  299. player->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
  300. player->m_Shared.InCond( TF_COND_BLEEDING ) )
  301. {
  302. // always notice players with these conditions
  303. if ( player->m_Shared.InCond( TF_COND_STEALTHED ) )
  304. {
  305. me->RealizeSpy( player );
  306. }
  307. return true;
  308. }
  309. #ifdef STAGING_ONLY
  310. // Bots can be hacked/reprogrammed by spies. Ignore.
  311. if ( player->m_Shared.InCond( TF_COND_REPROGRAMMED ) )
  312. {
  313. return false;
  314. }
  315. #endif // STAGING_ONLY
  316. // An upgrade in MvM grants AE stealth where the player can fire
  317. // while in stealth, and for a short period after it drops
  318. if ( player->m_Shared.InCond( TF_COND_STEALTHED_USER_BUFF_FADING ) )
  319. {
  320. me->ForgetSpy( player );
  321. return false;
  322. }
  323. if ( player->m_Shared.IsStealthed() )
  324. {
  325. if ( player->m_Shared.GetPercentInvisible() < 0.75f )
  326. {
  327. // spy is partially cloaked, and therefore attracts our attention
  328. me->RealizeSpy( player );
  329. return true;
  330. }
  331. // invisible!
  332. me->ForgetSpy( player );
  333. return false;
  334. }
  335. if ( TFGameRules()->IsMannVsMachineMode() ) // in MvM mode, forget spies as soon as they are fully disguised
  336. {
  337. CTFBot::SuspectedSpyInfo_t* pSuspectInfo = me->IsSuspectedSpy( player );
  338. // But only if we aren't suspecting them currently. This happens when we bump into them.
  339. if( !pSuspectInfo || !pSuspectInfo->IsCurrentlySuspected() )
  340. {
  341. if ( player->m_Shared.InCond( TF_COND_DISGUISED ) && player->m_Shared.GetDisguiseTeam() == me->GetTeamNumber() )
  342. {
  343. me->ForgetSpy( player );
  344. return false;
  345. }
  346. }
  347. }
  348. if ( me->IsKnownSpy( player ) )
  349. {
  350. // always notice non-invisible revealed spies
  351. return true;
  352. }
  353. if ( !TFGameRules()->IsMannVsMachineMode() ) // ignore in MvM mode
  354. {
  355. if ( player->IsPlacingSapper() )
  356. {
  357. // spotted a spy!
  358. me->RealizeSpy( player );
  359. return true;
  360. }
  361. }
  362. if ( player->m_Shared.InCond( TF_COND_DISGUISING ) )
  363. {
  364. // spotted a spy!
  365. me->RealizeSpy( player );
  366. return true;
  367. }
  368. if ( player->m_Shared.InCond( TF_COND_DISGUISED ) && player->m_Shared.GetDisguiseTeam() == me->GetTeamNumber() )
  369. {
  370. // spy is disguised as a member of my team, don't notice him
  371. return false;
  372. }
  373. }
  374. return true;
  375. }
  376. //------------------------------------------------------------------------------------------
  377. // Return VISUAL reaction time
  378. float CTFBotVision::GetMinRecognizeTime( void ) const
  379. {
  380. CTFBot *me = (CTFBot *)GetBot();
  381. switch ( me->GetDifficulty() )
  382. {
  383. case CTFBot::EASY: return 1.0f;
  384. case CTFBot::NORMAL: return 0.5f;
  385. case CTFBot::HARD: return 0.3f;
  386. case CTFBot::EXPERT: return 0.2f;
  387. }
  388. return 1.0f;
  389. }
  390. //------------------------------------------------------------------------------------------
  391. float CTFBotVision::GetMaxVisionRange( void ) const
  392. {
  393. CTFBot *me = (CTFBot *)GetBot();
  394. if ( me->GetMaxVisionRangeOverride() > 0.0f )
  395. {
  396. // designer specified vision range
  397. return me->GetMaxVisionRangeOverride();
  398. }
  399. // long range, particularly for snipers
  400. return 6000.0f;
  401. }