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.

620 lines
16 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=====================================================================================//
  6. #include "cbase.h"
  7. #include <ctype.h>
  8. #include "tf_autorp.h"
  9. #include "filesystem.h"
  10. // memdbgon must be the last include file in a .cpp file!!!
  11. #include "tier0/memdbgon.h"
  12. //-----------------------------------------------------------------------------
  13. // Purpose:
  14. //-----------------------------------------------------------------------------
  15. CTFAutoRP *AutoRP( void )
  16. {
  17. static CTFAutoRP *pSystem = NULL;
  18. if ( !pSystem )
  19. {
  20. pSystem = new CTFAutoRP();
  21. pSystem->ParseDataFile();
  22. }
  23. return pSystem;
  24. }
  25. //-----------------------------------------------------------------------------
  26. // Purpose:
  27. //-----------------------------------------------------------------------------
  28. void CTFAutoRP::ParseDataFile( void )
  29. {
  30. Assert( !m_pDataFileKV );
  31. // Load & parse the word files
  32. KeyValues *pFileKV = new KeyValues( "AutoRPFile" );
  33. if ( pFileKV->LoadFromFile( filesystem, "scripts/autorp.txt", "MOD" ) == false )
  34. return;
  35. m_pDataFileKV = pFileKV->MakeCopy();
  36. // Prepended word list
  37. KeyValues *pKVPrepended = m_pDataFileKV->FindKey( "prepended_words" );
  38. if ( pKVPrepended )
  39. {
  40. FOR_EACH_SUBKEY( pKVPrepended, pKVKey )
  41. {
  42. m_a_pszPrependedWords.AddToTail( pKVKey->GetName() );
  43. }
  44. }
  45. // Appended word list
  46. KeyValues *pKVAppended = m_pDataFileKV->FindKey( "appended_words" );
  47. if ( pKVAppended )
  48. {
  49. FOR_EACH_SUBKEY( pKVAppended, pKVKey )
  50. {
  51. m_a_pszAppendedWords.AddToTail( pKVKey->GetName() );
  52. }
  53. }
  54. // Word replacements
  55. KeyValues *pKVReplacements = m_pDataFileKV->FindKey( "word_replacements" );
  56. if ( pKVReplacements )
  57. {
  58. FOR_EACH_SUBKEY( pKVReplacements, pKVEntry )
  59. {
  60. int iIdx = m_a_Replacements.AddToTail();
  61. m_a_Replacements[iIdx].iChance = 1;
  62. m_a_Replacements[iIdx].iPrePendCount = 1;
  63. FOR_EACH_SUBKEY( pKVEntry, pKVKey )
  64. {
  65. const char *pszKey = pKVKey->GetName();
  66. const char *pszValue = pKVKey->GetString();
  67. if ( FStrEq(pszKey,"replacement") )
  68. {
  69. m_a_Replacements[iIdx].a_pszReplacements.AddToTail( pszValue );
  70. }
  71. else if ( FStrEq(pszKey,"replacement_prepend") )
  72. {
  73. m_a_Replacements[iIdx].a_pszPrepended.AddToTail( pszValue );
  74. }
  75. else if ( FStrEq(pszKey,"replacement_plural") )
  76. {
  77. m_a_Replacements[iIdx].a_pszPluralReplacements.AddToTail( pszValue );
  78. }
  79. else if ( FStrEq(pszKey,"prepend_count") )
  80. {
  81. m_a_Replacements[iIdx].iPrePendCount = pKVKey->GetInt();
  82. }
  83. else if ( FStrEq(pszKey,"chance") )
  84. {
  85. m_a_Replacements[iIdx].iChance = pKVKey->GetInt();
  86. }
  87. else if ( FStrEq(pszKey,"word") )
  88. {
  89. m_a_Replacements[iIdx].m_Words.AddToTail( m_pWordTable->AddString( pszValue ) );
  90. }
  91. else if ( FStrEq(pszKey,"word_plural") )
  92. {
  93. m_a_Replacements[iIdx].m_Plurals.AddToTail( m_pWordTable->AddString( pszValue ) );
  94. }
  95. else if ( FStrEq(pszKey,"prev") )
  96. {
  97. m_a_Replacements[iIdx].m_PrevWords.AddToTail( m_pWordTable->AddString( pszValue ) );
  98. }
  99. else
  100. {
  101. Assert(0);
  102. }
  103. }
  104. }
  105. }
  106. }
  107. //-----------------------------------------------------------------------------
  108. // Purpose:
  109. //-----------------------------------------------------------------------------
  110. void CTFAutoRP::ApplyRPTo( char *pBuf, int iBufSize )
  111. {
  112. if ( !m_pDataFileKV )
  113. return;
  114. if ( !pBuf || !pBuf[0] )
  115. return;
  116. // Ignore sourceMod commands
  117. if ( pBuf[0] == '!' || pBuf[0] == '/' )
  118. return;
  119. bool bDoPends = true;
  120. char *pszIn = new char[iBufSize];
  121. if ( pBuf[0] == '-' )
  122. {
  123. bDoPends = false;
  124. Q_strncpy( pszIn, pBuf+1, iBufSize-1 );
  125. }
  126. else
  127. {
  128. Q_strncpy( pszIn, pBuf, iBufSize );
  129. }
  130. pBuf[0] = '\0';
  131. ModifySpeech( pszIn, pBuf, iBufSize, bDoPends, false );
  132. }
  133. //-----------------------------------------------------------------------------
  134. // Purpose:
  135. //-----------------------------------------------------------------------------
  136. const char *CTFAutoRP::GetRandomPre( void )
  137. {
  138. if ( RandomInt(1,4) != 1 )
  139. return NULL;
  140. if ( !m_a_pszPrependedWords.Count() )
  141. return NULL;
  142. static int iPrevPre = 0;
  143. iPrevPre += RandomInt(1,4);
  144. while ( iPrevPre >= m_a_pszPrependedWords.Count() )
  145. {
  146. iPrevPre -= m_a_pszPrependedWords.Count();
  147. }
  148. return m_a_pszPrependedWords[iPrevPre];
  149. }
  150. //-----------------------------------------------------------------------------
  151. // Purpose:
  152. //-----------------------------------------------------------------------------
  153. const char *CTFAutoRP::GetRandomPost( void )
  154. {
  155. if ( RandomInt(1,5) != 1 )
  156. return NULL;
  157. if ( !m_a_pszAppendedWords.Count() )
  158. return NULL;
  159. static int iPrevPost = 0;
  160. iPrevPost += RandomInt(1,3);
  161. while ( iPrevPost >= m_a_pszAppendedWords.Count() )
  162. {
  163. iPrevPost -= m_a_pszAppendedWords.Count();
  164. }
  165. return m_a_pszAppendedWords[iPrevPost];
  166. }
  167. //-----------------------------------------------------------------------------
  168. // Purpose:
  169. //-----------------------------------------------------------------------------
  170. matchresult_t CTFAutoRP::WordMatches( wordreplacement_t *pRep, replacementcheck_t *pCheck )
  171. {
  172. if ( pRep->iChance != 1 )
  173. {
  174. if ( RandomInt( 1, pRep->iChance ) > 1 )
  175. return MATCHES_NOT;
  176. }
  177. // If it has prewords, make sure the preword matches first
  178. if ( pRep->m_PrevWords.Count() > 0 )
  179. {
  180. if ( pCheck->iPrevLen <= 0 )
  181. return MATCHES_NOT;
  182. CUtlSymbol sym = m_pWordTable->Find( pCheck->szPrevWord );
  183. if ( UTL_INVAL_SYMBOL == sym )
  184. return MATCHES_NOT;
  185. bool bMatchPrev = false;
  186. FOR_EACH_VEC( pRep->m_PrevWords, i )
  187. {
  188. if ( pRep->m_PrevWords[i] == sym )
  189. {
  190. bMatchPrev = true;
  191. break;
  192. }
  193. }
  194. if ( !bMatchPrev )
  195. return MATCHES_NOT;
  196. pCheck->bUsedPrevWord = true;
  197. }
  198. CUtlSymbol sym = m_pWordTable->Find( pCheck->szWord );
  199. FOR_EACH_VEC( pRep->m_Words, i )
  200. {
  201. if ( pRep->m_Words[i] == sym )
  202. return MATCHES_SINGULAR;
  203. }
  204. CUtlSymbol pluralsym = m_pWordTable->Find( pCheck->szWord );
  205. FOR_EACH_VEC( pRep->m_Plurals, i )
  206. {
  207. if ( pRep->m_Plurals[i] == pluralsym )
  208. return MATCHES_PLURAL;
  209. }
  210. pCheck->bUsedPrevWord = false;
  211. return MATCHES_NOT;
  212. }
  213. //-----------------------------------------------------------------------------
  214. // Purpose:
  215. //-----------------------------------------------------------------------------
  216. bool CTFAutoRP::ReplaceWord( replacementcheck_t *pCheck, char *szRep, int iRepSize, bool bSymbols, bool bWordListOnly )
  217. {
  218. szRep[0] = '\0';
  219. // First, see if we have a replacement
  220. FOR_EACH_VEC( m_a_Replacements, i )
  221. {
  222. wordreplacement_t *pRep = &m_a_Replacements[i];
  223. matchresult_t iRes = WordMatches( pRep, pCheck );
  224. if ( iRes == MATCHES_NOT )
  225. continue;
  226. if ( pRep->a_pszPrepended.Count() > 0 )
  227. {
  228. CUtlVector<int> vecUsed;
  229. for ( int iCount = 0; iCount < pRep->iPrePendCount; iCount++ )
  230. {
  231. // Ensure we don't choose two of the same prepends
  232. int rnd = 0;
  233. do
  234. {
  235. rnd = RandomInt( 0, (int)pRep->a_pszPrepended.Count() - 1 );
  236. } while ( vecUsed.Find(rnd) != vecUsed.InvalidIndex() );
  237. vecUsed.AddToTail(rnd);
  238. Q_strncat( szRep, pRep->a_pszPrepended[rnd], iRepSize );
  239. if ( (iCount+1) < pRep->iPrePendCount )
  240. {
  241. Q_strncat( szRep, ", ", iRepSize );
  242. }
  243. else
  244. {
  245. Q_strncat( szRep, " ", iRepSize );
  246. }
  247. }
  248. }
  249. if ( iRes == MATCHES_SINGULAR )
  250. {
  251. int rnd = RandomInt( 0, (int)pRep->a_pszReplacements.Count() - 1 );
  252. Q_strncat( szRep, pRep->a_pszReplacements[rnd], iRepSize );
  253. }
  254. else if ( iRes == MATCHES_PLURAL )
  255. {
  256. int rnd = RandomInt( 0, (int)pRep->a_pszPluralReplacements.Count() - 1 );
  257. Q_strncat( szRep, pRep->a_pszPluralReplacements[rnd], iRepSize );
  258. }
  259. return true;
  260. }
  261. if ( !bSymbols && !bWordListOnly )
  262. {
  263. char fc = pCheck->szWord[0];
  264. // Randomly replace h's at the front of words with apostrophes
  265. if ( fc == 'h' && RandomInt(1,2) == 1 )
  266. {
  267. Q_strncpy( szRep, pCheck->szWord, MIN( iRepSize, pCheck->iWordLen+1 ) );
  268. szRep[0] = '\'';
  269. return true;
  270. }
  271. char lc = pCheck->szWord[ pCheck->iWordLen-1 ];
  272. if ( pCheck->iWordLen > 3 )
  273. {
  274. char slc = pCheck->szWord[ pCheck->iWordLen-2 ];
  275. char lllc = pCheck->szWord[ pCheck->iWordLen-3 ];
  276. // Randomly modify words ending in "ed", by replacing the "e" with an apostrophe
  277. // i.e. "worked" -> "work'd", "waited" -> "wait'd"
  278. if ( slc == 'e' && lc == 'd' && lllc != 'e' && RandomInt(1,4) == 1 )
  279. {
  280. Q_strncpy( szRep, pCheck->szWord, MIN( iRepSize, pCheck->iWordLen+1 ) );
  281. szRep[ pCheck->iWordLen-2 ] = '\'';
  282. return true;
  283. }
  284. // Randomly append "th" or "st" to any word ending in "ke"
  285. // i.e. "take" -> "taketh", "broke" -> "brokest"
  286. if ( slc == 'k' && lc == 'e' && RandomInt(1,3) == 1 )
  287. {
  288. Q_strncpy( szRep, pCheck->szWord, MIN( iRepSize, pCheck->iWordLen+1 ) );
  289. if ( RandomInt(1,2) == 1 )
  290. {
  291. Q_strncat( szRep, "th", iRepSize );
  292. }
  293. else
  294. {
  295. Q_strncat( szRep, "st", iRepSize );
  296. }
  297. return true;
  298. }
  299. }
  300. if ( pCheck->iWordLen >= 3 )
  301. {
  302. char slc = pCheck->szWord[ pCheck->iWordLen-2 ];
  303. // Randomly append "eth" to words with appropriate last letters.
  304. if ( RandomInt( 1, 5 ) == 1 &&
  305. (lc == 't' || lc == 'p' || lc == 'k' || lc == 'g' || lc == 'b' || lc == 'w') )
  306. {
  307. Q_strncpy( szRep, pCheck->szWord, MIN( iRepSize, pCheck->iWordLen+1 ) );
  308. Q_strncat( szRep, "eth", iRepSize );
  309. return true;
  310. }
  311. // Randomly append "est" to any word ending in "ss"
  312. // i.e. "pass" -> "passest", "class" -> "classest"
  313. if ( lc == 's' && slc == 's' && RandomInt(1,5) == 1 )
  314. {
  315. Q_strncpy( szRep, pCheck->szWord, MIN( iRepSize, pCheck->iWordLen+1 ) );
  316. Q_strncat( szRep, "est", iRepSize );
  317. return true;
  318. }
  319. }
  320. if ( pCheck->iWordLen > 4 )
  321. {
  322. // Randomly prepend "a-" to words ending in "ing", and randomly replace the trailing g with an apostrophe
  323. // i.e. "coming" -> "a-comin'", "dancing" -> "a-dancing"
  324. char slc = pCheck->szWord[ pCheck->iWordLen-2 ];
  325. char lllc = pCheck->szWord[ pCheck->iWordLen-3 ];
  326. if ( lllc == 'i' && slc == 'n' && lc == 'g' )
  327. {
  328. char sc = pCheck->szWord[2];
  329. if ( sc != '-' )
  330. {
  331. Q_strncpy( szRep, "a-", iRepSize );
  332. if ( RandomInt(1,2) == 1 )
  333. {
  334. Q_strncat( szRep, pCheck->szWord, iRepSize, pCheck->iWordLen );
  335. }
  336. else
  337. {
  338. Q_strncat( szRep, pCheck->szWord, iRepSize, pCheck->iWordLen-1 );
  339. Q_strncat( szRep, "'", iRepSize );
  340. }
  341. return true;
  342. }
  343. }
  344. }
  345. }
  346. return false;
  347. }
  348. //-----------------------------------------------------------------------------
  349. // Purpose:
  350. //-----------------------------------------------------------------------------
  351. bool CTFAutoRP::PerformReplacement( const char *pszReplacement, replacementcheck_t *pRepCheck, char *szStoredWord, int iStoredWordSize, char *pszOutText, int iOutLen )
  352. {
  353. if ( pszReplacement && pszReplacement[0] )
  354. {
  355. // Check to see if the previous word should be modified
  356. char fc = tolower( *pszReplacement );
  357. if ( !_strnicmp( pRepCheck->szPrevWord, "an", MAX(pRepCheck->iPrevLen,2) ) )
  358. {
  359. if ( fc != 'a' && fc != 'e' && fc != 'i' && fc != 'o' && fc != 'u' )
  360. {
  361. // Remove the trailing n
  362. int iLen = (int)strlen( szStoredWord );
  363. szStoredWord[iLen-1] = '\0'; // Move back 3. 1 for null, 1 for space, 1 for n.
  364. }
  365. }
  366. else if ( *pRepCheck->szPrevWord == 'a' && pRepCheck->iPrevLen == 1 )
  367. {
  368. if ( fc == 'a' || fc == 'e' || fc == 'i' || fc == 'o' || fc == 'u' )
  369. {
  370. // Add a trailing n
  371. Q_strncat( szStoredWord, "n", iStoredWordSize );
  372. }
  373. }
  374. }
  375. // Only append the previous word if we didn't use it in our replacement
  376. if ( !pRepCheck->bUsedPrevWord )
  377. {
  378. // Append the previous word
  379. Q_strncat( pszOutText, szStoredWord, iOutLen );
  380. return true;
  381. }
  382. return false;
  383. }
  384. //-----------------------------------------------------------------------------
  385. // Purpose:
  386. //-----------------------------------------------------------------------------
  387. void CTFAutoRP::ModifySpeech( const char *pszInText, char *pszOutText, int iOutLen, bool bGeneratePreAndPost, bool bInPrePost )
  388. {
  389. if ( bGeneratePreAndPost )
  390. {
  391. // See if we generate a pre. If we do, modify it as well so we can perform replacements on it.
  392. const char *pszPre = GetRandomPre();
  393. if ( pszPre && pszPre[0] )
  394. {
  395. ModifySpeech( pszPre, pszOutText, iOutLen, false, true );
  396. Q_strncat( pszOutText, " ", iOutLen );
  397. }
  398. }
  399. // Iterate through all the words and test them vs our replacement list
  400. const char *pszPrevWord = pszInText;
  401. const char *pszCurWord = pszInText;
  402. const char *pszCh = pszInText;
  403. char szStoredWord[128];
  404. szStoredWord[0] = '\0';
  405. char szCurrentWord[128];
  406. szCurrentWord[0] = '\0';
  407. replacementcheck_t repCheck;
  408. while ( 1 )
  409. {
  410. if ( (*pszCh >= 'A' && *pszCh <= 'Z') || (*pszCh >= 'a' && *pszCh <= 'z') || *pszCh == '&' )
  411. {
  412. pszCh++;
  413. continue;
  414. }
  415. // Hit the end of a word/string.
  416. int iCurLen = (int)(pszCh - pszCurWord);
  417. int iPrevLen = MAX( 0, (int)(pszCurWord - pszPrevWord) - 1 ); // -1 for the space
  418. bool bModifyWord = true;
  419. bool bSkipOneLetter = false;
  420. // Pre/Post pend blocks only modify words that start with an '&'
  421. if ( bInPrePost )
  422. {
  423. bModifyWord = ( pszCurWord[0] == '&' );
  424. bSkipOneLetter = bModifyWord;
  425. }
  426. if ( bSkipOneLetter )
  427. {
  428. Q_strncpy( repCheck.szWord, pszCurWord+1, iCurLen );
  429. repCheck.iWordLen = iCurLen-1;
  430. }
  431. else
  432. {
  433. Q_strncpy( repCheck.szWord, pszCurWord, iCurLen+1 );
  434. repCheck.iWordLen = iCurLen;
  435. }
  436. Q_strncpy( repCheck.szPrevWord, pszPrevWord, iPrevLen+1 );
  437. repCheck.iPrevLen = iPrevLen;
  438. repCheck.bUsedPrevWord = false;
  439. if ( iCurLen > 0 )
  440. {
  441. bool bChanged = bModifyWord ? ReplaceWord( &repCheck, szCurrentWord, sizeof(szCurrentWord), false, bInPrePost ) : false;
  442. // If the character that broke the last two words apart was an apostrophe, see if we can replace the whole word
  443. if ( !bChanged && bModifyWord )
  444. {
  445. if ( szStoredWord[0] )
  446. {
  447. int iLen = Q_strlen(szStoredWord);
  448. if ( szStoredWord[iLen-1] == '\'' )
  449. {
  450. Q_strncpy( repCheck.szWord, szStoredWord, MIN( sizeof(repCheck.szWord),iLen+1 ) );
  451. Q_strncat( repCheck.szWord, pszCurWord, sizeof(repCheck.szWord), iCurLen );
  452. repCheck.iWordLen = iLen + iCurLen;
  453. repCheck.szPrevWord[0] = '\0';
  454. repCheck.iPrevLen = 0;
  455. bChanged = ReplaceWord( &repCheck, szCurrentWord, sizeof(szCurrentWord), false, bInPrePost );
  456. if ( bChanged )
  457. {
  458. repCheck.bUsedPrevWord = true;
  459. }
  460. }
  461. }
  462. }
  463. if ( szStoredWord[0] != '\0' )
  464. {
  465. if ( PerformReplacement( szCurrentWord, &repCheck, szStoredWord, sizeof(szStoredWord), pszOutText, iOutLen ) )
  466. {
  467. // Append a space, but not if the last character is an apostrophe
  468. int iLen = Q_strlen(szStoredWord);
  469. if ( szStoredWord[iLen-1] != '\'' )
  470. {
  471. Q_strncat( pszOutText, " ", iOutLen );
  472. }
  473. }
  474. }
  475. if ( bChanged )
  476. {
  477. Q_strncpy( szStoredWord, szCurrentWord, sizeof(szStoredWord) );
  478. // Match case of the first letter in the word we're replacing
  479. if ( pszCurWord[0] >= 'A' && pszCurWord[0] <= 'Z' )
  480. {
  481. szStoredWord[0] = toupper( szStoredWord[0] );
  482. }
  483. else if ( pszCurWord[0] >= 'a' && pszCurWord[0] <= 'a' )
  484. {
  485. szStoredWord[0] = tolower( szStoredWord[0] );
  486. }
  487. }
  488. else
  489. {
  490. Q_strncpy( szStoredWord, pszCurWord, MIN( (int)sizeof(szStoredWord), (int)(pszCh - pszCurWord)+1 ) );
  491. }
  492. }
  493. // Finished?
  494. if ( *pszCh == '\0' )
  495. {
  496. repCheck.bUsedPrevWord = false;
  497. if ( szStoredWord[0] != '\0' )
  498. {
  499. PerformReplacement( NULL, &repCheck, szStoredWord, sizeof(szStoredWord), pszOutText, iOutLen );
  500. }
  501. break;
  502. }
  503. // If it wasn't a space that ended this word, try checking it for a symbol
  504. if ( *pszCh != ' ' )
  505. {
  506. Q_strncpy( repCheck.szWord, pszCh, 2 );
  507. repCheck.iWordLen = 1;
  508. repCheck.iPrevLen = 0;
  509. repCheck.bUsedPrevWord = false;
  510. char szSymbolRep[128];
  511. szSymbolRep[0] = '\0';
  512. if ( ReplaceWord( &repCheck, szSymbolRep, sizeof(szSymbolRep), true, true ) )
  513. {
  514. Q_strncat( szStoredWord, szSymbolRep, (int)sizeof(szStoredWord) );
  515. }
  516. else
  517. {
  518. Q_strncat( szStoredWord, pszCh, (int)sizeof(szStoredWord), 1 );
  519. }
  520. }
  521. // Move on
  522. pszCh++;
  523. pszPrevWord = pszCurWord;
  524. pszCurWord = pszCh;
  525. }
  526. if ( bGeneratePreAndPost )
  527. {
  528. int iLen = (int)strlen( pszOutText );
  529. char pszLC = pszOutText[iLen-1];
  530. if ( pszLC != '?' && pszLC != '!' )
  531. {
  532. // See if we generate a post. If we do, modify it as well so we can perform replacements on it.
  533. const char *pszPost = GetRandomPost();
  534. if ( pszPost && pszPost[0] )
  535. {
  536. if ( pszLC != '.' )
  537. {
  538. Q_strncat( pszOutText, ". ", iOutLen );
  539. }
  540. else
  541. {
  542. Q_strncat( pszOutText, " ", iOutLen );
  543. }
  544. ModifySpeech( pszPost, pszOutText, iOutLen, false, true );
  545. }
  546. }
  547. }
  548. }