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.

604 lines
15 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. // Author: Michael S. Booth ([email protected]), 2003
  8. #include "cbase.h"
  9. #include "cs_shareddefs.h"
  10. #include "engine/IEngineSound.h"
  11. #include "KeyValues.h"
  12. #include "bot.h"
  13. #include "bot_util.h"
  14. #include "bot_profile.h"
  15. #include "cs_bot.h"
  16. #include <ctype.h>
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. static int s_iBeamSprite = 0;
  20. //--------------------------------------------------------------------------------------------------------------
  21. /**
  22. * Return true if given name is already in use by another player
  23. */
  24. bool UTIL_IsNameTaken( const char *name, bool ignoreHumans )
  25. {
  26. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  27. {
  28. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  29. if (player == NULL)
  30. continue;
  31. if (player->IsPlayer() && player->IsBot())
  32. {
  33. // bots can have prefixes so we need to check the name
  34. // against the profile name instead.
  35. CCSBot *bot = dynamic_cast<CCSBot *>(player);
  36. if ( bot && bot->GetProfile()->GetName() && FStrEq(name, bot->GetProfile()->GetName()))
  37. {
  38. return true;
  39. }
  40. }
  41. else
  42. {
  43. if (!ignoreHumans)
  44. {
  45. if (FStrEq( name, player->GetPlayerName() ))
  46. return true;
  47. }
  48. }
  49. }
  50. return false;
  51. }
  52. //--------------------------------------------------------------------------------------------------------------
  53. int UTIL_ClientsInGame( void )
  54. {
  55. int count = 0;
  56. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  57. {
  58. CBaseEntity *player = UTIL_PlayerByIndex( i );
  59. if (player == NULL)
  60. continue;
  61. count++;
  62. }
  63. return count;
  64. }
  65. //--------------------------------------------------------------------------------------------------------------
  66. /**
  67. * Return the number of non-bots on the given team
  68. */
  69. int UTIL_HumansOnTeam( int teamID, bool isAlive )
  70. {
  71. int count = 0;
  72. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  73. {
  74. CBaseEntity *entity = UTIL_PlayerByIndex( i );
  75. if ( entity == NULL )
  76. continue;
  77. CBasePlayer *player = static_cast<CBasePlayer *>( entity );
  78. if (player->IsBot())
  79. continue;
  80. if (player->GetTeamNumber() != teamID)
  81. continue;
  82. if (isAlive && !player->IsAlive())
  83. continue;
  84. count++;
  85. }
  86. return count;
  87. }
  88. //--------------------------------------------------------------------------------------------------------------
  89. int UTIL_BotsInGame( void )
  90. {
  91. int count = 0;
  92. for (int i = 1; i <= gpGlobals->maxClients; ++i )
  93. {
  94. CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex( i ));
  95. if ( player == NULL )
  96. continue;
  97. if ( !player->IsBot() )
  98. continue;
  99. count++;
  100. }
  101. return count;
  102. }
  103. //--------------------------------------------------------------------------------------------------------------
  104. /**
  105. * Kick a bot from the given team. If no bot exists on the team, return false.
  106. */
  107. bool UTIL_KickBotFromTeam( int kickTeam )
  108. {
  109. int i;
  110. // try to kick a dead bot first
  111. for ( i = 1; i <= gpGlobals->maxClients; ++i )
  112. {
  113. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  114. if (player == NULL)
  115. continue;
  116. if (!player->IsBot())
  117. continue;
  118. if (!player->IsAlive() && player->GetTeamNumber() == kickTeam)
  119. {
  120. // its a bot on the right team - kick it
  121. engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) );
  122. return true;
  123. }
  124. }
  125. // no dead bots, kick any bot on the given team
  126. for ( i = 1; i <= gpGlobals->maxClients; ++i )
  127. {
  128. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  129. if (player == NULL)
  130. continue;
  131. if (!player->IsBot())
  132. continue;
  133. if (player->GetTeamNumber() == kickTeam)
  134. {
  135. // its a bot on the right team - kick it
  136. engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) );
  137. return true;
  138. }
  139. }
  140. return false;
  141. }
  142. //--------------------------------------------------------------------------------------------------------------
  143. /**
  144. * Return true if all of the members of the given team are bots
  145. */
  146. bool UTIL_IsTeamAllBots( int team )
  147. {
  148. int botCount = 0;
  149. for( int i=1; i <= gpGlobals->maxClients; ++i )
  150. {
  151. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  152. if (player == NULL)
  153. continue;
  154. // skip players on other teams
  155. if (player->GetTeamNumber() != team)
  156. continue;
  157. // if not a bot, fail the test
  158. if (!player->IsBot())
  159. return false;
  160. // is a bot on given team
  161. ++botCount;
  162. }
  163. // if team is empty, there are no bots
  164. return (botCount) ? true : false;
  165. }
  166. //--------------------------------------------------------------------------------------------------------------
  167. /**
  168. * Return the closest active player to the given position.
  169. * If 'distance' is non-NULL, the distance to the closest player is returned in it.
  170. */
  171. extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, float *distance )
  172. {
  173. CBasePlayer *closePlayer = NULL;
  174. float closeDistSq = 999999999999.9f;
  175. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  176. {
  177. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  178. if (!IsEntityValid( player ))
  179. continue;
  180. if (!player->IsAlive())
  181. continue;
  182. Vector playerOrigin = GetCentroid( player );
  183. float distSq = (playerOrigin - pos).LengthSqr();
  184. if (distSq < closeDistSq)
  185. {
  186. closeDistSq = distSq;
  187. closePlayer = static_cast<CBasePlayer *>( player );
  188. }
  189. }
  190. if (distance)
  191. *distance = (float)sqrt( closeDistSq );
  192. return closePlayer;
  193. }
  194. //--------------------------------------------------------------------------------------------------------------
  195. /**
  196. * Return the closest active player on the given team to the given position.
  197. * If 'distance' is non-NULL, the distance to the closest player is returned in it.
  198. */
  199. extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, int team, float *distance )
  200. {
  201. CBasePlayer *closePlayer = NULL;
  202. float closeDistSq = 999999999999.9f;
  203. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  204. {
  205. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  206. if (!IsEntityValid( player ))
  207. continue;
  208. if (!player->IsAlive())
  209. continue;
  210. if (player->GetTeamNumber() != team)
  211. continue;
  212. Vector playerOrigin = GetCentroid( player );
  213. float distSq = (playerOrigin - pos).LengthSqr();
  214. if (distSq < closeDistSq)
  215. {
  216. closeDistSq = distSq;
  217. closePlayer = static_cast<CBasePlayer *>( player );
  218. }
  219. }
  220. if (distance)
  221. *distance = (float)sqrt( closeDistSq );
  222. return closePlayer;
  223. }
  224. //--------------------------------------------------------------------------------------------------------------
  225. // Takes the bot pointer and constructs the net name using the current bot name prefix.
  226. void UTIL_ConstructBotNetName( char *name, int nameLength, const BotProfile *profile )
  227. {
  228. if (profile == NULL)
  229. {
  230. name[0] = 0;
  231. return;
  232. }
  233. // if there is no bot prefix just use the profile name.
  234. if ((cv_bot_prefix.GetString() == NULL) || (strlen(cv_bot_prefix.GetString()) == 0))
  235. {
  236. Q_strncpy( name, profile->GetName(), nameLength );
  237. return;
  238. }
  239. // find the highest difficulty
  240. const char *diffStr = BotDifficultyName[0];
  241. for ( int i=BOT_EXPERT; i>0; --i )
  242. {
  243. if ( profile->IsDifficulty( (BotDifficultyType)i ) )
  244. {
  245. diffStr = BotDifficultyName[i];
  246. break;
  247. }
  248. }
  249. const char *weaponStr = NULL;
  250. if ( profile->GetWeaponPreferenceCount() )
  251. {
  252. weaponStr = profile->GetWeaponPreferenceAsString( 0 );
  253. const char *translatedAlias = GetTranslatedWeaponAlias( weaponStr );
  254. char wpnName[128];
  255. Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", translatedAlias );
  256. WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName );
  257. if ( hWpnInfo != GetInvalidWeaponInfoHandle() )
  258. {
  259. CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
  260. if ( pWeaponInfo )
  261. {
  262. CSWeaponType weaponType = pWeaponInfo->m_WeaponType;
  263. weaponStr = WeaponClassAsString( weaponType );
  264. }
  265. }
  266. }
  267. if ( !weaponStr )
  268. {
  269. weaponStr = "";
  270. }
  271. char skillStr[16];
  272. Q_snprintf( skillStr, sizeof( skillStr ), "%.0f", profile->GetSkill()*100 );
  273. char temp[MAX_PLAYER_NAME_LENGTH*2];
  274. char prefix[MAX_PLAYER_NAME_LENGTH*2];
  275. Q_strncpy( temp, cv_bot_prefix.GetString(), sizeof( temp ) );
  276. Q_StrSubst( temp, "<difficulty>", diffStr, prefix, sizeof( prefix ) );
  277. Q_StrSubst( prefix, "<weaponclass>", weaponStr, temp, sizeof( temp ) );
  278. Q_StrSubst( temp, "<skill>", skillStr, prefix, sizeof( prefix ) );
  279. Q_snprintf( name, nameLength, "%s %s", prefix, profile->GetName() );
  280. }
  281. //--------------------------------------------------------------------------------------------------------------
  282. /**
  283. * Return true if anyone on the given team can see the given spot
  284. */
  285. bool UTIL_IsVisibleToTeam( const Vector &spot, int team )
  286. {
  287. for( int i = 1; i <= gpGlobals->maxClients; ++i )
  288. {
  289. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  290. if (player == NULL)
  291. continue;
  292. if (!player->IsAlive())
  293. continue;
  294. if (player->GetTeamNumber() != team)
  295. continue;
  296. trace_t result;
  297. UTIL_TraceLine( player->EyePosition(), spot, CONTENTS_SOLID, player, COLLISION_GROUP_NONE, &result );
  298. if (result.fraction == 1.0f)
  299. return true;
  300. }
  301. return false;
  302. }
  303. //------------------------------------------------------------------------------------------------------------
  304. void UTIL_DrawBeamFromEnt( int i, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue )
  305. {
  306. /* BOTPORT: What is the replacement for MESSAGE_BEGIN?
  307. MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecEnd ); // vecEnd = origin???
  308. WRITE_BYTE( TE_BEAMENTPOINT );
  309. WRITE_SHORT( i );
  310. WRITE_COORD( vecEnd.x );
  311. WRITE_COORD( vecEnd.y );
  312. WRITE_COORD( vecEnd.z );
  313. WRITE_SHORT( s_iBeamSprite );
  314. WRITE_BYTE( 0 ); // startframe
  315. WRITE_BYTE( 0 ); // framerate
  316. WRITE_BYTE( iLifetime ); // life
  317. WRITE_BYTE( 10 ); // width
  318. WRITE_BYTE( 0 ); // noise
  319. WRITE_BYTE( bRed ); // r, g, b
  320. WRITE_BYTE( bGreen ); // r, g, b
  321. WRITE_BYTE( bBlue ); // r, g, b
  322. WRITE_BYTE( 255 ); // brightness
  323. WRITE_BYTE( 0 ); // speed
  324. MESSAGE_END();
  325. */
  326. }
  327. //------------------------------------------------------------------------------------------------------------
  328. void UTIL_DrawBeamPoints( Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue )
  329. {
  330. NDebugOverlay::Line( vecStart, vecEnd, bRed, bGreen, bBlue, true, 0.1f );
  331. /*
  332. MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecStart );
  333. WRITE_BYTE( TE_BEAMPOINTS );
  334. WRITE_COORD( vecStart.x );
  335. WRITE_COORD( vecStart.y );
  336. WRITE_COORD( vecStart.z );
  337. WRITE_COORD( vecEnd.x );
  338. WRITE_COORD( vecEnd.y );
  339. WRITE_COORD( vecEnd.z );
  340. WRITE_SHORT( s_iBeamSprite );
  341. WRITE_BYTE( 0 ); // startframe
  342. WRITE_BYTE( 0 ); // framerate
  343. WRITE_BYTE( iLifetime ); // life
  344. WRITE_BYTE( 10 ); // width
  345. WRITE_BYTE( 0 ); // noise
  346. WRITE_BYTE( bRed ); // r, g, b
  347. WRITE_BYTE( bGreen ); // r, g, b
  348. WRITE_BYTE( bBlue ); // r, g, b
  349. WRITE_BYTE( 255 ); // brightness
  350. WRITE_BYTE( 0 ); // speed
  351. MESSAGE_END();
  352. */
  353. }
  354. //------------------------------------------------------------------------------------------------------------
  355. void CONSOLE_ECHO( const char * pszMsg, ... )
  356. {
  357. va_list argptr;
  358. static char szStr[1024];
  359. va_start( argptr, pszMsg );
  360. vsprintf( szStr, pszMsg, argptr );
  361. va_end( argptr );
  362. Msg( "%s", szStr );
  363. }
  364. //------------------------------------------------------------------------------------------------------------
  365. void BotPrecache( void )
  366. {
  367. s_iBeamSprite = CBaseEntity::PrecacheModel( "sprites/smoke.spr" );
  368. }
  369. //------------------------------------------------------------------------------------------------------------
  370. #define COS_TABLE_SIZE 256
  371. static float cosTable[ COS_TABLE_SIZE ];
  372. void InitBotTrig( void )
  373. {
  374. for( int i=0; i<COS_TABLE_SIZE; ++i )
  375. {
  376. float angle = (float)(2.0f * M_PI * i / (float)(COS_TABLE_SIZE-1));
  377. cosTable[i] = (float)cos( angle );
  378. }
  379. }
  380. float BotCOS( float angle )
  381. {
  382. angle = AngleNormalizePositive( angle );
  383. int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f );
  384. return cosTable[i];
  385. }
  386. float BotSIN( float angle )
  387. {
  388. angle = AngleNormalizePositive( angle - 90 );
  389. int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f );
  390. return cosTable[i];
  391. }
  392. //--------------------------------------------------------------------------------------------------------------
  393. /**
  394. * Send a "hint" message to all players, dead or alive.
  395. */
  396. void HintMessageToAllPlayers( const char *message )
  397. {
  398. hudtextparms_t textParms;
  399. textParms.x = -1.0f;
  400. textParms.y = -1.0f;
  401. textParms.fadeinTime = 1.0f;
  402. textParms.fadeoutTime = 5.0f;
  403. textParms.holdTime = 5.0f;
  404. textParms.fxTime = 0.0f;
  405. textParms.r1 = 100;
  406. textParms.g1 = 255;
  407. textParms.b1 = 100;
  408. textParms.r2 = 255;
  409. textParms.g2 = 255;
  410. textParms.b2 = 255;
  411. textParms.effect = 0;
  412. textParms.channel = 0;
  413. UTIL_HudMessageAll( textParms, message );
  414. }
  415. //--------------------------------------------------------------------------------------------------------------------
  416. /**
  417. * Return true if moving from "start" to "finish" will cross a player's line of fire.
  418. * The path from "start" to "finish" is assumed to be a straight line.
  419. * "start" and "finish" are assumed to be points on the ground.
  420. */
  421. bool IsCrossingLineOfFire( const Vector &start, const Vector &finish, CBaseEntity *ignore, int ignoreTeam )
  422. {
  423. for ( int p=1; p <= gpGlobals->maxClients; ++p )
  424. {
  425. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( p ) );
  426. if (!IsEntityValid( player ))
  427. continue;
  428. if (player == ignore)
  429. continue;
  430. if (!player->IsAlive())
  431. continue;
  432. if (ignoreTeam && player->GetTeamNumber() == ignoreTeam)
  433. continue;
  434. // compute player's unit aiming vector
  435. Vector viewForward;
  436. AngleVectors( player->EyeAngles() + player->GetPunchAngle(), &viewForward );
  437. const float longRange = 5000.0f;
  438. Vector playerOrigin = GetCentroid( player );
  439. Vector playerTarget = playerOrigin + longRange * viewForward;
  440. Vector result( 0, 0, 0 );
  441. if (IsIntersecting2D( start, finish, playerOrigin, playerTarget, &result ))
  442. {
  443. // simple check to see if intersection lies in the Z range of the path
  444. float loZ, hiZ;
  445. if (start.z < finish.z)
  446. {
  447. loZ = start.z;
  448. hiZ = finish.z;
  449. }
  450. else
  451. {
  452. loZ = finish.z;
  453. hiZ = start.z;
  454. }
  455. if (result.z >= loZ && result.z <= hiZ + HumanHeight)
  456. return true;
  457. }
  458. }
  459. return false;
  460. }
  461. //--------------------------------------------------------------------------------------------------------------
  462. /**
  463. * Performs a simple case-insensitive string comparison, honoring trailing * wildcards
  464. */
  465. bool WildcardMatch( const char *query, const char *test )
  466. {
  467. if ( !query || !test )
  468. return false;
  469. while ( *test && *query )
  470. {
  471. char nameChar = *test;
  472. char queryChar = *query;
  473. if ( tolower(nameChar) != tolower(queryChar) ) // case-insensitive
  474. break;
  475. ++test;
  476. ++query;
  477. }
  478. if ( *query == 0 && *test == 0 )
  479. return true;
  480. // Support trailing *
  481. if ( *query == '*' )
  482. return true;
  483. return false;
  484. }