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.

704 lines
17 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. #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( void ) 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( void ) 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->InheritsFrom( name ) )
  81. {
  82. return true;
  83. }
  84. }
  85. return false;
  86. }
  87. //--------------------------------------------------------------------------------------------------------------
  88. /**
  89. * Constructor
  90. */
  91. BotProfileManager::BotProfileManager( void )
  92. {
  93. m_nextSkin = 0;
  94. for (int i=0; i<NumCustomSkins; ++i)
  95. {
  96. m_skins[i] = NULL;
  97. m_skinFilenames[i] = NULL;
  98. m_skinModelnames[i] = NULL;
  99. }
  100. }
  101. //--------------------------------------------------------------------------------------------------------------
  102. /**
  103. * Load the bot profile database
  104. */
  105. void BotProfileManager::Init( const char *filename, unsigned int *checksum )
  106. {
  107. FileHandle_t file = filesystem->Open( filename, "r" );
  108. if (!file)
  109. {
  110. if ( true ) // UTIL_IsGame( "czero" ) )
  111. {
  112. CONSOLE_ECHO( "WARNING: Cannot access bot profile database '%s'\n", filename );
  113. }
  114. return;
  115. }
  116. int dataLength = filesystem->Size( filename );
  117. char *dataPointer = new char[ dataLength ];
  118. int dataReadLength = filesystem->Read( dataPointer, dataLength, file );
  119. filesystem->Close( file );
  120. if ( dataReadLength > 0 )
  121. {
  122. // NULL-terminate based on the length read in, since Read() can transform \r\n to \n and
  123. // return fewer bytes than we were expecting.
  124. dataPointer[ dataReadLength - 1 ] = 0;
  125. }
  126. const char *dataFile = dataPointer;
  127. // compute simple checksum
  128. if (checksum)
  129. {
  130. *checksum = 0; // ComputeSimpleChecksum( (const unsigned char *)dataPointer, dataLength );
  131. }
  132. BotProfile defaultProfile;
  133. //
  134. // Parse the BotProfile.db into BotProfile instances
  135. //
  136. while( true )
  137. {
  138. dataFile = SharedParse( dataFile );
  139. if (!dataFile)
  140. break;
  141. char *token = SharedGetToken();
  142. bool isDefault = (!stricmp( token, "Default" ));
  143. bool isTemplate = (!stricmp( token, "Template" ));
  144. bool isCustomSkin = (!stricmp( token, "Skin" ));
  145. if ( isCustomSkin )
  146. {
  147. const int BufLen = 64;
  148. char skinName[BufLen];
  149. // get skin name
  150. dataFile = SharedParse( dataFile );
  151. if (!dataFile)
  152. {
  153. CONSOLE_ECHO( "Error parsing %s - expected skin name\n", filename );
  154. delete [] dataPointer;
  155. return;
  156. }
  157. token = SharedGetToken();
  158. Q_snprintf( skinName, sizeof( skinName ), "%s", token );
  159. // get attribute name
  160. dataFile = SharedParse( dataFile );
  161. if (!dataFile)
  162. {
  163. CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename );
  164. delete [] dataPointer;
  165. return;
  166. }
  167. token = SharedGetToken();
  168. if (stricmp( "Model", token ))
  169. {
  170. CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename );
  171. delete [] dataPointer;
  172. return;
  173. }
  174. // eat '='
  175. dataFile = SharedParse( dataFile );
  176. if (!dataFile)
  177. {
  178. CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
  179. delete [] dataPointer;
  180. return;
  181. }
  182. token = SharedGetToken();
  183. if (strcmp( "=", token ))
  184. {
  185. CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
  186. delete [] dataPointer;
  187. return;
  188. }
  189. // get attribute value
  190. dataFile = SharedParse( dataFile );
  191. if (!dataFile)
  192. {
  193. CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename );
  194. delete [] dataPointer;
  195. return;
  196. }
  197. token = SharedGetToken();
  198. const char *decoratedName = GetDecoratedSkinName( skinName, filename );
  199. bool skinExists = GetCustomSkinIndex( decoratedName ) > 0;
  200. if ( m_nextSkin < NumCustomSkins && !skinExists )
  201. {
  202. // decorate the name
  203. m_skins[ m_nextSkin ] = CloneString( decoratedName );
  204. // construct the model filename
  205. m_skinModelnames[ m_nextSkin ] = CloneString( token );
  206. m_skinFilenames[ m_nextSkin ] = new char[ strlen(token)*2 + strlen("models/player//.mdl") + 1 ];
  207. Q_snprintf( m_skinFilenames[ m_nextSkin ], sizeof( m_skinFilenames[ m_nextSkin ] ), "models/player/%s/%s.mdl", token, token );
  208. ++m_nextSkin;
  209. }
  210. // eat 'End'
  211. dataFile = SharedParse( dataFile );
  212. if (!dataFile)
  213. {
  214. CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
  215. delete [] dataPointer;
  216. return;
  217. }
  218. token = SharedGetToken();
  219. if (strcmp( "End", token ))
  220. {
  221. CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
  222. delete [] dataPointer;
  223. return;
  224. }
  225. continue; // it's just a custom skin - no need to do inheritance on a bot profile, etc.
  226. }
  227. // encountered a new profile
  228. BotProfile *profile;
  229. if (isDefault)
  230. {
  231. profile = &defaultProfile;
  232. }
  233. else
  234. {
  235. profile = new BotProfile;
  236. // always inherit from Default
  237. *profile = defaultProfile;
  238. }
  239. // do inheritance in order of appearance
  240. if (!isTemplate && !isDefault)
  241. {
  242. const BotProfile *inherit = NULL;
  243. // template names are separated by "+"
  244. while(true)
  245. {
  246. char *c = strchr( token, '+' );
  247. if (c)
  248. *c = '\000';
  249. // find the given template name
  250. FOR_EACH_LL( m_templateList, it )
  251. {
  252. BotProfile *profile = m_templateList[ it ];
  253. if (!stricmp( profile->GetName(), token ))
  254. {
  255. inherit = profile;
  256. break;
  257. }
  258. }
  259. if (inherit == NULL)
  260. {
  261. CONSOLE_ECHO( "Error parsing '%s' - invalid template reference '%s'\n", filename, token );
  262. delete [] dataPointer;
  263. return;
  264. }
  265. // inherit the data
  266. profile->Inherit( inherit, &defaultProfile );
  267. if (c == NULL)
  268. break;
  269. token = c+1;
  270. }
  271. }
  272. // get name of this profile
  273. if (!isDefault)
  274. {
  275. dataFile = SharedParse( dataFile );
  276. if (!dataFile)
  277. {
  278. CONSOLE_ECHO( "Error parsing '%s' - expected name\n", filename );
  279. delete [] dataPointer;
  280. return;
  281. }
  282. profile->m_name = CloneString( SharedGetToken() );
  283. /**
  284. * HACK HACK
  285. * Until we have a generalized means of storing bot preferences, we're going to hardcode the bot's
  286. * preference towards silencers based on his name.
  287. */
  288. if ( profile->m_name[0] % 2 )
  289. {
  290. profile->m_prefersSilencer = true;
  291. }
  292. }
  293. // read attributes for this profile
  294. bool isFirstWeaponPref = true;
  295. while( true )
  296. {
  297. // get next token
  298. dataFile = SharedParse( dataFile );
  299. if (!dataFile)
  300. {
  301. CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
  302. delete [] dataPointer;
  303. return;
  304. }
  305. token = SharedGetToken();
  306. // check for End delimiter
  307. if (!stricmp( token, "End" ))
  308. break;
  309. // found attribute name - keep it
  310. char attributeName[64];
  311. strcpy( attributeName, token );
  312. // eat '='
  313. dataFile = SharedParse( dataFile );
  314. if (!dataFile)
  315. {
  316. CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
  317. delete [] dataPointer;
  318. return;
  319. }
  320. token = SharedGetToken();
  321. if (strcmp( "=", token ))
  322. {
  323. CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
  324. delete [] dataPointer;
  325. return;
  326. }
  327. // get attribute value
  328. dataFile = SharedParse( dataFile );
  329. if (!dataFile)
  330. {
  331. CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename );
  332. delete [] dataPointer;
  333. return;
  334. }
  335. token = SharedGetToken();
  336. // store value in appropriate attribute
  337. if (!stricmp( "Aggression", attributeName ))
  338. {
  339. profile->m_aggression = (float)atof(token) / 100.0f;
  340. }
  341. else if (!stricmp( "Skill", attributeName ))
  342. {
  343. profile->m_skill = (float)atof(token) / 100.0f;
  344. }
  345. else if (!stricmp( "Skin", attributeName ))
  346. {
  347. profile->m_skin = atoi(token);
  348. if ( profile->m_skin == 0 )
  349. {
  350. // atoi() failed - try to look up a custom skin by name
  351. profile->m_skin = GetCustomSkinIndex( token, filename );
  352. }
  353. }
  354. else if (!stricmp( "Teamwork", attributeName ))
  355. {
  356. profile->m_teamwork = (float)atof(token) / 100.0f;
  357. }
  358. else if (!stricmp( "Cost", attributeName ))
  359. {
  360. profile->m_cost = atoi(token);
  361. }
  362. else if (!stricmp( "VoicePitch", attributeName ))
  363. {
  364. profile->m_voicePitch = atoi(token);
  365. }
  366. else if (!stricmp( "VoiceBank", attributeName ))
  367. {
  368. profile->m_voiceBank = FindVoiceBankIndex( token );
  369. }
  370. else if (!stricmp( "WeaponPreference", attributeName ))
  371. {
  372. // weapon preferences override parent prefs
  373. if (isFirstWeaponPref)
  374. {
  375. isFirstWeaponPref = false;
  376. profile->m_weaponPreferenceCount = 0;
  377. }
  378. if (!stricmp( token, "none" ))
  379. {
  380. profile->m_weaponPreferenceCount = 0;
  381. }
  382. else
  383. {
  384. if (profile->m_weaponPreferenceCount < BotProfile::MAX_WEAPON_PREFS)
  385. {
  386. profile->m_weaponPreference[ profile->m_weaponPreferenceCount++ ] = AliasToWeaponID( token );
  387. }
  388. }
  389. }
  390. else if (!stricmp( "ReactionTime", attributeName ))
  391. {
  392. profile->m_reactionTime = (float)atof(token);
  393. #ifndef GAMEUI_EXPORTS
  394. // subtract off latency due to "think" update rate.
  395. // In GameUI, we don't really care.
  396. //profile->m_reactionTime -= g_BotUpdateInterval;
  397. #endif
  398. }
  399. else if (!stricmp( "AttackDelay", attributeName ))
  400. {
  401. profile->m_attackDelay = (float)atof(token);
  402. }
  403. else if (!stricmp( "Difficulty", attributeName ))
  404. {
  405. // override inheritance
  406. profile->m_difficultyFlags = 0;
  407. // parse bit flags
  408. while(true)
  409. {
  410. char *c = strchr( token, '+' );
  411. if (c)
  412. *c = '\000';
  413. for( int i=0; i<NUM_DIFFICULTY_LEVELS; ++i )
  414. if (!stricmp( BotDifficultyName[i], token ))
  415. profile->m_difficultyFlags |= (1 << i);
  416. if (c == NULL)
  417. break;
  418. token = c+1;
  419. }
  420. }
  421. else if (!stricmp( "Team", attributeName ))
  422. {
  423. if ( !stricmp( token, "T" ) )
  424. {
  425. profile->m_teams = TEAM_TERRORIST;
  426. }
  427. else if ( !stricmp( token, "CT" ) )
  428. {
  429. profile->m_teams = TEAM_CT;
  430. }
  431. else
  432. {
  433. profile->m_teams = TEAM_UNASSIGNED;
  434. }
  435. }
  436. else
  437. {
  438. CONSOLE_ECHO( "Error parsing %s - unknown attribute '%s'\n", filename, attributeName );
  439. }
  440. }
  441. if (!isDefault)
  442. {
  443. if (isTemplate)
  444. {
  445. // add to template list
  446. m_templateList.AddToTail( profile );
  447. }
  448. else
  449. {
  450. // add profile to the master list
  451. m_profileList.AddToTail( profile );
  452. }
  453. }
  454. }
  455. delete [] dataPointer;
  456. }
  457. //--------------------------------------------------------------------------------------------------------------
  458. BotProfileManager::~BotProfileManager( void )
  459. {
  460. Reset();
  461. }
  462. //--------------------------------------------------------------------------------------------------------------
  463. /**
  464. * Free all bot profiles
  465. */
  466. void BotProfileManager::Reset( void )
  467. {
  468. m_profileList.PurgeAndDeleteElements();
  469. m_templateList.PurgeAndDeleteElements();
  470. int i;
  471. for (i=0; i<NumCustomSkins; ++i)
  472. {
  473. if ( m_skins[i] )
  474. {
  475. delete[] m_skins[i];
  476. m_skins[i] = NULL;
  477. }
  478. if ( m_skinFilenames[i] )
  479. {
  480. delete[] m_skinFilenames[i];
  481. m_skinFilenames[i] = NULL;
  482. }
  483. if ( m_skinModelnames[i] )
  484. {
  485. delete[] m_skinModelnames[i];
  486. m_skinModelnames[i] = NULL;
  487. }
  488. }
  489. for ( i=0; i<m_voiceBanks.Count(); ++i )
  490. {
  491. delete[] m_voiceBanks[i];
  492. }
  493. m_voiceBanks.RemoveAll();
  494. }
  495. //--------------------------------------------------------------------------------------------------------
  496. /**
  497. * Returns custom skin name at a particular index
  498. */
  499. const char * BotProfileManager::GetCustomSkin( int index )
  500. {
  501. if ( index < FirstCustomSkin || index > LastCustomSkin )
  502. {
  503. return NULL;
  504. }
  505. return m_skins[ index - FirstCustomSkin ];
  506. }
  507. //--------------------------------------------------------------------------------------------------------
  508. /**
  509. * Returns custom skin filename at a particular index
  510. */
  511. const char * BotProfileManager::GetCustomSkinFname( int index )
  512. {
  513. if ( index < FirstCustomSkin || index > LastCustomSkin )
  514. {
  515. return NULL;
  516. }
  517. return m_skinFilenames[ index - FirstCustomSkin ];
  518. }
  519. //--------------------------------------------------------------------------------------------------------
  520. /**
  521. * Returns custom skin modelname at a particular index
  522. */
  523. const char * BotProfileManager::GetCustomSkinModelname( int index )
  524. {
  525. if ( index < FirstCustomSkin || index > LastCustomSkin )
  526. {
  527. return NULL;
  528. }
  529. return m_skinModelnames[ index - FirstCustomSkin ];
  530. }
  531. //--------------------------------------------------------------------------------------------------------
  532. /**
  533. * Looks up a custom skin index by filename-decorated name (will decorate the name if filename is given)
  534. */
  535. int BotProfileManager::GetCustomSkinIndex( const char *name, const char *filename )
  536. {
  537. const char * skinName = name;
  538. if ( filename )
  539. {
  540. skinName = GetDecoratedSkinName( name, filename );
  541. }
  542. for (int i=0; i<NumCustomSkins; ++i)
  543. {
  544. if ( m_skins[i] )
  545. {
  546. if ( !stricmp( skinName, m_skins[i] ) )
  547. {
  548. return FirstCustomSkin + i;
  549. }
  550. }
  551. }
  552. return 0;
  553. }
  554. //--------------------------------------------------------------------------------------------------------
  555. /**
  556. * return index of the (custom) bot phrase db, inserting it if needed
  557. */
  558. int BotProfileManager::FindVoiceBankIndex( const char *filename )
  559. {
  560. int index = 0;
  561. for ( int i=0; i<m_voiceBanks.Count(); ++i )
  562. {
  563. if ( !stricmp( filename, m_voiceBanks[i] ) )
  564. {
  565. return index;
  566. }
  567. }
  568. m_voiceBanks.AddToTail( CloneString( filename ) );
  569. return index;
  570. }
  571. //--------------------------------------------------------------------------------------------------------------
  572. /**
  573. * Return random unused profile that matches the given difficulty level
  574. */
  575. const BotProfile *BotProfileManager::GetRandomProfile( BotDifficultyType difficulty, int team, CSWeaponType weaponType ) const
  576. {
  577. // count up valid profiles
  578. CUtlVector< const BotProfile * > profiles;
  579. FOR_EACH_LL( m_profileList, it )
  580. {
  581. const BotProfile *profile = m_profileList[ it ];
  582. // Match difficulty
  583. if ( !profile->IsDifficulty( difficulty ) )
  584. continue;
  585. // Prevent duplicate names
  586. if ( UTIL_IsNameTaken( profile->GetName() ) )
  587. continue;
  588. // Match team choice
  589. if ( !profile->IsValidForTeam( team ) )
  590. continue;
  591. // Match desired weapon
  592. if ( weaponType != WEAPONTYPE_UNKNOWN )
  593. {
  594. if ( !profile->GetWeaponPreferenceCount() )
  595. continue;
  596. if ( weaponType != WeaponClassFromWeaponID( (CSWeaponID)profile->GetWeaponPreference( 0 ) ) )
  597. continue;
  598. }
  599. profiles.AddToTail( profile );
  600. }
  601. if ( !profiles.Count() )
  602. return NULL;
  603. // select one at random
  604. int which = RandomInt( 0, profiles.Count()-1 );
  605. return profiles[which];
  606. }