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.

2582 lines
65 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_gamerules.h"
  10. #include "cs_player.h"
  11. #include "shared_util.h"
  12. #include "engine/IEngineSound.h"
  13. #include "KeyValues.h"
  14. #include "bot.h"
  15. #include "bot_util.h"
  16. #include "cs_bot.h"
  17. #include "cs_bot_chatter.h"
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. /**
  21. * @todo Fix this
  22. */
  23. const Vector *GetRandomSpotAtPlace( Place place )
  24. {
  25. int count = 0;
  26. FOR_EACH_VEC( TheNavAreas, it )
  27. {
  28. CNavArea *area = TheNavAreas[ it ];
  29. if (area->GetPlace() == place)
  30. ++count;
  31. }
  32. if (count == 0)
  33. return NULL;
  34. int which = RandomInt( 0, count-1 );
  35. FOR_EACH_VEC( TheNavAreas, rit )
  36. {
  37. CNavArea *area = TheNavAreas[ rit ];
  38. if (area->GetPlace() == place && which == 0)
  39. return &area->GetCenter();
  40. }
  41. return NULL;
  42. }
  43. //---------------------------------------------------------------------------------------------------------------
  44. /**
  45. * Transmit meme to other bots
  46. */
  47. void BotMeme::Transmit( CCSBot *sender ) const
  48. {
  49. for( int i = 1; i <= gpGlobals->maxClients; i++ )
  50. {
  51. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  52. if (player == NULL)
  53. continue;
  54. // if (FNullEnt( player->pev ))
  55. // continue;
  56. // if (FStrEq( STRING( player->pev->netname ), "" ))
  57. // continue;
  58. // skip self
  59. if (sender == player)
  60. continue;
  61. // ignore dead humans
  62. if (!player->IsBot() && !player->IsAlive())
  63. continue;
  64. // ignore enemies, since we can't hear them talk
  65. if (!player->InSameTeam( sender ))
  66. continue;
  67. // if not a bot, fail the test
  68. if (!player->IsBot())
  69. continue;
  70. CCSBot *bot = dynamic_cast<CCSBot *>( player );
  71. if ( !bot )
  72. continue;
  73. // allow bot to interpret our meme
  74. Interpret( sender, bot );
  75. }
  76. }
  77. //---------------------------------------------------------------------------------------------------------------
  78. /**
  79. * A teammate called for help - respond
  80. */
  81. void BotHelpMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
  82. {
  83. const float maxHelpRange = 3000.0f; // 2000
  84. receiver->RespondToHelpRequest( sender, m_place, maxHelpRange );
  85. }
  86. //---------------------------------------------------------------------------------------------------------------
  87. /**
  88. * A teammate reported information about a bombsite
  89. */
  90. void BotBombsiteStatusMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
  91. {
  92. // remember this bombsite's status
  93. if (m_status == CLEAR)
  94. receiver->GetGameState()->ClearBombsite( m_zoneIndex );
  95. else
  96. receiver->GetGameState()->MarkBombsiteAsPlanted( m_zoneIndex );
  97. // if we were heading to the just-cleared bombsite, pick another one to search
  98. // if our target bombsite wasn't cleared, will will continue going to it,
  99. // because GetNextBombsiteToSearch() will return the same zone (since its not cleared)
  100. // if the bomb was planted, we will head to that bombsite
  101. if (receiver->GetTask() == CCSBot::FIND_TICKING_BOMB)
  102. {
  103. receiver->Idle();
  104. receiver->GetChatter()->Affirmative();
  105. }
  106. }
  107. //---------------------------------------------------------------------------------------------------------------
  108. /**
  109. * A teammate reported information about the bomb
  110. */
  111. void BotBombStatusMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
  112. {
  113. // update our gamestate based on teammate's report
  114. switch( m_state )
  115. {
  116. case CSGameState::MOVING:
  117. receiver->GetGameState()->UpdateBomber( m_pos );
  118. // if we are hunting and see no enemies, respond
  119. if (!receiver->IsRogue() && receiver->IsHunting() && receiver->GetNearbyEnemyCount() == 0)
  120. receiver->RespondToHelpRequest( sender, TheNavMesh->GetPlace( m_pos ) );
  121. break;
  122. case CSGameState::LOOSE:
  123. receiver->GetGameState()->UpdateLooseBomb( m_pos );
  124. if (receiver->GetTask() == CCSBot::GUARD_BOMB_ZONE)
  125. {
  126. receiver->Idle();
  127. receiver->GetChatter()->Affirmative();
  128. }
  129. break;
  130. }
  131. }
  132. //---------------------------------------------------------------------------------------------------------------
  133. /**
  134. * A teammate has asked that we follow him
  135. */
  136. void BotFollowMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
  137. {
  138. if (receiver->IsRogue())
  139. return;
  140. // if we're busy, ignore
  141. if (receiver->IsBusy())
  142. return;
  143. // if we are too far away, ignore
  144. // compute actual travel distance
  145. Vector senderOrigin = GetCentroid( sender );
  146. PathCost cost( receiver );
  147. float travelDistance = NavAreaTravelDistance( receiver->GetLastKnownArea(),
  148. TheNavMesh->GetNearestNavArea( senderOrigin ),
  149. cost );
  150. if (travelDistance < 0.0f)
  151. return;
  152. const float tooFar = 1000.0f;
  153. if (travelDistance > tooFar)
  154. return;
  155. // begin following
  156. receiver->Follow( sender );
  157. // acknowledge
  158. receiver->GetChatter()->Say( "CoveringFriend" );
  159. }
  160. //---------------------------------------------------------------------------------------------------------------
  161. /**
  162. * A teammate has asked us to defend a place
  163. */
  164. void BotDefendHereMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
  165. {
  166. if (receiver->IsRogue())
  167. return;
  168. // if we're busy, ignore
  169. if (receiver->IsBusy())
  170. return;
  171. Place place = TheNavMesh->GetPlace( m_pos );
  172. if (place != UNDEFINED_PLACE)
  173. {
  174. // pick a random hiding spot in this place
  175. const Vector *spot = FindRandomHidingSpot( receiver, place, receiver->IsSniper() );
  176. if (spot)
  177. {
  178. receiver->SetTask( CCSBot::HOLD_POSITION );
  179. receiver->Hide( *spot );
  180. return;
  181. }
  182. }
  183. // hide nearby
  184. receiver->SetTask( CCSBot::HOLD_POSITION );
  185. receiver->Hide( TheNavMesh->GetNearestNavArea( m_pos ) );
  186. // acknowledge
  187. receiver->GetChatter()->Say( "Affirmative" );
  188. }
  189. //---------------------------------------------------------------------------------------------------------------
  190. /**
  191. * A teammate has asked where the bomb is planted
  192. */
  193. void BotWhereBombMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
  194. {
  195. int zone = receiver->GetGameState()->GetPlantedBombsite();
  196. if (zone != CSGameState::UNKNOWN)
  197. receiver->GetChatter()->FoundPlantedBomb( zone );
  198. }
  199. //---------------------------------------------------------------------------------------------------------------
  200. /**
  201. * A teammate has asked us to report in
  202. */
  203. void BotRequestReportMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
  204. {
  205. receiver->GetChatter()->ReportingIn();
  206. }
  207. //---------------------------------------------------------------------------------------------------------------
  208. /**
  209. * A teammate told us all the hostages are gone
  210. */
  211. void BotAllHostagesGoneMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
  212. {
  213. receiver->GetGameState()->AllHostagesGone();
  214. // acknowledge
  215. receiver->GetChatter()->Say( "Affirmative" );
  216. }
  217. //---------------------------------------------------------------------------------------------------------------
  218. /**
  219. * A teammate told us a CT is talking to a hostage
  220. */
  221. void BotHostageBeingTakenMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
  222. {
  223. receiver->GetGameState()->HostageWasTaken();
  224. // if we're busy, ignore
  225. if (receiver->IsBusy())
  226. return;
  227. receiver->Idle();
  228. // acknowledge
  229. receiver->GetChatter()->Say( "Affirmative" );
  230. }
  231. //---------------------------------------------------------------------------------------------------------------
  232. /**
  233. * A teammate heard a noise, so we shouldn't report noises for a while
  234. */
  235. void BotHeardNoiseMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
  236. {
  237. receiver->GetChatter()->FriendHeardNoise();
  238. }
  239. //---------------------------------------------------------------------------------------------------------------
  240. /**
  241. * A teammate warned about snipers, so we shouldn't warn again for awhile
  242. */
  243. void BotWarnSniperMeme::Interpret( CCSBot *sender, CCSBot *receiver ) const
  244. {
  245. receiver->GetChatter()->FriendSpottedSniper();
  246. }
  247. //---------------------------------------------------------------------------------------------------------------
  248. BotSpeakable::BotSpeakable()
  249. {
  250. m_phrase = NULL;
  251. }
  252. //---------------------------------------------------------------------------------------------------------------
  253. BotSpeakable::~BotSpeakable()
  254. {
  255. if ( m_phrase )
  256. {
  257. delete[] m_phrase;
  258. m_phrase = NULL;
  259. }
  260. }
  261. //---------------------------------------------------------------------------------------------------------------
  262. //---------------------------------------------------------------------------------------------------------------
  263. BotPhrase::BotPhrase( bool isPlace )
  264. {
  265. m_name = NULL;
  266. m_place = UNDEFINED_PLACE;
  267. m_isPlace = isPlace;
  268. m_radioEvent = RADIO_INVALID;
  269. m_isImportant = false;
  270. ClearCriteria();
  271. m_numVoiceBanks = 0;
  272. InitVoiceBank( 0 );
  273. }
  274. BotPhrase::~BotPhrase()
  275. {
  276. for( int bank=0; bank<m_voiceBank.Count(); ++bank )
  277. {
  278. for( int speakable=0; speakable<m_voiceBank[bank]->Count(); ++speakable )
  279. {
  280. delete (*m_voiceBank[bank])[speakable];
  281. }
  282. delete m_voiceBank[bank];
  283. }
  284. if ( m_name )
  285. delete [] m_name;
  286. }
  287. void BotPhrase::InitVoiceBank( int bankIndex )
  288. {
  289. while ( m_numVoiceBanks <= bankIndex )
  290. {
  291. m_count.AddToTail(0);
  292. m_index.AddToTail(0);
  293. m_voiceBank.AddToTail( new BotSpeakableVector );
  294. ++m_numVoiceBanks;
  295. }
  296. }
  297. /**
  298. * Return a random speakable - avoid repeating
  299. */
  300. char *BotPhrase::GetSpeakable( int bankIndex, float *duration ) const
  301. {
  302. if (bankIndex < 0 || bankIndex >= m_numVoiceBanks || m_count[bankIndex] == 0)
  303. {
  304. if (duration)
  305. *duration = 0.0f;
  306. return NULL;
  307. }
  308. // find phrase that meets the current criteria
  309. int start = m_index[bankIndex];
  310. while(true)
  311. {
  312. BotSpeakableVector *speakables = m_voiceBank[bankIndex];
  313. int& index = m_index[bankIndex];
  314. const BotSpeakable *speak = (*speakables)[index++];
  315. if (m_index[bankIndex] >= m_count[bankIndex])
  316. m_index[bankIndex] = 0;
  317. // check place criteria
  318. // if this speakable has a place criteria, it must match to be used
  319. // speakables with Place of ANY will match any place
  320. // speakables with a specific Place will only be used if Place matches
  321. // speakables with Place of UNDEFINED only match Place of UNDEFINED
  322. if (speak->m_place == ANY_PLACE || speak->m_place == m_placeCriteria)
  323. {
  324. // check count criteria
  325. // if this speakable has a count criteria, it must match to be used
  326. // if this speakable does not have a count criteria, we dont care what the count is set to
  327. if (speak->m_count == UNDEFINED_COUNT || speak->m_count == MIN( m_countCriteria, COUNT_MANY ))
  328. {
  329. if (duration)
  330. *duration = speak->m_duration;
  331. return speak->m_phrase;
  332. }
  333. }
  334. // check if we exhausted all speakables
  335. if (m_index[bankIndex] == start)
  336. {
  337. if (duration)
  338. *duration = 0.0f;
  339. return NULL;
  340. }
  341. }
  342. }
  343. //---------------------------------------------------------------------------------------------------------------
  344. /**
  345. * Randomly shuffle the speakable order
  346. */
  347. void BotPhrase::Randomize( void )
  348. {
  349. for ( int bank = 0; bank < m_voiceBank.Count(); ++bank )
  350. {
  351. BotSpeakableVector *speakables = m_voiceBank[bank];
  352. if ( speakables->Count() == 1 )
  353. continue;
  354. // A simple shuffle: for each array index, swap it with a random index
  355. for ( int index = 0; index < speakables->Count(); ++index )
  356. {
  357. int newIndex = RandomInt( 0, speakables->Count()-1 );
  358. BotSpeakable *speakable = (*speakables)[index];
  359. (*speakables)[index] = (*speakables)[newIndex];
  360. (*speakables)[newIndex] = speakable;
  361. }
  362. }
  363. }
  364. //---------------------------------------------------------------------------------------------------------------
  365. //---------------------------------------------------------------------------------------------------------------
  366. BotPhraseManager *TheBotPhrases = NULL;
  367. BotPhraseManager::BotPhraseManager( void )
  368. {
  369. m_placeCount = 0;
  370. }
  371. /**
  372. * Invoked when map changes
  373. */
  374. void BotPhraseManager::OnMapChange( void )
  375. {
  376. m_placeCount = 0;
  377. }
  378. /**
  379. * Removes everything from memory
  380. */
  381. void BotPhraseManager::Reset( void )
  382. {
  383. int i;
  384. // free phrase resources
  385. for( i=0; i<m_list.Count(); ++i )
  386. {
  387. delete m_list[i];
  388. }
  389. for( i=0; i<m_placeList.Count(); ++i )
  390. {
  391. delete m_placeList[i];
  392. }
  393. m_list.RemoveAll();
  394. m_placeList.RemoveAll();
  395. m_painPhrase = NULL;
  396. m_agreeWithPlanPhrase = NULL;
  397. }
  398. /**
  399. * Invoked when the round resets
  400. */
  401. void BotPhraseManager::OnRoundRestart( void )
  402. {
  403. // effectively reset all interval timers
  404. m_placeCount = 0;
  405. // shuffle all the speakables
  406. int i;
  407. for( i=0; i<m_placeList.Count(); ++i )
  408. m_placeList[i]->Randomize();
  409. for( i=0; i<m_list.Count(); ++i )
  410. m_list[i]->Randomize();
  411. }
  412. BotChatterOutputType BotPhraseManager::GetOutputType( int voiceBank ) const
  413. {
  414. if ( voiceBank >= 0 && voiceBank < m_output.Count() )
  415. {
  416. return m_output[voiceBank];
  417. }
  418. return BOT_CHATTER_RADIO;
  419. }
  420. /**
  421. * Initialize phrase system from database file
  422. */
  423. bool BotPhraseManager::Initialize( const char *filename, int bankIndex )
  424. {
  425. bool isDefault = (bankIndex == 0);
  426. FileHandle_t file = filesystem->Open( filename, "r" );
  427. if (!file)
  428. {
  429. CONSOLE_ECHO( "WARNING: Cannot access bot phrase database '%s'\n", filename );
  430. return false;
  431. }
  432. // BOTPORT: Redo file reading to avoid loading whole file into memory at once
  433. int phraseDataLength = filesystem->Size( filename );
  434. char *phraseDataFile = new char[ phraseDataLength ];
  435. int dataReadLength = filesystem->Read( phraseDataFile, phraseDataLength, file );
  436. filesystem->Close( file );
  437. if ( dataReadLength > 0 )
  438. {
  439. // NULL-terminate based on the length read in, since Read() can transform \r\n to \n and
  440. // return fewer bytes than we were expecting.
  441. phraseDataFile[ dataReadLength - 1 ] = 0;
  442. }
  443. const char *phraseData = phraseDataFile;
  444. const int RadioPathLen = 128; // wav filenames need to be shorter than this to go over the net anyway.
  445. char baseDir[RadioPathLen] = "";
  446. char compositeFilename[RadioPathLen];
  447. //
  448. // Parse the BotChatter.db into BotPhrase collections
  449. //
  450. while( true )
  451. {
  452. phraseData = SharedParse( phraseData );
  453. if (!phraseData)
  454. break;
  455. char *token = SharedGetToken();
  456. if ( !stricmp( token, "Output" ) )
  457. {
  458. // get name of this output device
  459. phraseData = SharedParse( phraseData );
  460. if (!phraseData)
  461. {
  462. CONSOLE_ECHO( "Error parsing '%s' - expected identifier\n", filename );
  463. delete [] phraseDataFile;
  464. return false;
  465. }
  466. while ( m_output.Count() <= bankIndex )
  467. {
  468. m_output.AddToTail(BOT_CHATTER_RADIO);
  469. }
  470. char *token = SharedGetToken();
  471. if ( !stricmp( token, "Voice" ) )
  472. {
  473. m_output[bankIndex] = BOT_CHATTER_VOICE;
  474. }
  475. }
  476. else if ( !stricmp( token, "BaseDir" ) )
  477. {
  478. // get name of this output device
  479. phraseData = SharedParse( phraseData );
  480. if (!phraseData)
  481. {
  482. CONSOLE_ECHO( "Error parsing '%s' - expected identifier\n", filename );
  483. delete [] phraseDataFile;
  484. return false;
  485. }
  486. char *token = SharedGetToken();
  487. Q_strncpy( baseDir, token, RadioPathLen );
  488. Q_strncat( baseDir, "\\", RadioPathLen, -1 );
  489. baseDir[RadioPathLen-1] = 0;
  490. }
  491. else if (!stricmp( token, "Place" ) || !stricmp( token, "Chatter" ))
  492. {
  493. bool isPlace = (stricmp( token, "Place" )) ? false : true;
  494. // encountered a new phrase collection
  495. BotPhrase *phrase = NULL;
  496. if ( isDefault )
  497. {
  498. phrase = new BotPhrase( isPlace );
  499. }
  500. // get name of this phrase
  501. phraseData = SharedParse( phraseData );
  502. if (!phraseData)
  503. {
  504. CONSOLE_ECHO( "Error parsing '%s' - expected identifier\n", filename );
  505. delete [] phraseDataFile;
  506. return false;
  507. }
  508. if ( isDefault )
  509. {
  510. phrase->m_name = CloneString( SharedGetToken() );
  511. phrase->m_place = (isPlace) ? TheNavMesh->NameToPlace( phrase->m_name ) : UNDEFINED_PLACE;
  512. }
  513. else // look up the existing phrase
  514. {
  515. if ( isPlace )
  516. {
  517. phrase = const_cast<BotPhrase *>(GetPlace( SharedGetToken() ));
  518. }
  519. else
  520. {
  521. phrase = const_cast<BotPhrase *>(GetPhrase( SharedGetToken() ));
  522. }
  523. if ( !phrase )
  524. {
  525. CONSOLE_ECHO( "Error parsing '%s' - phrase '%s' is invalid\n", filename, SharedGetToken() );
  526. delete [] phraseDataFile;
  527. return false;
  528. }
  529. }
  530. phrase->InitVoiceBank( bankIndex );
  531. PlaceCriteria placeCriteria = ANY_PLACE;
  532. CountCriteria countCriteria = UNDEFINED_COUNT;
  533. RadioType radioEvent = RADIO_INVALID;
  534. bool isImportant = false;
  535. // read attributes of this phrase
  536. while( true )
  537. {
  538. // get next token
  539. phraseData = SharedParse( phraseData );
  540. if (!phraseData)
  541. {
  542. CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
  543. delete [] phraseDataFile;
  544. return false;
  545. }
  546. token = SharedGetToken();
  547. // check for Place criteria
  548. if (!stricmp( token, "Place" ))
  549. {
  550. phraseData = SharedParse( phraseData );
  551. if (!phraseData)
  552. {
  553. CONSOLE_ECHO( "Error parsing %s - expected Place name\n", filename );
  554. delete [] phraseDataFile;
  555. return false;
  556. }
  557. token = SharedGetToken();
  558. // update place criteria for subsequent speak lines
  559. // NOTE: this assumes places must be first in the chatter database
  560. // check for special identifiers
  561. if (!stricmp( "ANY", token ))
  562. placeCriteria = ANY_PLACE;
  563. else if (!stricmp( "UNDEFINED", token ))
  564. placeCriteria = UNDEFINED_PLACE;
  565. else
  566. placeCriteria = TheNavMesh->NameToPlace( token );
  567. continue;
  568. }
  569. // check for Count criteria
  570. if (!stricmp( token, "Count" ))
  571. {
  572. phraseData = SharedParse( phraseData );
  573. if (!phraseData)
  574. {
  575. CONSOLE_ECHO( "Error parsing %s - expected Count value\n", filename );
  576. delete [] phraseDataFile;
  577. return false;
  578. }
  579. token = SharedGetToken();
  580. // update count criteria for subsequent speak lines
  581. if (!stricmp( token, "Many" ))
  582. countCriteria = COUNT_MANY;
  583. else
  584. countCriteria = atoi( token );
  585. continue;
  586. }
  587. // check for radio equivalent
  588. if (!stricmp( token, "Radio" ))
  589. {
  590. phraseData = SharedParse( phraseData );
  591. if (!phraseData)
  592. {
  593. CONSOLE_ECHO( "Error parsing %s - expected radio event\n", filename );
  594. delete [] phraseDataFile;
  595. return false;
  596. }
  597. token = SharedGetToken();
  598. RadioType event = NameToRadioEvent( token );
  599. if (event <= RADIO_START_1 || event >= RADIO_END)
  600. {
  601. CONSOLE_ECHO( "Error parsing %s - invalid radio event '%s'\n", filename, token );
  602. delete [] phraseDataFile;
  603. return false;
  604. }
  605. radioEvent = event;
  606. continue;
  607. }
  608. // check for "important" flag
  609. if (!stricmp( token, "Important" ))
  610. {
  611. isImportant = true;
  612. continue;
  613. }
  614. // check for End delimiter
  615. if (!stricmp( token, "End" ))
  616. break;
  617. // found a phrase - add it to the collection
  618. BotSpeakable *speak = new BotSpeakable;
  619. if ( baseDir[0] )
  620. {
  621. Q_snprintf( compositeFilename, RadioPathLen, "%s%s", baseDir, token );
  622. speak->m_phrase = CloneString( compositeFilename );
  623. }
  624. else
  625. {
  626. speak->m_phrase = CloneString( token );
  627. }
  628. speak->m_place = placeCriteria;
  629. speak->m_count = countCriteria;
  630. #ifdef POSIX
  631. Q_FixSlashes( speak->m_phrase );
  632. Q_strlower( speak->m_phrase );
  633. #endif
  634. speak->m_duration = enginesound->GetSoundDuration( speak->m_phrase );
  635. if (speak->m_duration <= 0.0f)
  636. {
  637. if ( !engine->IsDedicatedServer() )
  638. {
  639. DevMsg( "Warning: Couldn't get duration of phrase '%s'\n", speak->m_phrase );
  640. }
  641. speak->m_duration = 1.0f;
  642. }
  643. BotSpeakableVector * speakables = phrase->m_voiceBank[ bankIndex ];
  644. speakables->AddToTail( speak );
  645. ++phrase->m_count[ bankIndex ];
  646. }
  647. if ( isDefault )
  648. {
  649. phrase->m_radioEvent = radioEvent;
  650. phrase->m_isImportant = isImportant;
  651. }
  652. // add phrase collection to the appropriate master list
  653. if (isPlace)
  654. m_placeList.AddToTail( phrase );
  655. else
  656. m_list.AddToTail( phrase );
  657. }
  658. }
  659. delete [] phraseDataFile;
  660. m_painPhrase = GetPhrase( "Pain" );
  661. m_agreeWithPlanPhrase = GetPhrase( "AgreeWithPlan" );
  662. return true;
  663. }
  664. BotPhraseManager::~BotPhraseManager()
  665. {
  666. Reset();
  667. }
  668. /**
  669. * Given a name, return the associated phrase collection
  670. */
  671. const BotPhrase *BotPhraseManager::GetPhrase( const char *name ) const
  672. {
  673. for( int i=0; i<m_list.Count(); ++i )
  674. {
  675. if (!stricmp( m_list[i]->m_name, name ))
  676. return m_list[i];
  677. }
  678. //CONSOLE_ECHO( "GetPhrase: ERROR - Invalid phrase '%s'\n", name );
  679. return NULL;
  680. }
  681. /**
  682. * Given an id, return the associated phrase collection
  683. * @todo Store phrases in a vector to make this fast
  684. */
  685. /*
  686. const BotPhrase *BotPhraseManager::GetPhrase( unsigned int place ) const
  687. {
  688. for( BotPhraseList::const_iterator iter = m_list.begin(); iter != m_list.end(); ++iter )
  689. {
  690. const BotPhrase *phrase = *iter;
  691. if (phrase->m_place == id)
  692. return phrase;
  693. }
  694. CONSOLE_ECHO( "GetPhrase: ERROR - Invalid phrase id #%d\n", id );
  695. return NULL;
  696. }
  697. */
  698. /**
  699. * Given a name, return the associated Place phrase collection
  700. */
  701. const BotPhrase *BotPhraseManager::GetPlace( const char *name ) const
  702. {
  703. if (name == NULL)
  704. return NULL;
  705. for( int i=0; i<m_placeList.Count(); ++i )
  706. {
  707. if (!stricmp( m_placeList[i]->m_name, name ))
  708. return m_placeList[i];
  709. }
  710. return NULL;
  711. }
  712. /**
  713. * Given a name, return the associated Place phrase collection
  714. */
  715. const BotPhrase *BotPhraseManager::GetPlace( PlaceCriteria place ) const
  716. {
  717. if (place == UNDEFINED_PLACE)
  718. return NULL;
  719. for( int i=0; i<m_placeList.Count(); ++i )
  720. {
  721. if (m_placeList[i]->m_place == place)
  722. return m_placeList[i];
  723. }
  724. return NULL;
  725. }
  726. //---------------------------------------------------------------------------------------------------------------
  727. //---------------------------------------------------------------------------------------------------------------
  728. BotStatement::BotStatement( BotChatterInterface *chatter, BotStatementType type, float expireDuration )
  729. {
  730. m_chatter = chatter;
  731. m_next = NULL;
  732. m_prev = NULL;
  733. m_timestamp = gpGlobals->curtime;
  734. m_speakTimestamp = 0.0f;
  735. m_type = type;
  736. m_subject = UNDEFINED_SUBJECT;
  737. m_place = UNDEFINED_PLACE;
  738. m_meme = NULL;
  739. m_startTime = gpGlobals->curtime;
  740. m_expireTime = gpGlobals->curtime + expireDuration;
  741. m_isSpeaking = false;
  742. m_nextTime = 0.0f;
  743. m_index = -1;
  744. m_count = 0;
  745. m_conditionCount = 0;
  746. }
  747. BotStatement::~BotStatement()
  748. {
  749. if (m_meme)
  750. delete m_meme;
  751. }
  752. //---------------------------------------------------------------------------------------------------------------
  753. CCSBot *BotStatement::GetOwner( void ) const
  754. {
  755. return m_chatter->GetOwner();
  756. }
  757. //---------------------------------------------------------------------------------------------------------------
  758. /**
  759. * Attach a meme to this statement, to be transmitted to other friendly bots when spoken
  760. */
  761. void BotStatement::AttachMeme( BotMeme *meme )
  762. {
  763. m_meme = meme;
  764. }
  765. //---------------------------------------------------------------------------------------------------------------
  766. /**
  767. * Add a conditions that must be true for the statement to be spoken
  768. */
  769. void BotStatement::AddCondition( ConditionType condition )
  770. {
  771. if (m_conditionCount < MAX_BOT_CONDITIONS)
  772. m_condition[ m_conditionCount++ ] = condition;
  773. }
  774. //---------------------------------------------------------------------------------------------------------------
  775. /**
  776. * Return true if this statement is "important" and not personality chatter
  777. */
  778. bool BotStatement::IsImportant( void ) const
  779. {
  780. // if a statement contains any important phrases, it is important
  781. for( int i=0; i<m_count; ++i )
  782. {
  783. if (m_statement[i].isPhrase && m_statement[i].phrase->IsImportant())
  784. return true;
  785. // hack for now - phrases with enemy counts are important
  786. if (!m_statement[i].isPhrase && m_statement[i].context == BotStatement::CURRENT_ENEMY_COUNT)
  787. return true;
  788. }
  789. return false;
  790. }
  791. //---------------------------------------------------------------------------------------------------------------
  792. /**
  793. * Verify all attached conditions
  794. */
  795. bool BotStatement::IsValid( void ) const
  796. {
  797. for( int i=0; i<m_conditionCount; ++i )
  798. {
  799. switch( m_condition[i] )
  800. {
  801. case IS_IN_COMBAT:
  802. {
  803. if (!GetOwner()->IsAttacking())
  804. return false;
  805. break;
  806. }
  807. /*
  808. case RADIO_SILENCE:
  809. {
  810. if (GetOwner()->GetChatter()->GetRadioSilenceDuration() < 10.0f)
  811. return false;
  812. break;
  813. }
  814. */
  815. case ENEMIES_REMAINING:
  816. {
  817. if (GetOwner()->GetEnemiesRemaining() == 0)
  818. return false;
  819. break;
  820. }
  821. }
  822. }
  823. return true;
  824. }
  825. //---------------------------------------------------------------------------------------------------------------
  826. /**
  827. * Return true if this statement is essentially the same as the given one
  828. */
  829. bool BotStatement::IsRedundant( const BotStatement *say ) const
  830. {
  831. // special cases
  832. if (GetType() == REPORT_MY_PLAN ||
  833. GetType() == REPORT_REQUEST_HELP ||
  834. GetType() == REPORT_CRITICAL_EVENT ||
  835. GetType() == REPORT_ACKNOWLEDGE)
  836. return false;
  837. // check if topics are different
  838. if (say->GetType() != GetType())
  839. return false;
  840. if (!say->HasPlace() && !HasPlace() && !say->HasSubject() && !HasSubject())
  841. {
  842. // neither has place or subject, so they are the same
  843. return true;
  844. }
  845. // check if subject matter is the same
  846. if (say->HasPlace() && HasPlace() && say->GetPlace() == GetPlace())
  847. {
  848. // talking about the same place
  849. return true;
  850. }
  851. if (say->HasSubject() && HasSubject() && say->GetSubject() == GetSubject())
  852. {
  853. // talking about the same player
  854. return true;
  855. }
  856. return false;
  857. }
  858. //---------------------------------------------------------------------------------------------------------------
  859. /**
  860. * Return true if this statement is no longer appropriate to say
  861. */
  862. bool BotStatement::IsObsolete( void ) const
  863. {
  864. // if the round is over, the only things we should say are emotes
  865. if (GetOwner()->GetGameState()->IsRoundOver())
  866. {
  867. if (m_type != REPORT_EMOTE)
  868. return true;
  869. }
  870. // If we're wanting to say "I lost him" but we've spotted another enemy,
  871. // we no longer need to report losing someone.
  872. if ( GetOwner()->GetChatter()->SeesAtLeastOneEnemy() && m_type == REPORT_ENEMY_LOST )
  873. {
  874. return true;
  875. }
  876. // check if statement lifetime has expired
  877. return (gpGlobals->curtime > m_expireTime);
  878. }
  879. //---------------------------------------------------------------------------------------------------------------
  880. /**
  881. * Possibly change what were going to say base on what teammate is saying
  882. */
  883. void BotStatement::Convert( const BotStatement *say )
  884. {
  885. if (GetType() == REPORT_MY_PLAN && say->GetType() == REPORT_MY_PLAN)
  886. {
  887. const BotPhrase *meToo = TheBotPhrases->GetAgreeWithPlanPhrase();
  888. // don't reconvert
  889. if (m_statement[0].phrase == meToo)
  890. return;
  891. // if our plans are the same, change our statement to "me too"
  892. if (m_statement[0].phrase == say->m_statement[0].phrase)
  893. {
  894. if (m_place == say->m_place)
  895. {
  896. // same plan at the same place - convert to "me too"
  897. m_statement[0].phrase = meToo;
  898. m_startTime = gpGlobals->curtime + RandomFloat( 0.5f, 1.0f );
  899. }
  900. else
  901. {
  902. // same plan at different place - wait a bit to allow others to respond "me too"
  903. m_startTime = gpGlobals->curtime + RandomFloat( 3.0f, 4.0f );
  904. }
  905. }
  906. }
  907. }
  908. //---------------------------------------------------------------------------------------------------------------
  909. void BotStatement::AppendPhrase( const BotPhrase *phrase )
  910. {
  911. if (phrase == NULL)
  912. return;
  913. if (m_count < MAX_BOT_PHRASES)
  914. {
  915. m_statement[ m_count ].isPhrase = true;
  916. m_statement[ m_count++ ].phrase = phrase;
  917. }
  918. }
  919. /**
  920. * Special phrases that depend on the context
  921. */
  922. void BotStatement::AppendPhrase( ContextType contextPhrase )
  923. {
  924. if (m_count < MAX_BOT_PHRASES)
  925. {
  926. m_statement[ m_count ].isPhrase = false;
  927. m_statement[ m_count++ ].context = contextPhrase;
  928. }
  929. }
  930. /**
  931. * Say our statement
  932. * m_index refers to the phrase currently being spoken, or -1 if we havent started yet
  933. */
  934. bool BotStatement::Update( void )
  935. {
  936. CCSBot *me = GetOwner();
  937. // if all of our teammates are dead, the only non-redundant statements are emotes
  938. if (me->GetFriendsRemaining() == 0 && GetType() != REPORT_EMOTE)
  939. return false;
  940. if (!m_isSpeaking)
  941. {
  942. m_isSpeaking = true;
  943. m_speakTimestamp = gpGlobals->curtime;
  944. }
  945. // special case - context dependent delay
  946. if (m_index >= 0 && m_statement[ m_index ].context == ACCUMULATE_ENEMIES_DELAY)
  947. {
  948. // report if we see a lot of enemies, or if enough time has passed
  949. const float reportTime = 2.0f; // 1
  950. if (me->GetNearbyEnemyCount() > 3 || gpGlobals->curtime - m_speakTimestamp > reportTime)
  951. {
  952. // enough enemies have accumulated to expire this delay
  953. m_nextTime = 0.0f;
  954. }
  955. }
  956. if (gpGlobals->curtime > m_nextTime)
  957. {
  958. // check for end of statement
  959. if (++m_index == m_count)
  960. {
  961. // transmit any memes carried in this statement to our teammates
  962. if (m_meme)
  963. m_meme->Transmit( me );
  964. return false;
  965. }
  966. // start next part of statement
  967. float duration = 0.0f;
  968. const BotPhrase *phrase = NULL;
  969. if (m_statement[ m_index ].isPhrase)
  970. {
  971. // normal phrase
  972. phrase = m_statement[ m_index ].phrase;
  973. }
  974. else
  975. {
  976. // context-dependant phrase
  977. switch( m_statement[ m_index ].context )
  978. {
  979. case CURRENT_ENEMY_COUNT:
  980. {
  981. int enemyCount = me->GetNearbyEnemyCount();
  982. // if we are outnumbered, ask for help
  983. if (enemyCount-1 > me->GetNearbyFriendCount())
  984. {
  985. phrase = TheBotPhrases->GetPhrase( "Help" );
  986. AttachMeme( new BotHelpMeme() );
  987. }
  988. else if (enemyCount > 1)
  989. {
  990. phrase = TheBotPhrases->GetPhrase( "EnemySpotted" );
  991. phrase->SetCountCriteria( enemyCount );
  992. }
  993. break;
  994. }
  995. case REMAINING_ENEMY_COUNT:
  996. {
  997. static const char *speak[] =
  998. {
  999. "NoEnemiesLeft", "OneEnemyLeft", "TwoEnemiesLeft", "ThreeEnemiesLeft"
  1000. };
  1001. int enemyCount = me->GetEnemiesRemaining();
  1002. // dont report if there are lots of enemies left
  1003. if (enemyCount < 0 || enemyCount > 3)
  1004. {
  1005. phrase = NULL;
  1006. }
  1007. else
  1008. {
  1009. phrase = TheBotPhrases->GetPhrase( speak[ enemyCount ] );
  1010. }
  1011. break;
  1012. }
  1013. case SHORT_DELAY:
  1014. {
  1015. m_nextTime = gpGlobals->curtime + RandomFloat( 0.1f, 0.5f );
  1016. return true;
  1017. }
  1018. case LONG_DELAY:
  1019. {
  1020. m_nextTime = gpGlobals->curtime + RandomFloat( 1.0f, 2.0f );
  1021. return true;
  1022. }
  1023. case ACCUMULATE_ENEMIES_DELAY:
  1024. {
  1025. // wait until test becomes true
  1026. m_nextTime = 99999999.9f;
  1027. return true;
  1028. }
  1029. }
  1030. }
  1031. if (phrase)
  1032. {
  1033. // if chatter system is in "standard radio" mode, send the equivalent radio command
  1034. if (me->GetChatter()->GetVerbosity() == BotChatterInterface::RADIO)
  1035. {
  1036. RadioType radioEvent = phrase->GetRadioEquivalent();
  1037. if (radioEvent == RADIO_INVALID)
  1038. {
  1039. // skip directly to the next phrase
  1040. m_nextTime = 0.0f;
  1041. }
  1042. else
  1043. {
  1044. // use the standard radio
  1045. me->GetChatter()->ResetRadioSilenceDuration();
  1046. me->SendRadioMessage( radioEvent );
  1047. duration = 2.0f;
  1048. }
  1049. }
  1050. else
  1051. {
  1052. // set place criteria
  1053. phrase->SetPlaceCriteria( m_place );
  1054. const char *filename = phrase->GetSpeakable( me->GetProfile()->GetVoiceBank(), &duration );
  1055. // CONSOLE_ECHO( "%s: Radio( '%s' )\n", STRING( me->pev->netname ), filename );
  1056. bool sayIt = true;
  1057. if (phrase->IsPlace())
  1058. {
  1059. // don't repeat the place if someone just mentioned it not too long ago
  1060. float timeSince = TheBotPhrases->GetPlaceStatementInterval( phrase->GetPlace() );
  1061. const float minRepeatTime = 20.0f; // 30
  1062. if (timeSince < minRepeatTime)
  1063. {
  1064. sayIt = false;
  1065. }
  1066. else
  1067. {
  1068. TheBotPhrases->ResetPlaceStatementInterval( phrase->GetPlace() );
  1069. }
  1070. }
  1071. if (sayIt)
  1072. {
  1073. if ( !filename )
  1074. {
  1075. RadioType radioEvent = phrase->GetRadioEquivalent();
  1076. if (radioEvent == RADIO_INVALID)
  1077. {
  1078. // skip directly to the next phrase
  1079. m_nextTime = 0.0f;
  1080. }
  1081. else
  1082. {
  1083. // use the standard radio
  1084. me->SendRadioMessage( radioEvent );
  1085. me->GetChatter()->ResetRadioSilenceDuration();
  1086. duration = 2.0f;
  1087. }
  1088. }
  1089. /* BOTPORT: Wire up bot voice over IP
  1090. else if ( g_engfuncs.pfnPlayClientVoice && TheBotPhrases->GetOutputType( me->GetProfile()->GetVoiceBank() ) == BOT_CHATTER_VOICE )
  1091. {
  1092. me->GetChatter()->ResetRadioSilenceDuration();
  1093. g_engfuncs.pfnPlayClientVoice( me->entindex() - 1, filename );
  1094. }
  1095. */
  1096. else
  1097. {
  1098. me->SpeakAudio( filename, duration + 1.0f, me->GetProfile()->GetVoicePitch() );
  1099. }
  1100. }
  1101. }
  1102. const float gap = 0.1f;
  1103. m_nextTime = gpGlobals->curtime + duration + gap;
  1104. }
  1105. else
  1106. {
  1107. // skip directly to the next phrase
  1108. m_nextTime = 0.0f;
  1109. }
  1110. }
  1111. return true;
  1112. }
  1113. /**
  1114. * If this statement refers to a specific place, return that place
  1115. * Places can be implicit in the statement, or explicitly defined
  1116. */
  1117. unsigned int BotStatement::GetPlace( void ) const
  1118. {
  1119. // return any explicitly set place if we have one
  1120. if (m_place != UNDEFINED_PLACE)
  1121. return m_place;
  1122. // look for an implicit place in our statement
  1123. for( int i=0; i<m_count; ++i )
  1124. if (m_statement[i].isPhrase && m_statement[i].phrase->IsPlace())
  1125. return m_statement[i].phrase->GetPlace();
  1126. return 0;
  1127. }
  1128. /**
  1129. * Return true if this statement has an associated count
  1130. */
  1131. bool BotStatement::HasCount( void ) const
  1132. {
  1133. for( int i=0; i<m_count; ++i )
  1134. if (!m_statement[i].isPhrase && m_statement[i].context == CURRENT_ENEMY_COUNT)
  1135. return true;
  1136. return false;
  1137. }
  1138. //---------------------------------------------------------------------------------------------------------------
  1139. //---------------------------------------------------------------------------------------------------------------
  1140. CountdownTimer BotChatterInterface::m_encourageTimer;
  1141. IntervalTimer BotChatterInterface::m_radioSilenceInterval[ 2 ];
  1142. enum PitchHack
  1143. {
  1144. P_HI,
  1145. P_NORMAL,
  1146. P_LOW
  1147. };
  1148. static int nextPitch = P_HI;
  1149. BotChatterInterface::BotChatterInterface( CCSBot *me )
  1150. {
  1151. m_me = me;
  1152. m_statementList = NULL;
  1153. switch( nextPitch )
  1154. {
  1155. case P_HI:
  1156. m_pitch = RandomInt( 105, 110 );
  1157. break;
  1158. case P_NORMAL:
  1159. m_pitch = RandomInt( 95, 105 );
  1160. break;
  1161. case P_LOW:
  1162. m_pitch = RandomInt( 85, 95 );
  1163. break;
  1164. }
  1165. nextPitch = (nextPitch + 1) % 3;
  1166. Reset();
  1167. }
  1168. //---------------------------------------------------------------------------------------------------------------
  1169. BotChatterInterface::~BotChatterInterface()
  1170. {
  1171. // free pending statements
  1172. BotStatement *next;
  1173. for( BotStatement *msg = m_statementList; msg; msg = next )
  1174. {
  1175. next = msg->m_next;
  1176. delete msg;
  1177. }
  1178. }
  1179. //---------------------------------------------------------------------------------------------------------------
  1180. /**
  1181. * Reset to initial state
  1182. */
  1183. void BotChatterInterface::Reset( void )
  1184. {
  1185. BotStatement *msg, *nextMsg;
  1186. // removing pending statements - except for those about the round results
  1187. for( msg = m_statementList; msg; msg = nextMsg )
  1188. {
  1189. nextMsg = msg->m_next;
  1190. if (msg->GetType() != REPORT_ROUND_END)
  1191. RemoveStatement( msg );
  1192. }
  1193. m_seeAtLeastOneEnemy = false;
  1194. m_timeWhenSawFirstEnemy = 0.0f;
  1195. m_reportedEnemies = false;
  1196. m_requestedBombLocation = false;
  1197. ResetRadioSilenceDuration();
  1198. m_needBackupInterval.Invalidate();
  1199. m_spottedBomberInterval.Invalidate();
  1200. m_spottedLooseBombTimer.Invalidate();
  1201. m_heardNoiseTimer.Invalidate();
  1202. m_scaredInterval.Invalidate();
  1203. m_planInterval.Invalidate();
  1204. m_encourageTimer.Invalidate();
  1205. m_escortingHostageTimer.Invalidate();
  1206. m_warnSniperTimer.Invalidate();
  1207. }
  1208. //---------------------------------------------------------------------------------------------------------------
  1209. /**
  1210. * Register a statement for speaking
  1211. */
  1212. void BotChatterInterface::AddStatement( BotStatement *statement, bool mustAdd )
  1213. {
  1214. // don't add statements if bot chatter is shut off
  1215. if (GetVerbosity() == OFF)
  1216. {
  1217. delete statement;
  1218. return;
  1219. }
  1220. // if we only want mission-critical radio chatter, ignore non-important phrases
  1221. if (GetVerbosity() == MINIMAL && !statement->IsImportant())
  1222. {
  1223. delete statement;
  1224. return;
  1225. }
  1226. // don't add statements if we're dead
  1227. if (!m_me->IsAlive() && !mustAdd)
  1228. {
  1229. delete statement;
  1230. return;
  1231. }
  1232. // don't add empty statements
  1233. if (statement->m_count == 0)
  1234. {
  1235. delete statement;
  1236. return;
  1237. }
  1238. // don't add statements that are redundant with something we're already waiting to say
  1239. BotStatement *s;
  1240. for( s=m_statementList; s; s = s->m_next )
  1241. {
  1242. if (statement->IsRedundant( s ))
  1243. {
  1244. m_me->PrintIfWatched( "I tried to say something I'm already saying.\n" );
  1245. delete statement;
  1246. return;
  1247. }
  1248. }
  1249. // keep statements in order of start time
  1250. // check list is empty
  1251. if (m_statementList == NULL)
  1252. {
  1253. statement->m_next = NULL;
  1254. statement->m_prev = NULL;
  1255. m_statementList = statement;
  1256. return;
  1257. }
  1258. // list has at least one statement on it
  1259. // insert into list in order
  1260. BotStatement *earlier = NULL;
  1261. for( s=m_statementList; s; s = s->m_next )
  1262. {
  1263. if (s->GetStartTime() > statement->GetStartTime())
  1264. break;
  1265. earlier = s;
  1266. }
  1267. // insert just after "earlier"
  1268. if (earlier)
  1269. {
  1270. if (earlier->m_next)
  1271. earlier->m_next->m_prev = statement;
  1272. statement->m_next = earlier->m_next;
  1273. earlier->m_next = statement;
  1274. statement->m_prev = earlier;
  1275. }
  1276. else
  1277. {
  1278. // insert at head
  1279. statement->m_prev = NULL;
  1280. statement->m_next = m_statementList;
  1281. m_statementList->m_prev = statement;
  1282. m_statementList = statement;
  1283. }
  1284. }
  1285. //---------------------------------------------------------------------------------------------------------------
  1286. /**
  1287. * Remove a statement
  1288. */
  1289. void BotChatterInterface::RemoveStatement( BotStatement *statement )
  1290. {
  1291. if (statement->m_next)
  1292. statement->m_next->m_prev = statement->m_prev;
  1293. if (statement->m_prev)
  1294. statement->m_prev->m_next = statement->m_next;
  1295. else
  1296. m_statementList = statement->m_next;
  1297. delete statement;
  1298. }
  1299. //---------------------------------------------------------------------------------------------------------------
  1300. /**
  1301. * Track nearby enemy count and report enemy activity
  1302. */
  1303. void BotChatterInterface::ReportEnemies( void )
  1304. {
  1305. if (!m_me->IsAlive())
  1306. return;
  1307. if (m_me->GetNearbyEnemyCount() == 0)
  1308. {
  1309. m_seeAtLeastOneEnemy = false;
  1310. m_reportedEnemies = false;
  1311. }
  1312. else if (!m_seeAtLeastOneEnemy)
  1313. {
  1314. m_seeAtLeastOneEnemy = true;
  1315. m_timeWhenSawFirstEnemy = gpGlobals->curtime;
  1316. }
  1317. // determine whether we should report enemy activity
  1318. if (!m_reportedEnemies && m_seeAtLeastOneEnemy)
  1319. {
  1320. // request backup if we're outnumbered
  1321. if (m_me->IsOutnumbered() && NeedBackup())
  1322. {
  1323. m_reportedEnemies = true;
  1324. return;
  1325. }
  1326. m_me->GetChatter()->EnemySpotted();
  1327. m_reportedEnemies = true;
  1328. }
  1329. }
  1330. //---------------------------------------------------------------------------------------------------------------
  1331. /**
  1332. * Invoked when we die
  1333. */
  1334. void BotChatterInterface::OnDeath( void )
  1335. {
  1336. if (IsTalking())
  1337. {
  1338. if (m_me->GetChatter()->GetVerbosity() == BotChatterInterface::MINIMAL ||
  1339. m_me->GetChatter()->GetVerbosity() == BotChatterInterface::NORMAL)
  1340. {
  1341. // we've died mid-sentance - emit a gargle of pain
  1342. const BotPhrase *pain = TheBotPhrases->GetPainPhrase();
  1343. if (pain)
  1344. {
  1345. /*
  1346. if ( g_engfuncs.pfnPlayClientVoice && TheBotPhrases->GetOutputType( m_me->GetProfile()->GetVoiceBank() ) == BOT_CHATTER_VOICE )
  1347. {
  1348. g_engfuncs.pfnPlayClientVoice( m_me->entindex() - 1, pain->GetSpeakable(m_me->GetProfile()->GetVoiceBank()) );
  1349. m_me->GetChatter()->ResetRadioSilenceDuration();
  1350. }
  1351. else
  1352. */
  1353. {
  1354. m_me->SpeakAudio( pain->GetSpeakable( m_me->GetProfile()->GetVoiceBank() ), 0.0f, m_me->GetProfile()->GetVoicePitch() );
  1355. }
  1356. }
  1357. }
  1358. }
  1359. // remove all of our statements
  1360. Reset();
  1361. }
  1362. //---------------------------------------------------------------------------------------------------------------
  1363. /**
  1364. * Process ongoing chatter for this bot
  1365. */
  1366. void BotChatterInterface::Update( void )
  1367. {
  1368. // report enemy activity
  1369. ReportEnemies();
  1370. // ask team to report in if we havent heard anything in awhile
  1371. if (ShouldSpeak())
  1372. {
  1373. const float longTime = 30.0f;
  1374. if (m_me->GetEnemiesRemaining() > 0 && GetRadioSilenceDuration() > longTime)
  1375. {
  1376. ReportIn();
  1377. }
  1378. }
  1379. // speak if it is our turn
  1380. BotStatement *say = GetActiveStatement();
  1381. if (say)
  1382. {
  1383. // if our statement is active, speak it
  1384. if (say->GetOwner() == m_me)
  1385. {
  1386. if (say->Update() == false)
  1387. {
  1388. // this statement is complete - destroy it
  1389. RemoveStatement( say );
  1390. }
  1391. }
  1392. }
  1393. //
  1394. // Process active statements.
  1395. // Removed expired statements, re-order statements according to their relavence and importance
  1396. // Remove redundant statements (ie: our teammates already said them)
  1397. //
  1398. const BotStatement *friendSay = GetActiveStatement();
  1399. if (friendSay && friendSay->GetOwner() == m_me)
  1400. friendSay = NULL;
  1401. BotStatement *nextSay;
  1402. for( say = m_statementList; say; say = nextSay )
  1403. {
  1404. nextSay = say->m_next;
  1405. // check statement conditions
  1406. if (!say->IsValid())
  1407. {
  1408. RemoveStatement( say );
  1409. continue;
  1410. }
  1411. // don't interrupt ourselves
  1412. if (say->IsSpeaking())
  1413. continue;
  1414. // check for obsolete statements
  1415. if (say->IsObsolete())
  1416. {
  1417. m_me->PrintIfWatched( "Statement obsolete - removing.\n" );
  1418. RemoveStatement( say );
  1419. continue;
  1420. }
  1421. // if a teammate is saying what we were going to say, dont repeat it
  1422. if (friendSay)
  1423. {
  1424. // convert what we're about to say based on what our teammate is currently saying
  1425. say->Convert( friendSay );
  1426. // don't say things our teammates have just said
  1427. if (say->IsRedundant( friendSay ))
  1428. {
  1429. // thie statement is redundant - destroy it
  1430. //m_me->PrintIfWatched( "Teammate said what I was going to say - shutting up.\n" );
  1431. m_me->PrintIfWatched( "Teammate said what I was going to say - shutting up.\n" );
  1432. RemoveStatement( say );
  1433. }
  1434. }
  1435. }
  1436. }
  1437. //---------------------------------------------------------------------------------------------------------------
  1438. /**
  1439. * Returns the statement that is being spoken, or is next to be spoken if no-one is speaking now
  1440. */
  1441. BotStatement *BotChatterInterface::GetActiveStatement( void )
  1442. {
  1443. // keep track of statement waiting longest to be spoken - it is next
  1444. BotStatement *earliest = NULL;
  1445. float earlyTime = 999999999.9f;
  1446. for( int i = 1; i <= gpGlobals->maxClients; i++ )
  1447. {
  1448. CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
  1449. if (player == NULL)
  1450. continue;
  1451. // ignore dead humans
  1452. if (!player->IsBot() && !player->IsAlive())
  1453. continue;
  1454. // ignore enemies, since we can't hear them talk
  1455. if (!m_me->InSameTeam( player ))
  1456. continue;
  1457. CCSBot *bot = dynamic_cast<CCSBot *>(player);
  1458. // if not a bot, fail the test
  1459. /// @todo Check if human is currently talking
  1460. if (!bot)
  1461. continue;
  1462. for( BotStatement *say = bot->GetChatter()->m_statementList; say; say = say->m_next )
  1463. {
  1464. // if this statement is currently being spoken, return it
  1465. if (say->IsSpeaking())
  1466. return say;
  1467. // keep track of statement that has been waiting longest to be spoken of anyone on our team
  1468. if (say->GetStartTime() < earlyTime)
  1469. {
  1470. earlyTime = say->GetTimestamp();
  1471. earliest = say;
  1472. }
  1473. }
  1474. }
  1475. // make sure it is time to start this statement
  1476. if (earliest && earliest->GetStartTime() > gpGlobals->curtime)
  1477. return NULL;
  1478. return earliest;
  1479. }
  1480. /**
  1481. * Return true if we speaking makes sense now
  1482. */
  1483. bool BotChatterInterface::ShouldSpeak( void ) const
  1484. {
  1485. // don't talk to non-existent friends
  1486. if (m_me->GetFriendsRemaining() == 0)
  1487. return false;
  1488. // if everyone is together, no need to tell them what's going on
  1489. if (m_me->GetNearbyFriendCount() == m_me->GetFriendsRemaining())
  1490. return false;
  1491. return true;
  1492. }
  1493. //---------------------------------------------------------------------------------------------------------------
  1494. float BotChatterInterface::GetRadioSilenceDuration( void )
  1495. {
  1496. return m_radioSilenceInterval[ m_me->GetTeamNumber() % 2 ].GetElapsedTime();
  1497. }
  1498. //---------------------------------------------------------------------------------------------------------------
  1499. void BotChatterInterface::ResetRadioSilenceDuration( void )
  1500. {
  1501. m_radioSilenceInterval[ m_me->GetTeamNumber() % 2 ].Reset();
  1502. }
  1503. //---------------------------------------------------------------------------------------------------------------
  1504. inline const BotPhrase *GetPlacePhrase( CCSBot *me )
  1505. {
  1506. Place place = me->GetPlace();
  1507. if (place != UNDEFINED_PLACE)
  1508. return TheBotPhrases->GetPlace( place );
  1509. return NULL;
  1510. }
  1511. inline void SayWhere( BotStatement *say, Place place )
  1512. {
  1513. say->AppendPhrase( TheBotPhrases->GetPlace( place ) );
  1514. }
  1515. /**
  1516. * Report enemy sightings
  1517. */
  1518. void BotChatterInterface::EnemySpotted( void )
  1519. {
  1520. // NOTE: This could be a few seconds out of date (enemy is in an adjacent place)
  1521. Place place = m_me->GetEnemyPlace();
  1522. BotStatement *say = new BotStatement( this, REPORT_VISIBLE_ENEMIES, 10.0f );
  1523. // where are the enemies
  1524. say->AppendPhrase( TheBotPhrases->GetPlace( place ) );
  1525. // how many are there
  1526. say->AppendPhrase( BotStatement::ACCUMULATE_ENEMIES_DELAY );
  1527. say->AppendPhrase( BotStatement::CURRENT_ENEMY_COUNT );
  1528. say->AddCondition( BotStatement::IS_IN_COMBAT );
  1529. AddStatement( say );
  1530. }
  1531. //---------------------------------------------------------------------------------------------------------------
  1532. /**
  1533. * If a friend warned of snipers, don't warn again for awhile
  1534. */
  1535. void BotChatterInterface::FriendSpottedSniper( void )
  1536. {
  1537. m_warnSniperTimer.Start( 60.0f );
  1538. }
  1539. //---------------------------------------------------------------------------------------------------------------
  1540. /**
  1541. * Warn of an enemy sniper
  1542. */
  1543. void BotChatterInterface::SpottedSniper( void )
  1544. {
  1545. if (!m_warnSniperTimer.IsElapsed())
  1546. {
  1547. return;
  1548. }
  1549. if (m_me->GetFriendsRemaining() == 0)
  1550. {
  1551. // no-one to warn
  1552. return;
  1553. }
  1554. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
  1555. say->AppendPhrase( TheBotPhrases->GetPhrase( "SniperWarning" ) );
  1556. say->AttachMeme( new BotWarnSniperMeme() );
  1557. AddStatement( say );
  1558. }
  1559. //---------------------------------------------------------------------------------------------------------------
  1560. void BotChatterInterface::Clear( Place place )
  1561. {
  1562. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
  1563. SayWhere( say, place );
  1564. say->AppendPhrase( TheBotPhrases->GetPhrase( "Clear" ) );
  1565. AddStatement( say );
  1566. }
  1567. //---------------------------------------------------------------------------------------------------------------
  1568. /**
  1569. * Request enemy activity report
  1570. */
  1571. void BotChatterInterface::ReportIn( void )
  1572. {
  1573. BotStatement *say = new BotStatement( this, REPORT_REQUEST_INFORMATION, 10.0f );
  1574. say->AppendPhrase( TheBotPhrases->GetPhrase( "RequestReport" ) );
  1575. say->AddCondition( BotStatement::RADIO_SILENCE );
  1576. say->AttachMeme( new BotRequestReportMeme() );
  1577. AddStatement( say );
  1578. }
  1579. //---------------------------------------------------------------------------------------------------------------
  1580. /**
  1581. * Report our situtation
  1582. */
  1583. void BotChatterInterface::ReportingIn( void )
  1584. {
  1585. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
  1586. // where are we
  1587. Place place = m_me->GetPlace();
  1588. SayWhere( say, place );
  1589. // what are we doing
  1590. switch( m_me->GetTask() )
  1591. {
  1592. case CCSBot::PLANT_BOMB:
  1593. {
  1594. m_me->GetChatter()->GoingToPlantTheBomb( UNDEFINED_PLACE );
  1595. break;
  1596. }
  1597. case CCSBot::DEFUSE_BOMB:
  1598. {
  1599. m_me->GetChatter()->Say( "DefusingBomb" );
  1600. break;
  1601. }
  1602. case CCSBot::GUARD_LOOSE_BOMB:
  1603. {
  1604. if (TheCSBots()->GetLooseBomb())
  1605. {
  1606. say->AppendPhrase( TheBotPhrases->GetPhrase( "GuardingLooseBomb" ) );
  1607. say->AttachMeme( new BotBombStatusMeme( CSGameState::LOOSE, TheCSBots()->GetLooseBomb()->GetAbsOrigin() ) );
  1608. }
  1609. break;
  1610. }
  1611. case CCSBot::GUARD_HOSTAGES:
  1612. {
  1613. m_me->GetChatter()->GuardingHostages( UNDEFINED_PLACE, !m_me->IsAtHidingSpot() );
  1614. break;
  1615. }
  1616. case CCSBot::GUARD_HOSTAGE_RESCUE_ZONE:
  1617. {
  1618. m_me->GetChatter()->GuardingHostageEscapeZone( !m_me->IsAtHidingSpot() );
  1619. break;
  1620. }
  1621. case CCSBot::COLLECT_HOSTAGES:
  1622. {
  1623. break;
  1624. }
  1625. case CCSBot::RESCUE_HOSTAGES:
  1626. {
  1627. m_me->GetChatter()->EscortingHostages();
  1628. break;
  1629. }
  1630. case CCSBot::GUARD_VIP_ESCAPE_ZONE:
  1631. {
  1632. break;
  1633. }
  1634. }
  1635. // what do we see
  1636. if (m_me->IsAttacking())
  1637. {
  1638. if (m_me->IsOutnumbered())
  1639. {
  1640. // in trouble in a firefight
  1641. say->AppendPhrase( TheBotPhrases->GetPhrase( "Help" ) );
  1642. say->AttachMeme( new BotHelpMeme( place ) );
  1643. }
  1644. else
  1645. {
  1646. // battling enemies
  1647. say->AppendPhrase( TheBotPhrases->GetPhrase( "InCombat" ) );
  1648. }
  1649. }
  1650. else
  1651. {
  1652. // not in combat, start our report a little later
  1653. say->SetStartTime( gpGlobals->curtime + 2.0f );
  1654. const float recentTime = 10.0f;
  1655. if (m_me->GetEnemyDeathTimestamp() < recentTime && m_me->GetEnemyDeathTimestamp() >= m_me->GetTimeSinceLastSawEnemy() + 0.5f)
  1656. {
  1657. // recently saw an enemy die
  1658. say->AppendPhrase( TheBotPhrases->GetPhrase( "EnemyDown" ) );
  1659. }
  1660. else if (m_me->GetTimeSinceLastSawEnemy() < recentTime)
  1661. {
  1662. // recently saw an enemy
  1663. say->AppendPhrase( TheBotPhrases->GetPhrase( "EnemySpotted" ) );
  1664. }
  1665. else
  1666. {
  1667. // haven't seen enemies
  1668. say->AppendPhrase( TheBotPhrases->GetPhrase( "Clear" ) );
  1669. }
  1670. }
  1671. AddStatement( say );
  1672. }
  1673. //---------------------------------------------------------------------------------------------------------------
  1674. bool BotChatterInterface::NeedBackup( void )
  1675. {
  1676. const float minRequestInterval = 10.0f;
  1677. if (m_needBackupInterval.IsLessThen( minRequestInterval ))
  1678. return false;
  1679. m_needBackupInterval.Reset();
  1680. if (m_me->GetFriendsRemaining() == 0)
  1681. {
  1682. // we're all alone...
  1683. Scared();
  1684. return true;
  1685. }
  1686. else
  1687. {
  1688. // ask friends for help
  1689. BotStatement *say = new BotStatement( this, REPORT_REQUEST_HELP, 10.0f );
  1690. // where are we
  1691. Place place = m_me->GetPlace();
  1692. SayWhere( say, place );
  1693. say->AppendPhrase( TheBotPhrases->GetPhrase( "Help" ) );
  1694. say->AttachMeme( new BotHelpMeme( place ) );
  1695. AddStatement( say );
  1696. }
  1697. return true;
  1698. }
  1699. //---------------------------------------------------------------------------------------------------------------
  1700. void BotChatterInterface::PinnedDown( void )
  1701. {
  1702. // this is a form of "need backup"
  1703. const float minRequestInterval = 10.0f;
  1704. if (m_needBackupInterval.IsLessThen( minRequestInterval ))
  1705. return;
  1706. m_needBackupInterval.Reset();
  1707. BotStatement *say = new BotStatement( this, REPORT_REQUEST_HELP, 10.0f );
  1708. // where are we
  1709. Place place = m_me->GetPlace();
  1710. SayWhere( say, place );
  1711. say->AppendPhrase( TheBotPhrases->GetPhrase( "PinnedDown" ) );
  1712. say->AttachMeme( new BotHelpMeme( place ) );
  1713. say->AddCondition( BotStatement::IS_IN_COMBAT );
  1714. AddStatement( say );
  1715. }
  1716. //---------------------------------------------------------------------------------------------------------------
  1717. /**
  1718. * If a friend said that they heard something, we don't want to say something similar
  1719. * for a while.
  1720. */
  1721. void BotChatterInterface::FriendHeardNoise( void )
  1722. {
  1723. m_heardNoiseTimer.Start( 20.0f );
  1724. }
  1725. //---------------------------------------------------------------------------------------------------------------
  1726. void BotChatterInterface::HeardNoise( const Vector &pos )
  1727. {
  1728. if (TheCSBots()->IsRoundOver())
  1729. return;
  1730. if (m_heardNoiseTimer.IsElapsed())
  1731. {
  1732. // throttle frequency
  1733. m_heardNoiseTimer.Start( 20.0f );
  1734. // make rare, since many teammates may try to say this
  1735. if (RandomFloat( 0, 100 ) < 33)
  1736. {
  1737. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 5.0f );
  1738. say->AppendPhrase( TheBotPhrases->GetPhrase( "HeardNoise" ) );
  1739. say->SetPlace( TheNavMesh->GetPlace( pos ) );
  1740. say->AttachMeme( new BotHeardNoiseMeme() );
  1741. AddStatement( say );
  1742. }
  1743. }
  1744. }
  1745. //---------------------------------------------------------------------------------------------------------------
  1746. void BotChatterInterface::KilledMyEnemy( int victimID )
  1747. {
  1748. // only report if we killed the last enemy in the area
  1749. if (m_me->GetNearbyEnemyCount() <= 1)
  1750. return;
  1751. BotStatement *say = new BotStatement( this, REPORT_ENEMY_ACTION, 3.0f );
  1752. say->AppendPhrase( TheBotPhrases->GetPhrase( "KilledMyEnemy" ) );
  1753. say->SetSubject( victimID );
  1754. AddStatement( say );
  1755. }
  1756. //---------------------------------------------------------------------------------------------------------------
  1757. void BotChatterInterface::EnemiesRemaining( void )
  1758. {
  1759. // only report if we killed the last enemy in the area
  1760. if (m_me->GetNearbyEnemyCount() > 1)
  1761. return;
  1762. BotStatement *say = new BotStatement( this, REPORT_ENEMIES_REMAINING, 5.0f );
  1763. say->AppendPhrase( BotStatement::REMAINING_ENEMY_COUNT );
  1764. say->SetStartTime( gpGlobals->curtime + RandomFloat( 2.0f, 4.0f ) );
  1765. AddStatement( say );
  1766. }
  1767. //---------------------------------------------------------------------------------------------------------------
  1768. void BotChatterInterface::Affirmative( void )
  1769. {
  1770. BotStatement *say = new BotStatement( this, REPORT_ACKNOWLEDGE, 3.0f );
  1771. say->AppendPhrase( TheBotPhrases->GetPhrase( "Affirmative" ) );
  1772. AddStatement( say );
  1773. }
  1774. //---------------------------------------------------------------------------------------------------------------
  1775. void BotChatterInterface::Negative( void )
  1776. {
  1777. BotStatement *say = new BotStatement( this, REPORT_ACKNOWLEDGE, 3.0f );
  1778. say->AppendPhrase( TheBotPhrases->GetPhrase( "Negative" ) );
  1779. AddStatement( say );
  1780. }
  1781. //---------------------------------------------------------------------------------------------------------------
  1782. void BotChatterInterface::GoingToPlantTheBomb( Place place )
  1783. {
  1784. if (TheCSBots()->IsRoundOver())
  1785. return;
  1786. const float minInterval = 20.0f;
  1787. if (m_planInterval.IsLessThen( minInterval ))
  1788. return;
  1789. m_planInterval.Reset();
  1790. BotStatement *say = new BotStatement( this, REPORT_CRITICAL_EVENT, 10.0f );
  1791. say->AppendPhrase( TheBotPhrases->GetPhrase( "GoingToPlantBomb" ) );
  1792. say->SetPlace( place );
  1793. say->AttachMeme( new BotFollowMeme() );
  1794. AddStatement( say );
  1795. }
  1796. //---------------------------------------------------------------------------------------------------------------
  1797. void BotChatterInterface::PlantingTheBomb( Place place )
  1798. {
  1799. if (TheCSBots()->IsRoundOver())
  1800. return;
  1801. BotStatement *say = new BotStatement( this, REPORT_CRITICAL_EVENT, 10.0f );
  1802. say->AppendPhrase( TheBotPhrases->GetPhrase( "PlantingBomb" ) );
  1803. say->SetPlace( place );
  1804. Vector myOrigin = GetCentroid( m_me );
  1805. say->AttachMeme( new BotDefendHereMeme( myOrigin ) );
  1806. AddStatement( say );
  1807. }
  1808. //---------------------------------------------------------------------------------------------------------------
  1809. void BotChatterInterface::TheyPickedUpTheBomb( void )
  1810. {
  1811. if (TheCSBots()->IsRoundOver())
  1812. return;
  1813. // if we already know the bomb is not loose, this is old news
  1814. if (!m_me->GetGameState()->IsBombLoose())
  1815. return;
  1816. // update our gamestate - use our own position for now
  1817. const Vector &myOrigin = GetCentroid( m_me );
  1818. m_me->GetGameState()->UpdateBomber( myOrigin );
  1819. // tell our teammates
  1820. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
  1821. say->AppendPhrase( TheBotPhrases->GetPhrase( "TheyPickedUpTheBomb" ) );
  1822. say->AttachMeme( new BotBombStatusMeme( CSGameState::MOVING, myOrigin ) );
  1823. AddStatement( say );
  1824. }
  1825. //---------------------------------------------------------------------------------------------------------------
  1826. void BotChatterInterface::SpottedBomber( CBasePlayer *bomber )
  1827. {
  1828. const Vector &bomberOrigin = GetCentroid( bomber );
  1829. if (m_me->GetGameState()->IsBombMoving())
  1830. {
  1831. // if we knew where the bomber was, this is old news
  1832. const Vector *bomberPos = m_me->GetGameState()->GetBombPosition();
  1833. const float closeRangeSq = 1000.0f * 1000.0f;
  1834. if (bomberPos && (bomberOrigin - *bomberPos).LengthSqr() < closeRangeSq)
  1835. return;
  1836. }
  1837. // update our gamestate
  1838. m_me->GetGameState()->UpdateBomber( bomberOrigin );
  1839. // tell our teammates
  1840. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
  1841. // where is the bomber
  1842. Place place = TheNavMesh->GetPlace( bomberOrigin );
  1843. SayWhere( say, place );
  1844. say->AppendPhrase( TheBotPhrases->GetPhrase( "SpottedBomber" ) );
  1845. say->SetSubject( bomber->entindex() );
  1846. //say->AttachMeme( new BotHelpMeme( place ) );
  1847. say->AttachMeme( new BotBombStatusMeme( CSGameState::MOVING, bomberOrigin ) );
  1848. AddStatement( say );
  1849. }
  1850. //---------------------------------------------------------------------------------------------------------------
  1851. void BotChatterInterface::SpottedLooseBomb( CBaseEntity *bomb )
  1852. {
  1853. if (TheCSBots()->IsRoundOver())
  1854. return;
  1855. // if we already know the bomb is loose, this is old news
  1856. if (m_me->GetGameState()->IsBombLoose())
  1857. return;
  1858. // update our gamestate
  1859. m_me->GetGameState()->UpdateLooseBomb( bomb->GetAbsOrigin() );
  1860. if (m_spottedLooseBombTimer.IsElapsed())
  1861. {
  1862. // throttle frequency
  1863. m_spottedLooseBombTimer.Start( 10.0f );
  1864. // tell our teammates
  1865. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
  1866. // where is the bomb
  1867. Place place = TheNavMesh->GetPlace( bomb->GetAbsOrigin() );
  1868. SayWhere( say, place );
  1869. say->AppendPhrase( TheBotPhrases->GetPhrase( "SpottedLooseBomb" ) );
  1870. if (TheCSBots()->GetLooseBomb())
  1871. say->AttachMeme( new BotBombStatusMeme( CSGameState::LOOSE, bomb->GetAbsOrigin() ) );
  1872. AddStatement( say );
  1873. }
  1874. }
  1875. //---------------------------------------------------------------------------------------------------------------
  1876. void BotChatterInterface::GuardingLooseBomb( CBaseEntity *bomb )
  1877. {
  1878. // if we already know the bomb is loose, this is old news
  1879. // if (m_me->GetGameState()->IsBombLoose())
  1880. // return;
  1881. if (TheCSBots()->IsRoundOver() || !bomb)
  1882. return;
  1883. const float minInterval = 20.0f;
  1884. if (m_planInterval.IsLessThen( minInterval ))
  1885. return;
  1886. m_planInterval.Reset();
  1887. // update our gamestate
  1888. m_me->GetGameState()->UpdateLooseBomb( bomb->GetAbsOrigin() );
  1889. // tell our teammates
  1890. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
  1891. // where is the bomb
  1892. Place place = TheNavMesh->GetPlace( bomb->GetAbsOrigin() );
  1893. SayWhere( say, place );
  1894. say->AppendPhrase( TheBotPhrases->GetPhrase( "GuardingLooseBomb" ) );
  1895. if (TheCSBots()->GetLooseBomb())
  1896. say->AttachMeme( new BotBombStatusMeme( CSGameState::LOOSE, bomb->GetAbsOrigin() ) );
  1897. AddStatement( say );
  1898. }
  1899. //---------------------------------------------------------------------------------------------------------------
  1900. void BotChatterInterface::RequestBombLocation( void )
  1901. {
  1902. // only ask once per round
  1903. if (m_requestedBombLocation)
  1904. return;
  1905. m_requestedBombLocation = true;
  1906. // tell our teammates
  1907. BotStatement *say = new BotStatement( this, REPORT_REQUEST_INFORMATION, 10.0f );
  1908. say->AppendPhrase( TheBotPhrases->GetPhrase( "WhereIsTheBomb" ) );
  1909. say->AttachMeme( new BotWhereBombMeme() );
  1910. AddStatement( say );
  1911. }
  1912. //---------------------------------------------------------------------------------------------------------------
  1913. void BotChatterInterface::BombsiteClear( int zoneIndex )
  1914. {
  1915. const CCSBotManager::Zone *zone = TheCSBots()->GetZone( zoneIndex );
  1916. if (zone == NULL)
  1917. return;
  1918. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 10.0f );
  1919. SayWhere( say, TheNavMesh->GetPlace( zone->m_center ) );
  1920. say->AppendPhrase( TheBotPhrases->GetPhrase( "BombsiteClear" ) );
  1921. say->AttachMeme( new BotBombsiteStatusMeme( zoneIndex, BotBombsiteStatusMeme::CLEAR ) );
  1922. AddStatement( say );
  1923. }
  1924. //---------------------------------------------------------------------------------------------------------------
  1925. void BotChatterInterface::FoundPlantedBomb( int zoneIndex )
  1926. {
  1927. const CCSBotManager::Zone *zone = TheCSBots()->GetZone( zoneIndex );
  1928. if (zone == NULL)
  1929. return;
  1930. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 3.0f );
  1931. say->AppendPhrase( TheBotPhrases->GetPhrase( "PlantedBombPlace" ) );
  1932. say->SetPlace( TheNavMesh->GetPlace( zone->m_center ) );
  1933. say->AttachMeme( new BotBombsiteStatusMeme( zoneIndex, BotBombsiteStatusMeme::PLANTED ) );
  1934. AddStatement( say );
  1935. }
  1936. //---------------------------------------------------------------------------------------------------------------
  1937. void BotChatterInterface::Scared( void )
  1938. {
  1939. const float minInterval = 10.0f;
  1940. if (m_scaredInterval.IsLessThen( minInterval ))
  1941. return;
  1942. m_scaredInterval.Reset();
  1943. BotStatement *say = new BotStatement( this, REPORT_EMOTE, 1.0f );
  1944. say->AppendPhrase( TheBotPhrases->GetPhrase( "ScaredEmote" ) );
  1945. say->AddCondition( BotStatement::IS_IN_COMBAT );
  1946. AddStatement( say );
  1947. }
  1948. //---------------------------------------------------------------------------------------------------------------
  1949. void BotChatterInterface::CelebrateWin( void )
  1950. {
  1951. BotStatement *say = new BotStatement( this, REPORT_EMOTE, 15.0f );
  1952. // wait a bit before speaking
  1953. say->SetStartTime( gpGlobals->curtime + RandomFloat( 2.0f, 5.0f ) );
  1954. const float quickRound = 45.0f;
  1955. if (m_me->GetFriendsRemaining() == 0)
  1956. {
  1957. // we were the last man standing
  1958. if (TheCSBots()->GetElapsedRoundTime() < quickRound)
  1959. say->AppendPhrase( TheBotPhrases->GetPhrase( "WonRoundQuickly" ) );
  1960. else if (RandomFloat( 0.0f, 100.0f ) < 33.3f)
  1961. say->AppendPhrase( TheBotPhrases->GetPhrase( "LastManStanding" ) );
  1962. }
  1963. else
  1964. {
  1965. if (TheCSBots()->GetElapsedRoundTime() < quickRound)
  1966. {
  1967. if (RandomFloat( 0.0f, 100.0f ) < 33.3f)
  1968. say->AppendPhrase( TheBotPhrases->GetPhrase( "WonRoundQuickly" ) );
  1969. }
  1970. else if (RandomFloat( 0.0f, 100.0f ) < 10.0f)
  1971. {
  1972. say->AppendPhrase( TheBotPhrases->GetPhrase( "WonRound" ) );
  1973. }
  1974. }
  1975. AddStatement( say );
  1976. }
  1977. //---------------------------------------------------------------------------------------------------------------
  1978. void BotChatterInterface::AnnouncePlan( const char *phraseName, Place place )
  1979. {
  1980. if (TheCSBots()->IsRoundOver())
  1981. return;
  1982. BotStatement *say = new BotStatement( this, REPORT_MY_PLAN, 10.0f );
  1983. say->AppendPhrase( TheBotPhrases->GetPhrase( phraseName ) );
  1984. say->SetPlace( place );
  1985. // wait at least a short time after round start
  1986. say->SetStartTime( TheCSBots()->GetRoundStartTime() + RandomFloat( 2.0, 3.0f ) );
  1987. AddStatement( say );
  1988. }
  1989. //---------------------------------------------------------------------------------------------------------------
  1990. void BotChatterInterface::GuardingBombsite( Place place )
  1991. {
  1992. if (TheCSBots()->IsRoundOver())
  1993. return;
  1994. const float minInterval = 20.0f;
  1995. if (m_planInterval.IsLessThen( minInterval ))
  1996. return;
  1997. m_planInterval.Reset();
  1998. AnnouncePlan( "GoingToDefendBombsite", place );
  1999. }
  2000. //---------------------------------------------------------------------------------------------------------------
  2001. void BotChatterInterface::GuardingHostages( Place place, bool isPlan )
  2002. {
  2003. if (TheCSBots()->IsRoundOver())
  2004. return;
  2005. const float minInterval = 20.0f;
  2006. if (m_planInterval.IsLessThen( minInterval ))
  2007. return;
  2008. m_planInterval.Reset();
  2009. if (isPlan)
  2010. AnnouncePlan( "GoingToGuardHostages", place );
  2011. else
  2012. Say( "GuardingHostages" );
  2013. }
  2014. //---------------------------------------------------------------------------------------------------------------
  2015. void BotChatterInterface::GuardingHostageEscapeZone( bool isPlan )
  2016. {
  2017. if (TheCSBots()->IsRoundOver())
  2018. return;
  2019. const float minInterval = 20.0f;
  2020. if (m_planInterval.IsLessThen( minInterval ))
  2021. return;
  2022. m_planInterval.Reset();
  2023. if (isPlan)
  2024. AnnouncePlan( "GoingToGuardHostageEscapeZone", UNDEFINED_PLACE );
  2025. else
  2026. Say( "GuardingHostageEscapeZone" );
  2027. }
  2028. //---------------------------------------------------------------------------------------------------------------
  2029. void BotChatterInterface::HostagesBeingTaken( void )
  2030. {
  2031. if (TheCSBots()->IsRoundOver())
  2032. return;
  2033. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 3.0f );
  2034. say->AppendPhrase( TheBotPhrases->GetPhrase( "HostagesBeingTaken" ) );
  2035. say->AttachMeme( new BotHostageBeingTakenMeme() );
  2036. AddStatement( say );
  2037. }
  2038. //---------------------------------------------------------------------------------------------------------------
  2039. void BotChatterInterface::HostagesTaken( void )
  2040. {
  2041. if (TheCSBots()->IsRoundOver())
  2042. return;
  2043. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 3.0f );
  2044. say->AppendPhrase( TheBotPhrases->GetPhrase( "HostagesTaken" ) );
  2045. AddStatement( say );
  2046. }
  2047. //---------------------------------------------------------------------------------------------------------------
  2048. void BotChatterInterface::TalkingToHostages( void )
  2049. {
  2050. }
  2051. //---------------------------------------------------------------------------------------------------------------
  2052. void BotChatterInterface::EscortingHostages( void )
  2053. {
  2054. if (TheCSBots()->IsRoundOver())
  2055. return;
  2056. if (m_escortingHostageTimer.IsElapsed())
  2057. {
  2058. // throttle frequency
  2059. m_escortingHostageTimer.Start( 10.0f );
  2060. BotStatement *say = new BotStatement( this, REPORT_MY_PLAN, 5.0f );
  2061. say->AppendPhrase( TheBotPhrases->GetPhrase( "EscortingHostages" ) );
  2062. AddStatement( say );
  2063. }
  2064. }
  2065. //---------------------------------------------------------------------------------------------------------------
  2066. void BotChatterInterface::HostageDown( void )
  2067. {
  2068. if (TheCSBots()->IsRoundOver())
  2069. return;
  2070. BotStatement *say = new BotStatement( this, REPORT_INFORMATION, 3.0f );
  2071. say->AppendPhrase( TheBotPhrases->GetPhrase( "HostageDown" ) );
  2072. AddStatement( say );
  2073. }
  2074. //---------------------------------------------------------------------------------------------------------------
  2075. void BotChatterInterface::Encourage( const char *phraseName, float repeatInterval, float lifetime )
  2076. {
  2077. if (m_encourageTimer.IsElapsed())
  2078. {
  2079. Say( phraseName, lifetime );
  2080. m_encourageTimer.Start( repeatInterval );
  2081. }
  2082. }
  2083. //---------------------------------------------------------------------------------------------------------------
  2084. void BotChatterInterface::KilledFriend( void )
  2085. {
  2086. BotStatement *say = new BotStatement( this, REPORT_KILLED_FRIEND, 2.0f );
  2087. say->AppendPhrase( TheBotPhrases->GetPhrase( "KilledFriend" ) );
  2088. // give them time to react
  2089. say->SetStartTime( gpGlobals->curtime + RandomFloat( 0.5f, 1.0f ) );
  2090. AddStatement( say );
  2091. }
  2092. //---------------------------------------------------------------------------------------------------------------
  2093. void BotChatterInterface::FriendlyFire( void )
  2094. {
  2095. if ( !friendlyfire.GetBool() )
  2096. return;
  2097. BotStatement *say = new BotStatement( this, REPORT_FRIENDLY_FIRE, 1.0f );
  2098. say->AppendPhrase( TheBotPhrases->GetPhrase( "FriendlyFire" ) );
  2099. // give them time to react
  2100. say->SetStartTime( gpGlobals->curtime + RandomFloat( 0.3f, 0.5f ) );
  2101. AddStatement( say );
  2102. }