Counter Strike : Global Offensive Source Code
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.

852 lines
20 KiB

  1. //========= Copyright � 1996-2005, 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. #pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning
  10. #define DEFINE_DIFFICULTY_NAMES
  11. #include "bot_profile.h"
  12. #include "shared_util.h"
  13. #include "bot.h"
  14. #include "bot_util.h"
  15. #include "cs_bot.h" // BOTPORT: Remove this CS dependency
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. BotProfileManager *TheBotProfiles = NULL;
  19. //--------------------------------------------------------------------------------------------------------
  20. /**
  21. * Generates a filename-decorated skin name
  22. */
  23. static const char * GetDecoratedSkinName( const char *name, const char *filename )
  24. {
  25. const int BufLen = _MAX_PATH + 64;
  26. static char buf[BufLen];
  27. Q_snprintf( buf, sizeof( buf ), "%s/%s", filename, name );
  28. return buf;
  29. }
  30. //--------------------------------------------------------------------------------------------------------------
  31. const char* BotProfile::GetWeaponPreferenceAsString( int i ) const
  32. {
  33. if ( i < 0 || i >= m_weaponPreferenceCount )
  34. return NULL;
  35. return WeaponIDToAlias( m_weaponPreference[ i ] );
  36. }
  37. //--------------------------------------------------------------------------------------------------------------
  38. /**
  39. * Return true if this profile has a primary weapon preference
  40. */
  41. bool BotProfile::HasPrimaryPreference() const
  42. {
  43. for ( int i=0; i < m_weaponPreferenceCount; ++i )
  44. {
  45. if ( IsPrimaryWeapon( m_weaponPreference[i] ) )
  46. return true;
  47. }
  48. return false;
  49. }
  50. //--------------------------------------------------------------------------------------------------------------
  51. /**
  52. * Return true if this profile has a pistol weapon preference
  53. */
  54. bool BotProfile::HasPistolPreference() const
  55. {
  56. for ( int i=0; i < m_weaponPreferenceCount; ++i )
  57. if ( IsSecondaryWeapon( m_weaponPreference[i] ) )
  58. return true;
  59. return false;
  60. }
  61. //--------------------------------------------------------------------------------------------------------------
  62. /**
  63. * Return true if this profile is valid for the specified team
  64. */
  65. bool BotProfile::IsValidForTeam( int team ) const
  66. {
  67. return ( team == TEAM_UNASSIGNED || m_teams == TEAM_UNASSIGNED || team == m_teams );
  68. }
  69. //--------------------------------------------------------------------------------------------------------------
  70. /**
  71. * Return true if this profile inherits from the specified template
  72. */
  73. bool BotProfile::InheritsFrom( const char *name ) const
  74. {
  75. if ( WildcardMatch( name, GetName() ) )
  76. return true;
  77. for ( int i=0; i<m_templates.Count(); ++i )
  78. {
  79. const BotProfile *queryTemplate = m_templates[i];
  80. if ( queryTemplate && queryTemplate->InheritsFrom( name ) )
  81. {
  82. return true;
  83. }
  84. }
  85. return false;
  86. }
  87. void BotProfile::SetName( const char* name )
  88. {
  89. if ( name )
  90. {
  91. if ( m_name )
  92. {
  93. // Delete the current name if it exists
  94. delete [] m_name;
  95. }
  96. m_name = CloneString( name );
  97. }
  98. }
  99. const BotProfile* BotProfile::GetTemplate( int index ) const
  100. {
  101. if ( index > 0 && index < m_templates.Count() )
  102. {
  103. return m_templates[index];
  104. }
  105. return NULL;
  106. }
  107. void BotProfile::Clone( const BotProfile* source_profile )
  108. {
  109. if ( source_profile )
  110. {
  111. SetName( source_profile->GetName() );
  112. for ( int device = 0; device < BotProfileInputDevice::COUNT; ++device )
  113. {
  114. m_aggression = source_profile->GetAggression();
  115. m_skill = source_profile->GetSkill();
  116. m_teamwork = source_profile->GetTeamwork();
  117. m_aimFocusInitial = source_profile->GetAimFocusInitial();
  118. m_aimFocusDecay = source_profile->GetAimFocusDecay();
  119. m_aimFocusOffsetScale = source_profile->GetAimFocusOffsetScale();
  120. m_aimFocusInterval = source_profile->GetAimFocusInterval();
  121. m_weaponPreferenceCount = source_profile->GetWeaponPreferenceCount();
  122. for( int i = 0; i < source_profile->GetWeaponPreferenceCount(); ++i )
  123. {
  124. m_weaponPreference[i] = source_profile->GetWeaponPreference(i);
  125. }
  126. m_cost = source_profile->GetCost();
  127. m_skin = source_profile->GetSkin();
  128. m_difficultyFlags = source_profile->GetDifficultyFlags();
  129. m_voicePitch = source_profile->GetVoicePitch();
  130. m_reactionTime = source_profile->GetReactionTime();
  131. m_attackDelay = source_profile->GetAttackDelay();
  132. m_lookAngleMaxAccelNormal = source_profile->GetLookAngleMaxAccelerationNormal();
  133. m_lookAngleStiffnessNormal = source_profile->GetLookAngleStiffnessNormal();
  134. m_lookAngleDampingNormal = source_profile->GetLookAngleDampingNormal();
  135. m_lookAngleMaxAccelAttacking = source_profile->GetLookAngleMaxAccelerationAttacking();
  136. m_lookAngleStiffnessAttacking = source_profile->GetLookAngleStiffnessAttacking();
  137. m_lookAngleDampingAttacking = source_profile->GetLookAngleDampingAttacking();
  138. }
  139. m_teams = source_profile->GetTeams();
  140. m_voiceBank = source_profile->GetVoiceBank();
  141. m_prefersSilencer = source_profile->PrefersSilencer();
  142. for ( int i = 0; i < source_profile->GetTemplatesCount(); ++i )
  143. {
  144. m_templates.AddToTail( source_profile->GetTemplate( i ) );
  145. }
  146. }
  147. }
  148. //--------------------------------------------------------------------------------------------------------------
  149. /**
  150. * Constructor
  151. */
  152. BotProfileManager::BotProfileManager( void )
  153. {
  154. m_nextSkin = 0;
  155. for (int i=0; i<NumCustomSkins; ++i)
  156. {
  157. m_skins[i] = NULL;
  158. m_skinFilenames[i] = NULL;
  159. m_skinModelnames[i] = NULL;
  160. }
  161. }
  162. //--------------------------------------------------------------------------------------------------------------
  163. /**
  164. * Load the bot profile database
  165. */
  166. void BotProfileManager::Init( const char *filename, unsigned int *checksum )
  167. {
  168. FileHandle_t file = filesystem->Open( filename, "r" );
  169. if (!file)
  170. {
  171. if ( true ) // UTIL_IsGame( "czero" ) )
  172. {
  173. CONSOLE_ECHO( "WARNING: Cannot access bot profile database '%s'\n", filename );
  174. }
  175. return;
  176. }
  177. int dataLength = filesystem->Size( filename );
  178. char *dataPointer = new char[ dataLength ];
  179. int dataReadLength = filesystem->Read( dataPointer, dataLength, file );
  180. filesystem->Close( file );
  181. if ( dataReadLength > 0 )
  182. {
  183. // NULL-terminate based on the length read in, since Read() can transform \r\n to \n and
  184. // return fewer bytes than we were expecting.
  185. dataPointer[ dataReadLength - 1 ] = 0;
  186. }
  187. const char *dataFile = dataPointer;
  188. // compute simple checksum
  189. if (checksum)
  190. {
  191. *checksum = 0; // ComputeSimpleChecksum( (const unsigned char *)dataPointer, dataLength );
  192. }
  193. BotProfile defaultProfile;
  194. //
  195. // Parse the BotProfile.db into BotProfile instances
  196. //
  197. while( true )
  198. {
  199. dataFile = SharedParse( dataFile );
  200. if (!dataFile)
  201. break;
  202. char *token = SharedGetToken();
  203. bool isDefault = ( !stricmp( token, "Default" ));
  204. bool isTemplate = ( !stricmp( token, "Template" ));
  205. bool isCustomSkin = ( !stricmp( token, "Skin" ));
  206. if ( isCustomSkin )
  207. {
  208. const int BufLen = 64;
  209. char skinName[BufLen];
  210. // get skin name
  211. dataFile = SharedParse( dataFile );
  212. if (!dataFile)
  213. {
  214. CONSOLE_ECHO( "Error parsing %s - expected skin name\n", filename );
  215. delete [] dataPointer;
  216. return;
  217. }
  218. token = SharedGetToken();
  219. Q_snprintf( skinName, sizeof( skinName ), "%s", token );
  220. // get attribute name
  221. dataFile = SharedParse( dataFile );
  222. if (!dataFile)
  223. {
  224. CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename );
  225. delete [] dataPointer;
  226. return;
  227. }
  228. token = SharedGetToken();
  229. if (stricmp( "Model", token ))
  230. {
  231. CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename );
  232. delete [] dataPointer;
  233. return;
  234. }
  235. // eat '='
  236. dataFile = SharedParse( dataFile );
  237. if (!dataFile)
  238. {
  239. CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
  240. delete [] dataPointer;
  241. return;
  242. }
  243. token = SharedGetToken();
  244. if (strcmp( "=", token ))
  245. {
  246. CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
  247. delete [] dataPointer;
  248. return;
  249. }
  250. // get attribute value
  251. dataFile = SharedParse( dataFile );
  252. if (!dataFile)
  253. {
  254. CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename );
  255. delete [] dataPointer;
  256. return;
  257. }
  258. token = SharedGetToken();
  259. const char *decoratedName = GetDecoratedSkinName( skinName, filename );
  260. bool skinExists = GetCustomSkinIndex( decoratedName ) > 0;
  261. if ( m_nextSkin < NumCustomSkins && !skinExists )
  262. {
  263. // decorate the name
  264. m_skins[ m_nextSkin ] = CloneString( decoratedName );
  265. // construct the model filename
  266. m_skinModelnames[ m_nextSkin ] = CloneString( token );
  267. m_skinFilenames[ m_nextSkin ] = new char[ strlen( token )*2 + strlen("models/player//.mdl") + 1 ];
  268. Q_snprintf( m_skinFilenames[ m_nextSkin ], sizeof( m_skinFilenames[ m_nextSkin ] ), "models/player/%s/%s.mdl", token, token );
  269. ++m_nextSkin;
  270. }
  271. // eat 'End'
  272. dataFile = SharedParse( dataFile );
  273. if (!dataFile)
  274. {
  275. CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
  276. delete [] dataPointer;
  277. return;
  278. }
  279. token = SharedGetToken();
  280. if (strcmp( "End", token ))
  281. {
  282. CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
  283. delete [] dataPointer;
  284. return;
  285. }
  286. continue; // it's just a custom skin - no need to do inheritance on a bot profile, etc.
  287. }
  288. // encountered a new profile
  289. BotProfile *profile;
  290. if (isDefault)
  291. {
  292. profile = &defaultProfile;
  293. }
  294. else
  295. {
  296. profile = new BotProfile;
  297. // always inherit from Default
  298. *profile = defaultProfile;
  299. }
  300. // do inheritance in order of appearance
  301. if (!isTemplate && !isDefault)
  302. {
  303. const BotProfile *inherit = NULL;
  304. // template names are separated by "+"
  305. while(true)
  306. {
  307. char *c = strchr( token, '+' );
  308. if (c)
  309. *c = '\000';
  310. // find the given template name
  311. FOR_EACH_LL( m_templateList, it )
  312. {
  313. BotProfile *profile = m_templateList[ it ];
  314. if ( !stricmp( profile->GetName(), token ))
  315. {
  316. inherit = profile;
  317. break;
  318. }
  319. }
  320. if (inherit == NULL)
  321. {
  322. CONSOLE_ECHO( "Error parsing '%s' - invalid template reference '%s'\n", filename, token );
  323. delete [] dataPointer;
  324. return;
  325. }
  326. // inherit the data
  327. profile->Inherit( inherit, &defaultProfile );
  328. if (c == NULL)
  329. break;
  330. token = c+1;
  331. }
  332. }
  333. // get name of this profile
  334. if (!isDefault)
  335. {
  336. dataFile = SharedParse( dataFile );
  337. if (!dataFile)
  338. {
  339. CONSOLE_ECHO( "Error parsing '%s' - expected name\n", filename );
  340. delete [] dataPointer;
  341. return;
  342. }
  343. profile->m_name = CloneString( SharedGetToken() );
  344. /**
  345. * HACK HACK
  346. * Until we have a generalized means of storing bot preferences, we're going to hardcode the bot's
  347. * preference towards silencers based on his name.
  348. */
  349. if ( profile->m_name[0] % 2 )
  350. {
  351. profile->m_prefersSilencer = true;
  352. }
  353. }
  354. // read attributes for this profile
  355. bool isFirstWeaponPref = true;
  356. while( true )
  357. {
  358. // get next token
  359. dataFile = SharedParse( dataFile );
  360. if (!dataFile)
  361. {
  362. CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
  363. delete [] dataPointer;
  364. return;
  365. }
  366. token = SharedGetToken();
  367. // check for End delimiter
  368. if ( !stricmp( token, "End" ))
  369. break;
  370. // found attribute name - keep it
  371. char attributeName[64];
  372. strcpy( attributeName, token );
  373. // eat '='
  374. dataFile = SharedParse( dataFile );
  375. if (!dataFile)
  376. {
  377. CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
  378. delete [] dataPointer;
  379. return;
  380. }
  381. token = SharedGetToken();
  382. if (strcmp( "=", token ))
  383. {
  384. CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
  385. delete [] dataPointer;
  386. return;
  387. }
  388. // get attribute value
  389. dataFile = SharedParse( dataFile );
  390. if (!dataFile)
  391. {
  392. CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename );
  393. delete [] dataPointer;
  394. return;
  395. }
  396. token = SharedGetToken();
  397. // store value in appropriate attribute
  398. if ( !stricmp( "Aggression", attributeName ) )
  399. {
  400. profile->m_aggression = (float)atof( token ) / 100.0f;
  401. }
  402. else if ( !stricmp( "Skill", attributeName ) )
  403. {
  404. profile->m_skill = (float)atof( token ) / 100.0f;
  405. }
  406. else if ( !stricmp( "Skin", attributeName ) )
  407. {
  408. profile->m_skin = atoi( token );
  409. if ( profile->m_skin == 0 )
  410. {
  411. // atoi() failed - try to look up a custom skin by name
  412. profile->m_skin = GetCustomSkinIndex( token, filename );
  413. }
  414. }
  415. else if ( !stricmp( "Teamwork", attributeName ))
  416. {
  417. profile->m_teamwork = (float)atof( token ) / 100.0f;
  418. }
  419. else if ( !V_stricmp( "AimFocusInitial", attributeName ) )
  420. {
  421. profile->m_aimFocusInitial = (float)atof( token );
  422. }
  423. else if ( !V_stricmp( "AimFocusDecay", attributeName ) )
  424. {
  425. profile->m_aimFocusDecay = (float)atof( token );
  426. }
  427. else if ( !V_stricmp( "AimFocusOffsetScale", attributeName ) )
  428. {
  429. profile->m_aimFocusOffsetScale = (float)atof( token );
  430. }
  431. else if ( !V_stricmp( "AimFocusInterval", attributeName ) )
  432. {
  433. profile->m_aimFocusInterval = (float)atof( token );
  434. }
  435. else if ( !stricmp( "Cost", attributeName ) )
  436. {
  437. profile->m_cost = atoi( token );
  438. }
  439. else if ( !stricmp( "VoicePitch", attributeName ) )
  440. {
  441. profile->m_voicePitch = atoi( token );
  442. }
  443. else if ( !stricmp( "VoiceBank", attributeName ) )
  444. {
  445. profile->m_voiceBank = FindVoiceBankIndex( token );
  446. }
  447. else if ( !stricmp( "WeaponPreference", attributeName ) )
  448. {
  449. ParseWeaponPreference( isFirstWeaponPref, profile->m_weaponPreferenceCount, profile->m_weaponPreference, token );
  450. }
  451. else if ( !stricmp( "ReactionTime", attributeName ) )
  452. {
  453. profile->m_reactionTime = (float)atof( token );
  454. }
  455. else if ( !stricmp( "AttackDelay", attributeName ))
  456. {
  457. profile->m_attackDelay = (float)atof( token );
  458. }
  459. else if ( !stricmp( "Difficulty", attributeName ))
  460. {
  461. // override inheritance
  462. ParseDifficultySetting( profile->m_difficultyFlags, token );
  463. }
  464. else if ( !stricmp( "Team", attributeName ))
  465. {
  466. if ( !stricmp( token, "T" ) )
  467. {
  468. profile->m_teams = TEAM_TERRORIST;
  469. }
  470. else if ( !stricmp( token, "CT" ) )
  471. {
  472. profile->m_teams = TEAM_CT;
  473. }
  474. else
  475. {
  476. profile->m_teams = TEAM_UNASSIGNED;
  477. }
  478. }
  479. else if ( !stricmp( "LookAngleMaxAccelNormal", attributeName ) )
  480. {
  481. profile->m_lookAngleMaxAccelNormal = atof( token );
  482. }
  483. else if ( !stricmp( "LookAngleStiffnessNormal", attributeName ) )
  484. {
  485. profile->m_lookAngleStiffnessNormal = atof( token );
  486. }
  487. else if ( !stricmp( "LookAngleDampingNormal", attributeName ) )
  488. {
  489. profile->m_lookAngleDampingNormal = atof( token );
  490. }
  491. else if ( !stricmp( "LookAngleMaxAccelAttacking", attributeName ) )
  492. {
  493. profile->m_lookAngleMaxAccelAttacking = atof( token );
  494. }
  495. else if ( !stricmp( "LookAngleStiffnessAttacking", attributeName ) )
  496. {
  497. profile->m_lookAngleStiffnessAttacking = atof( token );
  498. }
  499. else if ( !stricmp( "LookAngleDampingAttacking", attributeName ) )
  500. {
  501. profile->m_lookAngleDampingAttacking = atof( token );
  502. }
  503. else
  504. {
  505. CONSOLE_ECHO( "Error parsing %s - unknown attribute '%s'\n", filename, attributeName );
  506. }
  507. }
  508. if (!isDefault)
  509. {
  510. if (isTemplate)
  511. {
  512. // add to template list
  513. m_templateList.AddToTail( profile );
  514. }
  515. else
  516. {
  517. // add profile to the master list
  518. m_profileList.AddToTail( profile );
  519. }
  520. }
  521. }
  522. delete [] dataPointer;
  523. }
  524. void BotProfileManager::ParseDifficultySetting( unsigned char &difficultyFlags, char* token)
  525. {
  526. // override inheritance
  527. difficultyFlags = 0;
  528. // parse bit flags
  529. while(true)
  530. {
  531. char *c = strchr( token, '+' );
  532. if (c)
  533. {
  534. *c = '\000';
  535. }
  536. for( int i=0; i<NUM_DIFFICULTY_LEVELS; ++i )
  537. {
  538. if ( !stricmp( BotDifficultyName[i], token ))
  539. {
  540. difficultyFlags |= (1 << i);
  541. }
  542. }
  543. if (c == NULL)
  544. {
  545. break;
  546. }
  547. token = c+1;
  548. }
  549. }
  550. void BotProfileManager::ParseWeaponPreference( bool &isFirstWeaponPref, int &weaponPreferenceCount, CSWeaponID* weaponPreference, char* token )
  551. {
  552. // weapon preferences override parent prefs
  553. if (isFirstWeaponPref)
  554. {
  555. isFirstWeaponPref = false;
  556. weaponPreferenceCount = 0;
  557. }
  558. if ( !stricmp( token, "none" ))
  559. {
  560. weaponPreferenceCount = 0;
  561. }
  562. else
  563. {
  564. if (weaponPreferenceCount < BotProfile::MAX_WEAPON_PREFS)
  565. {
  566. weaponPreference[ weaponPreferenceCount++ ] = AliasToWeaponID( token );
  567. }
  568. }
  569. }
  570. //--------------------------------------------------------------------------------------------------------------
  571. BotProfileManager::~BotProfileManager( void )
  572. {
  573. Reset();
  574. }
  575. //--------------------------------------------------------------------------------------------------------------
  576. /**
  577. * Free all bot profiles
  578. */
  579. void BotProfileManager::Reset( void )
  580. {
  581. m_profileList.PurgeAndDeleteElements();
  582. m_templateList.PurgeAndDeleteElements();
  583. int i;
  584. for (i=0; i<NumCustomSkins; ++i)
  585. {
  586. if ( m_skins[i] )
  587. {
  588. delete[] m_skins[i];
  589. m_skins[i] = NULL;
  590. }
  591. if ( m_skinFilenames[i] )
  592. {
  593. delete[] m_skinFilenames[i];
  594. m_skinFilenames[i] = NULL;
  595. }
  596. if ( m_skinModelnames[i] )
  597. {
  598. delete[] m_skinModelnames[i];
  599. m_skinModelnames[i] = NULL;
  600. }
  601. }
  602. for ( i=0; i<m_voiceBanks.Count(); ++i )
  603. {
  604. delete[] m_voiceBanks[i];
  605. }
  606. m_voiceBanks.RemoveAll();
  607. }
  608. //--------------------------------------------------------------------------------------------------------
  609. /**
  610. * Returns custom skin name at a particular index
  611. */
  612. const char * BotProfileManager::GetCustomSkin( int index )
  613. {
  614. if ( index < FirstCustomSkin || index > LastCustomSkin )
  615. {
  616. return NULL;
  617. }
  618. return m_skins[ index - FirstCustomSkin ];
  619. }
  620. //--------------------------------------------------------------------------------------------------------
  621. /**
  622. * Returns custom skin filename at a particular index
  623. */
  624. const char * BotProfileManager::GetCustomSkinFname( int index )
  625. {
  626. if ( index < FirstCustomSkin || index > LastCustomSkin )
  627. {
  628. return NULL;
  629. }
  630. return m_skinFilenames[ index - FirstCustomSkin ];
  631. }
  632. //--------------------------------------------------------------------------------------------------------
  633. /**
  634. * Returns custom skin modelname at a particular index
  635. */
  636. const char * BotProfileManager::GetCustomSkinModelname( int index )
  637. {
  638. if ( index < FirstCustomSkin || index > LastCustomSkin )
  639. {
  640. return NULL;
  641. }
  642. return m_skinModelnames[ index - FirstCustomSkin ];
  643. }
  644. //--------------------------------------------------------------------------------------------------------
  645. /**
  646. * Looks up a custom skin index by filename-decorated name (will decorate the name if filename is given)
  647. */
  648. int BotProfileManager::GetCustomSkinIndex( const char *name, const char *filename )
  649. {
  650. const char * skinName = name;
  651. if ( filename )
  652. {
  653. skinName = GetDecoratedSkinName( name, filename );
  654. }
  655. for (int i=0; i<NumCustomSkins; ++i)
  656. {
  657. if ( m_skins[i] )
  658. {
  659. if ( !stricmp( skinName, m_skins[i] ) )
  660. {
  661. return FirstCustomSkin + i;
  662. }
  663. }
  664. }
  665. return 0;
  666. }
  667. //--------------------------------------------------------------------------------------------------------
  668. /**
  669. * return index of the (custom) bot phrase db, inserting it if needed
  670. */
  671. int BotProfileManager::FindVoiceBankIndex( const char *filename )
  672. {
  673. int index = 0;
  674. for ( int i=0; i<m_voiceBanks.Count(); ++i )
  675. {
  676. if ( !stricmp( filename, m_voiceBanks[i] ) )
  677. {
  678. return index;
  679. }
  680. }
  681. m_voiceBanks.AddToTail( CloneString( filename ) );
  682. return index;
  683. }
  684. //--------------------------------------------------------------------------------------------------------------
  685. /**
  686. * Return random unused profile that matches the given difficulty level
  687. */
  688. const BotProfile *BotProfileManager::GetRandomProfile( BotDifficultyType difficulty, int team, CSWeaponType weaponType, bool forceMatchHighestDifficulty ) const
  689. {
  690. // count up valid profiles
  691. CUtlVector< const BotProfile * > profiles;
  692. FOR_EACH_LL( m_profileList, it )
  693. {
  694. const BotProfile *profile = m_profileList[ it ];
  695. // Match difficulty
  696. if ( forceMatchHighestDifficulty )
  697. {
  698. if ( !profile->IsMaxDifficulty( difficulty ) )
  699. {
  700. continue;
  701. }
  702. }
  703. else
  704. {
  705. if ( !profile->IsDifficulty( difficulty ) )
  706. continue;
  707. }
  708. // Prevent duplicate names
  709. if ( UTIL_IsNameTaken( profile->GetName() ) )
  710. continue;
  711. // Match team choice
  712. if ( !profile->IsValidForTeam( team ) )
  713. continue;
  714. // Match desired weapon
  715. if ( weaponType != WEAPONTYPE_UNKNOWN )
  716. {
  717. if ( !profile->GetWeaponPreferenceCount() )
  718. continue;
  719. if ( weaponType != WeaponClassFromWeaponID( (CSWeaponID)profile->GetWeaponPreference( 0 ) ) )
  720. continue;
  721. }
  722. profiles.AddToTail( profile );
  723. }
  724. if ( !profiles.Count() )
  725. return NULL;
  726. // select one at random
  727. int which = RandomInt( 0, profiles.Count()-1 );
  728. return profiles[which];
  729. }