Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

511 lines
17 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "ai_speech.h"
  9. #include "game.h"
  10. #include "eventqueue.h"
  11. #include "ai_basenpc.h"
  12. #include "basemultiplayerplayer.h"
  13. #include "ai_baseactor.h"
  14. //#include "flex_expresser.h"
  15. #include "scenefilecache/ISceneFileCache.h"
  16. /*
  17. #include "engine/IEngineSound.h"
  18. #include "keyvalues.h"
  19. #include "ai_criteria.h"
  20. #include "isaverestore.h"
  21. #include "sceneentity.h"
  22. */
  23. // memdbgon must be the last include file in a .cpp file!!!
  24. #include <tier0/memdbgon.h>
  25. static const char *GetResponseName( CBaseEntity *pEnt )
  26. {
  27. Assert( pEnt );
  28. if ( pEnt == NULL )
  29. return "";
  30. return STRING( pEnt->GetEntityName() );
  31. }
  32. // This is a tiny helper function for below -- what I'd use a lambda for, usually
  33. static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity *pSpeaker, CBaseEntity *pRespondent, AI_ResponseFollowup &followup )
  34. {
  35. AssertMsg(pSpeaker != NULL, "Response expressor somehow got called with a NULL Outer.\n");
  36. if ( !pRespondent )
  37. {
  38. return;
  39. }
  40. float delay = followup.followup_delay;
  41. if (pSpeaker == pRespondent && delay < 0)
  42. {
  43. Warning("Response rule with a 'self' target specified negative delay, which isn't legal because that would make someone talk over himself.");
  44. delay = 0;
  45. }
  46. // Msg( "%s: Dispatch comeback about %s to %s\n", pSpeaker->GetBotString(), g_pConceptManager->GetTopicName( handle ), pRespondent->GetBotString() );
  47. // build an input event that we will use to force the bot to talk through the IO system
  48. variant_t value;
  49. // Don't send along null contexts
  50. if (followup.followup_contexts && followup.followup_contexts[0] != '\0')
  51. {
  52. value.SetString( MAKE_STRING( followup.followup_contexts ) );
  53. g_EventQueue.AddEvent( pRespondent, "AddContext", value, delay - 0.01, pSpeaker, pSpeaker );
  54. }
  55. /*
  56. value.SetString(MAKE_STRING(followup.followup_concept));
  57. g_EventQueue.AddEvent( pRespondent, "SpeakResponseConcept", value, delay , pSpeaker, pSpeaker );
  58. */
  59. AI_CriteriaSet criteria;
  60. // add in the FROM context so dispatchee knows was from me
  61. const char * RESTRICT pszSpeakerName = GetResponseName( pSpeaker );
  62. criteria.AppendCriteria( "From", pszSpeakerName );
  63. // if a SUBJECT criteria is missing, put it back in.
  64. if ( criteria.FindCriterionIndex( "Subject" ) == -1 )
  65. {
  66. criteria.AppendCriteria( "Subject", pszSpeakerName );
  67. }
  68. // add in any provided contexts from the parameters onto the ones stored in the followup
  69. criteria.Merge( followup.followup_contexts );
  70. // This is kludgy and needs to be fixed in class hierarchy, but for now, try to guess at the most likely
  71. // kinds of targets and dispatch to them.
  72. if (CBaseMultiplayerPlayer *pPlayer = dynamic_cast<CBaseMultiplayerPlayer *>(pRespondent))
  73. {
  74. pPlayer->Speak( followup.followup_concept, &criteria );
  75. }
  76. else if (CAI_BaseActor *pActor = dynamic_cast<CAI_BaseActor *>(pRespondent))
  77. {
  78. pActor->Speak( followup.followup_concept, &criteria );
  79. }
  80. }
  81. #if 0
  82. //-----------------------------------------------------------------------------
  83. // Purpose: Placeholder for rules based response system
  84. // Input : concept -
  85. // Output : Returns true on success, false on failure.
  86. //-----------------------------------------------------------------------------
  87. bool CAI_ExpresserWithFollowup::Speak( AIConcept_t &concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /* = NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ )
  88. {
  89. AI_Response *result = SpeakFindResponse( concept, modifiers );
  90. if ( !result )
  91. {
  92. return false;
  93. }
  94. CNPC_CompanionBot *pBot = dynamic_cast<CNPC_CompanionBot *>(GetOuter());
  95. if ( pBot )
  96. {
  97. pBot->SetConversationTopic( g_pConceptManager->GetTopic( handle ) );
  98. pBot->SetLastSpeaker( g_pConceptManager->GetSpeaker( handle ) );
  99. // Msg( "%s: Conversing about %s\n", pBot->GetBotString(), g_pConceptManager->GetTopicName( handle ) );
  100. }
  101. SpeechMsg( GetOuter(), "%s (%x) spoke %s (%f)\n", STRING(GetOuter()->GetEntityName()), GetOuter(), g_pConceptManager->GetConcept( handle ), gpGlobals->curtime );
  102. bool spoke = SpeakDispatchResponse( handle, result, filter );
  103. if ( pszOutResponseChosen )
  104. {
  105. result->GetResponse( pszOutResponseChosen, bufsize );
  106. }
  107. return spoke;
  108. }
  109. #endif
  110. // Work out the character from the "subject" context.
  111. // Right now, this is a simple find by entity name search.
  112. // But you can define arbitrary subject names, like L4D does
  113. // for "biker", "manager", etc.
  114. static CBaseEntity *AscertainSpeechSubjectFromContext( AI_Response *response, AI_CriteriaSet &criteria, const char *pContextName )
  115. {
  116. const char *subject = criteria.GetValue( criteria.FindCriterionIndex( pContextName ) );
  117. if (subject)
  118. {
  119. #if defined( TERROR )
  120. // try looking up "namvet", etc. if not one of those, then look up by name.
  121. CBaseEntity *pSurvivor = TerrorGetPlayerPointerFromCharacterName(subject);
  122. return pSurvivor ? pSurvivor : gEntList.FindEntityByName( NULL, subject );
  123. #else
  124. return gEntList.FindEntityByName( NULL, subject );
  125. #endif
  126. }
  127. else
  128. {
  129. return NULL;
  130. }
  131. }
  132. // TODO: Currently uses awful stricmp. Use symbols! Once I know which ones we want, that is.
  133. static CResponseQueue::CFollowupTargetSpec_t ResolveFollowupTargetToEntity( AIConcept_t &concept, AI_CriteriaSet &criteria, const char * RESTRICT szTarget, AI_Response * RESTRICT response = NULL )
  134. {
  135. #if defined(TERROR)
  136. SurvivorCharacterType surv;
  137. #endif
  138. if ( Q_stricmp(szTarget, "self") == 0 )
  139. {
  140. return CResponseQueue::CFollowupTargetSpec_t( kDRT_SPECIFIC, concept.GetSpeaker() );
  141. }
  142. else if ( Q_stricmp(szTarget, "subject") == 0 )
  143. {
  144. return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "Subject" ) );
  145. }
  146. else if ( Q_stricmp(szTarget, "from") == 0 )
  147. {
  148. return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "From" ) );
  149. }
  150. else if ( Q_stricmp(szTarget, "any") == 0 )
  151. {
  152. return CResponseQueue::CFollowupTargetSpec_t( kDRT_ANY, concept.GetSpeaker() );
  153. }
  154. else if ( Q_stricmp(szTarget, "all") == 0 )
  155. {
  156. return CResponseQueue::CFollowupTargetSpec_t( kDRT_ALL );
  157. }
  158. #if defined(TERROR)
  159. else if ( ( surv = GetCharacterFromName( szTarget ) ) < NUM_SURVIVOR_CHARACTERS )
  160. {
  161. return CResponseQueue::CFollowupTargetSpec_t( CTerrorPlayer::GetPlayerByCharacter( surv ) );
  162. }
  163. #endif
  164. // last resort, try a named lookup
  165. else if ( CBaseEntity *pSpecific = gEntList.FindEntityByName(NULL, szTarget) ) // it could be anything
  166. {
  167. return CResponseQueue::CFollowupTargetSpec_t( pSpecific );
  168. }
  169. Warning("Couldn't resolve response target %s\n", szTarget );
  170. return CResponseQueue::CFollowupTargetSpec_t(); // couldn't resolve.
  171. }
  172. // TODO: Currently uses awful stricmp. Use symbols! Once I know which ones we want, that is.
  173. static CResponseQueue::CFollowupTargetSpec_t ResolveFollowupTargetToEntity( AIConcept_t &concept, AI_CriteriaSet &criteria, AI_Response * RESTRICT response, AI_ResponseFollowup * RESTRICT followup )
  174. {
  175. const char * RESTRICT szTarget = followup->followup_target;
  176. const CResponseQueue::CFollowupTargetSpec_t INVALID; // default: invalid result
  177. if ( szTarget == NULL )
  178. return INVALID;
  179. else
  180. return ResolveFollowupTargetToEntity( concept, criteria, szTarget, response );
  181. }
  182. ConVar chet_debug_idle( "chet_debug_idle", "0", FCVAR_ARCHIVE, "If set one, many debug prints to help track down the TLK_IDLE issue. Set two for super verbose info" );
  183. // extern ConVar chet_debug_idle;
  184. bool CAI_ExpresserWithFollowup::Speak( AIConcept_t &concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /* = NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ )
  185. {
  186. VPROF("CAI_Expresser::Speak");
  187. if ( IsSpeechGloballySuppressed() )
  188. {
  189. return false;
  190. }
  191. concept.SetSpeaker( GetOuter() );
  192. AI_CriteriaSet criteria;
  193. GatherCriteria(&criteria, concept, modifiers);
  194. GetOuter()->ModifyOrAppendDerivedCriteria(criteria);
  195. AI_Response result;
  196. if ( !FindResponse( result, concept, &criteria ) )
  197. {
  198. if (chet_debug_idle.GetBool())
  199. {
  200. #ifdef TERROR
  201. const char *name;
  202. if ( CTerrorPlayer *pPlayer = ToTerrorPlayer(GetOuter()) )
  203. {
  204. name = SurvivorCharacterName(pPlayer->GetCharacter());
  205. }
  206. else
  207. {
  208. name = GetOuter()->GetDebugName();
  209. }
  210. #else
  211. const char *name = GetOuter()->GetDebugName();
  212. #endif
  213. Msg( "TLK_IDLE: %s did not FindResponse\n", name );
  214. }
  215. return false;
  216. }
  217. else
  218. {
  219. if (chet_debug_idle.GetBool())
  220. {
  221. #ifdef TERROR
  222. const char *name;
  223. if ( CTerrorPlayer *pPlayer = ToTerrorPlayer(GetOuter()) )
  224. {
  225. name = SurvivorCharacterName(pPlayer->GetCharacter());
  226. }
  227. else
  228. {
  229. name = GetOuter()->GetDebugName();
  230. }
  231. #else
  232. const char *name = GetOuter()->GetDebugName();
  233. #endif
  234. Msg( "TLK_IDLE: %s SUCCESSFUL FindResponse\n", name );
  235. }
  236. }
  237. SpeechMsg( GetOuter(), "%s (%x) spoke %s (%f)", STRING(GetOuter()->GetEntityName()), GetOuter(), (const char*)concept, gpGlobals->curtime );
  238. // Msg( "%s:%s to %s:%s\n", GetOuter()->GetDebugName(), concept.GetStringConcept(), criteria.GetValue(criteria.FindCriterionIndex("Subject")), pTarget ? pTarget->GetDebugName() : "none" );
  239. bool spoke = SpeakDispatchResponse( concept, &result, &criteria, filter );
  240. if ( pszOutResponseChosen )
  241. {
  242. result.GetResponse( pszOutResponseChosen, bufsize );
  243. }
  244. return spoke;
  245. }
  246. extern ISceneFileCache *scenefilecache;
  247. static float GetSpeechDurationForResponse( const AI_Response * RESTRICT response )
  248. {
  249. char sceneName[256];
  250. response->GetResponse( sceneName, sizeof(sceneName) );
  251. // look up the scene in the scene cache
  252. SceneCachedData_t scenedata;
  253. bool bHasData = scenefilecache->GetSceneCachedData( sceneName, &scenedata );
  254. AssertMsg1( bHasData, "Couldn't find %s in scene cache.", sceneName );
  255. if ( bHasData )
  256. {
  257. return scenedata.m_fLastSpeakSecs;
  258. }
  259. else
  260. {
  261. return -1;
  262. }
  263. }
  264. //-----------------------------------------------------------------------------
  265. // Purpose: Dispatches the result
  266. // Input : *response -
  267. //-----------------------------------------------------------------------------
  268. bool CAI_ExpresserWithFollowup::SpeakDispatchResponse( AIConcept_t &concept, AI_Response *response, AI_CriteriaSet *criteria, IRecipientFilter *filter )
  269. {
  270. // This gives the chance for the other bot to respond.
  271. if ( !concept.GetSpeaker().IsValid() )
  272. {
  273. concept.SetSpeaker( GetOuter() );
  274. }
  275. bool bInterrupted = IsSpeaking();
  276. bool bSuc = CAI_Expresser::SpeakDispatchResponse( concept, response, criteria, filter );
  277. if (!bSuc)
  278. {
  279. return false;
  280. }
  281. if ( bInterrupted )
  282. {
  283. g_ResponseQueueManager.GetQueue()->RemoveSpeechQueuedFor( GetOuter() );
  284. }
  285. // Record my followup details so that I may defer its use til end of the speech
  286. AI_ResponseFollowup * RESTRICT followup = response->GetParams()->m_pFollowup;
  287. if ( followup )
  288. {
  289. if ( followup->followup_entityiotarget && followup->followup_entityioinput )
  290. if ( criteria )
  291. {
  292. CBaseEntity * RESTRICT pTarget = gEntList.FindEntityByName( NULL, followup->followup_entityiotarget );
  293. if ( pTarget )
  294. {
  295. g_EventQueue.AddEvent( pTarget, followup->followup_entityioinput, variant_t(), followup->followup_entityiodelay, GetOuter(), GetOuter() );
  296. }
  297. }
  298. if ( followup->IsValid() )
  299. {
  300. // 11th hour change: rather than trigger followups from the end of a VCD,
  301. // instead fire it from the end of the last speech event in the VCD, because
  302. // there's a multisecond facial relax delay built into the scene.
  303. // The speech length is stored in the cache, so we can post the followup now.
  304. if ( response->GetType() == ResponseRules::RESPONSE_SCENE &&
  305. followup->followup_delay >= 0 )
  306. {
  307. float fTimeToLastSpeech = GetSpeechDurationForResponse( response );
  308. // failsafe
  309. if ( fTimeToLastSpeech > 0 )
  310. {
  311. DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts,
  312. ResolveFollowupTargetToEntity( concept, *criteria, response, followup ),
  313. fTimeToLastSpeech + followup->followup_delay, GetOuter() );
  314. }
  315. else // error
  316. {
  317. // old way, copied from "else" below
  318. m_pPostponedFollowup = followup;
  319. if ( criteria )
  320. m_followupTarget = ResolveFollowupTargetToEntity( concept, *criteria, response, m_pPostponedFollowup );
  321. else
  322. {
  323. AI_CriteriaSet tmpCriteria;
  324. m_followupTarget = ResolveFollowupTargetToEntity( concept, tmpCriteria, response, m_pPostponedFollowup );
  325. }
  326. }
  327. }
  328. else if ( followup->followup_delay < 0 )
  329. {
  330. // a negative delay has a special meaning. Usually the comeback dispatches after
  331. // the currently said line is finished; the delay is added to that, to provide a
  332. // pause between when character A finishes speaking and B begins.
  333. // A negative delay (-n) actually means "dispatch the comeback n seconds
  334. // after I start talking".
  335. // In this case we do not need to postpone the followup; we just throw it directly
  336. // into the queue.
  337. DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts,
  338. ResolveFollowupTargetToEntity( concept, *criteria, response, followup ),
  339. -followup->followup_delay, GetOuter() );
  340. }
  341. else if ( response->GetType() == ResponseRules::RESPONSE_PRINT )
  342. { // zero-duration responses dispatch immediately via the queue (must be the queue bec.
  343. // the m_pPostponedFollowup will never trigger)
  344. DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts,
  345. ResolveFollowupTargetToEntity( concept, *criteria, response, followup ),
  346. followup->followup_delay, GetOuter() );
  347. }
  348. else
  349. {
  350. // this is kind of a quick patch to immediately deal with the issue of null criteria
  351. // (arose while branching to main) without replumbing a bunch of stuff -- to be fixed
  352. // 5.13.08 egr
  353. m_pPostponedFollowup = followup;
  354. if ( criteria )
  355. m_followupTarget = ResolveFollowupTargetToEntity( concept, *criteria, response, m_pPostponedFollowup );
  356. else
  357. {
  358. AI_CriteriaSet tmpCriteria;
  359. m_followupTarget = ResolveFollowupTargetToEntity( concept, tmpCriteria, response, m_pPostponedFollowup );
  360. }
  361. }
  362. }
  363. }
  364. return bSuc;
  365. }
  366. // This is a gimmick used when a negative delay is specified in a followup, which is a shorthand
  367. // for "this many seconds after the beginning of the line" rather than "this may seconds after the end
  368. // of the line", eg to create a THEN rule when two characters talk over each other.
  369. // It's static to avoid accidental use of the postponed followup/target members.
  370. void CAI_ExpresserWithFollowup::DispatchFollowupThroughQueue( const AIConcept_t &concept,
  371. const char * RESTRICT criteriaStr,
  372. const CResponseQueue::CFollowupTargetSpec_t &target,
  373. float delay,
  374. CBaseEntity * RESTRICT pOuter
  375. )
  376. {
  377. AI_CriteriaSet criteria;
  378. // Don't add my own criteria! GatherCriteria( &criteria, followup.followup_concept, followup.followup_contexts );
  379. #ifdef TERROR
  380. CTerrorPlayer *ptplayer = ToTerrorPlayer(pOuter);
  381. AssertMsg(ptplayer || dynamic_cast<CFlexExpresser *>(pOuter) , "Non-CTerrorPlayer tried to dispatch a followup concept");
  382. if (ptplayer)
  383. {
  384. const char * const RESTRICT survName = GetResponseName( ptplayer );
  385. criteria.AppendCriteria( "Subject", survName );
  386. criteria.AppendCriteria( "From", survName );
  387. }
  388. else
  389. {
  390. criteria.AppendCriteria( "From", STRING( pOuter->GetEntityName() ) );
  391. }
  392. #else
  393. criteria.AppendCriteria( "From", STRING( pOuter->GetEntityName() ) );
  394. #endif
  395. criteria.Merge( criteriaStr );
  396. g_ResponseQueueManager.GetQueue()->Add( concept, &criteria, gpGlobals->curtime + delay, target, pOuter );
  397. }
  398. //-----------------------------------------------------------------------------
  399. // Purpose: Handles the new concept objects
  400. //-----------------------------------------------------------------------------
  401. void CAI_ExpresserWithFollowup::SpeakDispatchFollowup( AI_ResponseFollowup &followup )
  402. {
  403. if ( !m_followupTarget.IsValid() )
  404. return;
  405. #ifdef TERROR
  406. // If a specific entity target is given, use the old pathway for now
  407. if ( m_followupTarget.m_iTargetType == kDRT_SPECIFIC && followup.followup_delay == 0 )
  408. {
  409. CBaseEntity *pTarget = m_followupTarget.m_hHandle.Get();
  410. if (!pTarget)
  411. {
  412. return;
  413. }
  414. DispatchComeback( this, GetOuter(), pTarget, followup );
  415. }
  416. else
  417. #endif
  418. {
  419. DispatchFollowupThroughQueue( followup.followup_concept, followup.followup_contexts, m_followupTarget, followup.followup_delay, GetOuter() );
  420. }
  421. // clear out the followup member just in case.
  422. m_pPostponedFollowup = NULL;
  423. m_followupTarget.m_iTargetType = kDRT_MAX;
  424. }
  425. void CAI_ExpresserWithFollowup::OnSpeechFinished()
  426. {
  427. if (m_pPostponedFollowup && m_pPostponedFollowup->IsValid())
  428. {
  429. return SpeakDispatchFollowup(*m_pPostponedFollowup);
  430. }
  431. }
  432. void CC_RR_ForceConcept_f( const CCommand &args )
  433. {
  434. if ( args.ArgC() < 3 )
  435. {
  436. Msg("USAGE: rr_forceconcept <target> <concept> \"criteria1:value1,criteria2:value2,...\"\n");
  437. return;
  438. }
  439. AI_CriteriaSet criteria;
  440. if ( args.ArgC() >= 3 )
  441. {
  442. const char *criteriastring = args[3];
  443. criteria.Merge( criteriastring );
  444. }
  445. AIConcept_t concept( args[2] );
  446. QueueSpeak( concept, ResolveFollowupTargetToEntity( concept, criteria, args[1] ), criteria );
  447. }
  448. static ConCommand rr_forceconcept( "rr_forceconcept", CC_RR_ForceConcept_f,
  449. "fire a response concept directly at a given character.\n"
  450. "USAGE: rr_forceconcept <target> <concept> \"criteria1:value1,criteria2:value2,...\"\n"
  451. "criteria values are optional.\n"
  452. #ifdef TERROR
  453. "target may also be 'any', 'all', etc."
  454. #endif
  455. , FCVAR_CHEAT );