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.

523 lines
14 KiB

  1. // NextBotCombatCharacter.cpp
  2. // Next generation bot system
  3. // Author: Michael Booth, April 2005
  4. //========= Copyright Valve Corporation, All rights reserved. ============//
  5. #include "cbase.h"
  6. #include "team.h"
  7. #include "CRagdollMagnet.h"
  8. #include "NextBot.h"
  9. #include "NextBotLocomotionInterface.h"
  10. #include "NextBotBodyInterface.h"
  11. #ifdef TERROR
  12. #include "TerrorGamerules.h"
  13. #endif
  14. #include "vprof.h"
  15. #include "datacache/imdlcache.h"
  16. #include "EntityFlame.h"
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. ConVar NextBotStop( "nb_stop", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Stop all NextBots" );
  20. //--------------------------------------------------------------------------------------------------------
  21. class CSendBotCommand
  22. {
  23. public:
  24. CSendBotCommand( const char *command )
  25. {
  26. m_command = command;
  27. }
  28. bool operator() ( INextBot *bot )
  29. {
  30. bot->OnCommandString( m_command );
  31. return true;
  32. }
  33. const char *m_command;
  34. };
  35. CON_COMMAND_F( nb_command, "Sends a command string to all bots", FCVAR_CHEAT )
  36. {
  37. if ( args.ArgC() <= 1 )
  38. {
  39. Msg( "Missing command string" );
  40. return;
  41. }
  42. CSendBotCommand sendCmd( args.ArgS() );
  43. TheNextBots().ForEachBot( sendCmd );
  44. }
  45. //-----------------------------------------------------------------------------------------------------
  46. BEGIN_DATADESC( NextBotCombatCharacter )
  47. DEFINE_THINKFUNC( DoThink ),
  48. END_DATADESC()
  49. //-----------------------------------------------------------------------------------------------------
  50. IMPLEMENT_SERVERCLASS_ST( NextBotCombatCharacter, DT_NextBot )
  51. END_SEND_TABLE()
  52. //-----------------------------------------------------------------------------------------------------
  53. NextBotDestroyer::NextBotDestroyer( int team )
  54. {
  55. m_team = team;
  56. }
  57. //-----------------------------------------------------------------------------------------------------
  58. bool NextBotDestroyer::operator() ( INextBot *bot )
  59. {
  60. if ( m_team == TEAM_ANY || bot->GetEntity()->GetTeamNumber() == m_team )
  61. {
  62. // players need to be kicked, not deleted
  63. if ( bot->GetEntity()->IsPlayer() )
  64. {
  65. CBasePlayer *player = dynamic_cast< CBasePlayer * >( bot->GetEntity() );
  66. engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", player->GetUserID() ) );
  67. }
  68. else
  69. {
  70. UTIL_Remove( bot->GetEntity() );
  71. }
  72. }
  73. return true;
  74. }
  75. //-----------------------------------------------------------------------------------------------------
  76. CON_COMMAND_F( nb_delete_all, "Delete all non-player NextBot entities.", FCVAR_CHEAT )
  77. {
  78. // Listenserver host or rcon access only!
  79. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  80. return;
  81. CTeam *team = NULL;
  82. if ( args.ArgC() == 2 )
  83. {
  84. const char *teamName = args[1];
  85. for( int i=0; i < g_Teams.Count(); ++i )
  86. {
  87. if ( FStrEq( teamName, g_Teams[i]->GetName() ) )
  88. {
  89. // delete all bots on this team
  90. team = g_Teams[i];
  91. break;
  92. }
  93. }
  94. if ( team == NULL )
  95. {
  96. Msg( "Invalid team '%s'\n", teamName );
  97. return;
  98. }
  99. }
  100. // delete all bots on all teams
  101. NextBotDestroyer destroyer( team ? team->GetTeamNumber() : TEAM_ANY );
  102. TheNextBots().ForEachBot( destroyer );
  103. }
  104. //-----------------------------------------------------------------------------------------------------
  105. class NextBotApproacher
  106. {
  107. public:
  108. NextBotApproacher( void )
  109. {
  110. CBasePlayer *player = UTIL_GetListenServerHost();
  111. if ( player )
  112. {
  113. Vector forward;
  114. player->EyeVectors( &forward );
  115. trace_t result;
  116. unsigned int mask = MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE | CONTENTS_GRATE | CONTENTS_WINDOW;
  117. UTIL_TraceLine( player->EyePosition(), player->EyePosition() + 999999.9f * forward, mask, player, COLLISION_GROUP_NONE, &result );
  118. if ( result.DidHit() )
  119. {
  120. NDebugOverlay::Cross3D( result.endpos, 5, 0, 255, 0, true, 10.0f );
  121. m_isGoalValid = true;
  122. m_goal = result.endpos;
  123. }
  124. else
  125. {
  126. m_isGoalValid = false;
  127. }
  128. }
  129. }
  130. bool operator() ( INextBot *bot )
  131. {
  132. if ( TheNextBots().IsDebugFilterMatch( bot ) )
  133. {
  134. bot->OnCommandApproach( m_goal );
  135. }
  136. return true;
  137. }
  138. bool m_isGoalValid;
  139. Vector m_goal;
  140. };
  141. CON_COMMAND_F( nb_move_to_cursor, "Tell all NextBots to move to the cursor position", FCVAR_CHEAT )
  142. {
  143. // Listenserver host or rcon access only!
  144. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  145. return;
  146. NextBotApproacher approach;
  147. TheNextBots().ForEachBot( approach );
  148. }
  149. //----------------------------------------------------------------------------------------------------------
  150. //----------------------------------------------------------------------------------------------------------
  151. bool IgnoreActorsTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
  152. {
  153. CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
  154. return ( entity->MyCombatCharacterPointer() == NULL ); // includes all bots, npcs, players, and TF2 buildings
  155. }
  156. //----------------------------------------------------------------------------------------------------------
  157. //----------------------------------------------------------------------------------------------------------
  158. bool VisionTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
  159. {
  160. // Honor BlockLOS also to allow seeing through partially-broken doors
  161. CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
  162. return ( entity->MyCombatCharacterPointer() == NULL && entity->BlocksLOS() );
  163. }
  164. //----------------------------------------------------------------------------------------------------------
  165. //----------------------------------------------------------------------------------------------------------
  166. NextBotCombatCharacter::NextBotCombatCharacter( void )
  167. {
  168. m_lastAttacker = NULL;
  169. m_didModelChange = false;
  170. }
  171. //----------------------------------------------------------------------------------------------------------
  172. void NextBotCombatCharacter::Spawn( void )
  173. {
  174. BaseClass::Spawn();
  175. // reset bot components
  176. Reset();
  177. SetSolid( SOLID_BBOX );
  178. AddSolidFlags( FSOLID_NOT_STANDABLE );
  179. SetMoveType( MOVETYPE_CUSTOM );
  180. SetCollisionGroup( COLLISION_GROUP_PLAYER );
  181. m_iMaxHealth = m_iHealth;
  182. m_takedamage = DAMAGE_YES;
  183. MDLCACHE_CRITICAL_SECTION();
  184. InitBoneControllers( );
  185. // set up think callback
  186. SetThink( &NextBotCombatCharacter::DoThink );
  187. SetNextThink( gpGlobals->curtime );
  188. m_lastAttacker = NULL;
  189. }
  190. bool NextBotCombatCharacter::IsAreaTraversable( const CNavArea *area ) const
  191. {
  192. if ( !area )
  193. return false;
  194. ILocomotion *mover = GetLocomotionInterface();
  195. if ( mover && !mover->IsAreaTraversable( area ) )
  196. return false;
  197. return BaseClass::IsAreaTraversable( area );
  198. }
  199. //----------------------------------------------------------------------------------------------------------
  200. void NextBotCombatCharacter::DoThink( void )
  201. {
  202. VPROF_BUDGET( "NextBotCombatCharacter::DoThink", "NextBot" );
  203. SetNextThink( gpGlobals->curtime );
  204. if ( BeginUpdate() )
  205. {
  206. // emit model change event
  207. if ( m_didModelChange )
  208. {
  209. m_didModelChange = false;
  210. OnModelChanged();
  211. // propagate model change into NextBot event responders
  212. for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
  213. {
  214. sub->OnModelChanged();
  215. }
  216. }
  217. UpdateLastKnownArea();
  218. // update bot components
  219. if ( !NextBotStop.GetBool() && (GetFlags() & FL_FROZEN) == 0 )
  220. {
  221. Update();
  222. }
  223. EndUpdate();
  224. }
  225. }
  226. //----------------------------------------------------------------------------------------------------------
  227. void NextBotCombatCharacter::Touch( CBaseEntity *other )
  228. {
  229. if ( ShouldTouch( other ) )
  230. {
  231. // propagate touch into NextBot event responders
  232. trace_t result;
  233. result = GetTouchTrace();
  234. // OnContact refers to *physical* contact, not triggers or other non-physical entities
  235. if ( result.DidHit() || other->MyCombatCharacterPointer() != NULL )
  236. {
  237. OnContact( other, &result );
  238. }
  239. }
  240. BaseClass::Touch( other );
  241. }
  242. //----------------------------------------------------------------------------------------------------------
  243. void NextBotCombatCharacter::SetModel( const char *szModelName )
  244. {
  245. // actually change the model
  246. BaseClass::SetModel( szModelName );
  247. // need to do a lazy-check because precache system also invokes this
  248. m_didModelChange = true;
  249. }
  250. //----------------------------------------------------------------------------------------------------------
  251. void NextBotCombatCharacter::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
  252. {
  253. BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
  254. // propagate event to components
  255. OnIgnite();
  256. }
  257. //----------------------------------------------------------------------------------------------------------
  258. void NextBotCombatCharacter::Ignite( float flFlameLifetime, CBaseEntity *pAttacker )
  259. {
  260. if ( IsOnFire() )
  261. return;
  262. // BaseClass::Ignite stuff, plus SetAttacker on the flame, so our attacker gets credit
  263. CEntityFlame *pFlame = CEntityFlame::Create( this );
  264. if ( pFlame )
  265. {
  266. pFlame->SetLifetime( flFlameLifetime );
  267. AddFlag( FL_ONFIRE );
  268. SetEffectEntity( pFlame );
  269. }
  270. m_OnIgnite.FireOutput( this, this );
  271. // propagate event to components
  272. OnIgnite();
  273. }
  274. //----------------------------------------------------------------------------------------------------------
  275. int NextBotCombatCharacter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  276. {
  277. // track our last attacker
  278. if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
  279. {
  280. m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
  281. }
  282. // propagate event to components
  283. OnInjured( info );
  284. return CBaseCombatCharacter::OnTakeDamage_Alive( info );
  285. }
  286. //----------------------------------------------------------------------------------------------------------
  287. int NextBotCombatCharacter::OnTakeDamage_Dying( const CTakeDamageInfo &info )
  288. {
  289. // track our last attacker
  290. if ( info.GetAttacker()->MyCombatCharacterPointer() )
  291. {
  292. m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
  293. }
  294. // propagate event to components
  295. OnInjured( info );
  296. return CBaseCombatCharacter::OnTakeDamage_Dying( info );
  297. }
  298. //----------------------------------------------------------------------------------------------------------
  299. /**
  300. * Can't use CBaseCombatCharacter's Event_Killed because it will immediately ragdoll us
  301. */
  302. static int g_DeathStartEvent = 0;
  303. void NextBotCombatCharacter::Event_Killed( const CTakeDamageInfo &info )
  304. {
  305. // track our last attacker
  306. if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
  307. {
  308. m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
  309. }
  310. // propagate event to my components
  311. OnKilled( info );
  312. // Advance life state to dying
  313. m_lifeState = LIFE_DYING;
  314. #ifdef TERROR
  315. /*
  316. * TODO: Make this game-generic
  317. */
  318. // Create the death event just like players do.
  319. TerrorGameRules()->DeathNoticeForEntity( this, info );
  320. // Infected specific event
  321. TerrorGameRules()->DeathNoticeForInfected( this, info );
  322. #endif
  323. if ( GetOwnerEntity() != NULL )
  324. {
  325. GetOwnerEntity()->DeathNotice( this );
  326. }
  327. // inform the other bots
  328. TheNextBots().OnKilled( this, info );
  329. }
  330. //----------------------------------------------------------------------------------------------------------
  331. void NextBotCombatCharacter::PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity )
  332. {
  333. ILocomotion *mover = GetLocomotionInterface();
  334. if ( mover )
  335. {
  336. // hack to keep ground entity from being NULL'd when Z velocity is positive
  337. SetGroundEntity( mover->GetGround() );
  338. }
  339. }
  340. //----------------------------------------------------------------------------------------------------------
  341. bool NextBotCombatCharacter::BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector )
  342. {
  343. // See if there's a ragdoll magnet that should influence our force.
  344. Vector adjustedForceVector = forceVector;
  345. CRagdollMagnet *magnet = CRagdollMagnet::FindBestMagnet( this );
  346. if ( magnet )
  347. {
  348. adjustedForceVector += magnet->GetForceVector( this );
  349. }
  350. // clear the deceased's sound channels.(may have been firing or reloading when killed)
  351. EmitSound( "BaseCombatCharacter.StopWeaponSounds" );
  352. return BaseClass::BecomeRagdoll( info, adjustedForceVector );
  353. }
  354. //----------------------------------------------------------------------------------------------------------
  355. void NextBotCombatCharacter::HandleAnimEvent( animevent_t *event )
  356. {
  357. // propagate event to components
  358. OnAnimationEvent( event );
  359. }
  360. //----------------------------------------------------------------------------------------------------------
  361. /**
  362. * Propagate event into NextBot event responders
  363. */
  364. void NextBotCombatCharacter::OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea )
  365. {
  366. INextBotEventResponder::OnNavAreaChanged( enteredArea, leftArea );
  367. BaseClass::OnNavAreaChanged( enteredArea, leftArea );
  368. }
  369. //----------------------------------------------------------------------------------------------------------
  370. Vector NextBotCombatCharacter::EyePosition( void )
  371. {
  372. if ( GetBodyInterface() )
  373. {
  374. return GetBodyInterface()->GetEyePosition();
  375. }
  376. return BaseClass::EyePosition();
  377. }
  378. //----------------------------------------------------------------------------------------------------------
  379. /**
  380. * Return true if this object can be +used by the bot
  381. */
  382. bool NextBotCombatCharacter::IsUseableEntity( CBaseEntity *entity, unsigned int requiredCaps )
  383. {
  384. if ( entity )
  385. {
  386. int caps = entity->ObjectCaps();
  387. if ( caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) )
  388. {
  389. if ( (caps & requiredCaps) == requiredCaps )
  390. {
  391. return true;
  392. }
  393. }
  394. }
  395. return false;
  396. }
  397. //----------------------------------------------------------------------------------------------------------
  398. void NextBotCombatCharacter::UseEntity( CBaseEntity *entity, USE_TYPE useType )
  399. {
  400. if ( IsUseableEntity( entity ) )
  401. {
  402. variant_t emptyVariant;
  403. entity->AcceptInput( "Use", this, this, emptyVariant, useType );
  404. }
  405. }