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.

2862 lines
68 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Voice / Sentence streaming & parsing code
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. // $NoKeywords: $
  8. //=============================================================================//
  9. //===============================================================================
  10. // VOX. Algorithms to load and play spoken text sentences from a file:
  11. //
  12. // In ambient sounds or entity sounds, precache the
  13. // name of the sentence instead of the wave name, ie: !C1A2S4
  14. //
  15. // During sound system init, the 'sentences.txt' is read.
  16. // This file has the format:
  17. //
  18. // C1A2S4 agrunt/vox/You will be exterminated, surrender NOW.
  19. // C1A2s5 hgrunt/vox/Radio check, over.
  20. // ...
  21. //
  22. // There must be at least one space between the sentence name and the sentence.
  23. // Sentences may be separated by one or more lines
  24. // There may be tabs or spaces preceding the sentence name
  25. // The sentence must end in a /n or /r
  26. // Lines beginning with // are ignored as comments
  27. //
  28. // Period or comma will insert a pause in the wave unless
  29. // the period or comma is the last character in the string.
  30. //
  31. // If first 2 chars of a word are upper case, word volume increased by 25%
  32. //
  33. // If last char of a word is a number from 0 to 9
  34. // then word will be pitch-shifted up by 0 to 9, where 0 is a small shift
  35. // and 9 is a very high pitch shift.
  36. //
  37. // We alloc heap space to contain this data, and track total
  38. // sentences read. A pointer to each sentence is maintained in g_Sentences.
  39. //
  40. // When sound is played back in S_StartDynamicSound or s_startstaticsound, we detect the !name
  41. // format and lookup the actual sentence in the sentences array
  42. //
  43. // To play, we parse each word in the sentence, chain the words, and play the sentence
  44. // each word's data is loaded directy from disk and freed right after playback.
  45. //===============================================================================
  46. #include "audio_pch.h"
  47. #include "vox_private.h"
  48. #include "characterset.h"
  49. #include "vstdlib/random.h"
  50. #include "engine/IEngineSound.h"
  51. #include "utlsymbol.h"
  52. #include "utldict.h"
  53. #include "../../MapReslistGenerator.h"
  54. // memdbgon must be the last include file in a .cpp file!!!
  55. #include "tier0/memdbgon.h"
  56. // In other C files.
  57. // Globals
  58. extern IFileSystem *g_pFileSystem;
  59. // This is the initial capacity for sentences, the array will grow if necessary
  60. #define MAX_EXPECTED_SENTENCES 900
  61. CUtlVector<sentence_t> g_Sentences;
  62. // FIXME: could get this through common includes
  63. const char *COM_Parse (const char *data);
  64. extern char com_token[1024];
  65. // Module Locals
  66. static char *rgpparseword[CVOXWORDMAX]; // array of pointers to parsed words
  67. static char voxperiod[] = "_period"; // vocal pause
  68. static char voxcomma[] = "_comma"; // vocal pause
  69. #define CVOXMAPNAMESMAX 24
  70. static char *g_rgmapnames[CVOXMAPNAMESMAX];
  71. static int g_cmapnames = 0;
  72. // Sentence file list management
  73. static void VOX_ListClear( void );
  74. static int VOX_ListFileIsLoaded( const char *psentenceFileName );
  75. static void VOX_ListMarkFileLoaded( const char *psentenceFileName );
  76. static void VOX_InitAllEntnames( void );
  77. void VOX_LookupMapnames( void );
  78. static void VOX_Reload()
  79. {
  80. VOX_Shutdown();
  81. VOX_Init();
  82. }
  83. static ConCommand vox_reload( "vox_reload", VOX_Reload, "Reload sentences.txt file", FCVAR_CHEAT );
  84. static CUtlVector<unsigned char> g_GroupLRU;
  85. static CUtlVector<char> g_SentenceFile;
  86. struct sentencegroup_t
  87. {
  88. short count;
  89. public:
  90. short lru;
  91. const char *GroupName() const;
  92. CUtlSymbol GroupNameSymbol() const;
  93. void SetGroupName( const char *pName );
  94. static CUtlSymbol GetSymbol( const char *pName );
  95. private:
  96. CUtlSymbol groupname;
  97. static CUtlSymbolTable s_SymbolTable;
  98. };
  99. const char *sentencegroup_t::GroupName() const
  100. {
  101. return s_SymbolTable.String( groupname );
  102. }
  103. void sentencegroup_t::SetGroupName( const char *pName )
  104. {
  105. groupname = s_SymbolTable.AddString( pName );
  106. }
  107. CUtlSymbol sentencegroup_t::GroupNameSymbol() const
  108. {
  109. return groupname;
  110. }
  111. CUtlSymbol sentencegroup_t::GetSymbol( const char *pName )
  112. {
  113. return s_SymbolTable.AddString( pName );
  114. }
  115. CUtlVector<sentencegroup_t> g_SentenceGroups;
  116. CUtlSymbolTable sentencegroup_t::s_SymbolTable( 0, 256, true );
  117. struct WordBuf
  118. {
  119. WordBuf()
  120. {
  121. word[ 0 ] = 0;
  122. }
  123. WordBuf( const WordBuf& src )
  124. {
  125. Q_strncpy( word, src.word, sizeof( word ) );
  126. }
  127. void Set( char const *w )
  128. {
  129. if ( !w )
  130. {
  131. word[ 0 ] = 0;
  132. return;
  133. }
  134. Q_strncpy( word, w, sizeof( word ) );
  135. while ( Q_strlen( word ) >= 1 && word[ Q_strlen( word ) - 1 ] == ' ' )
  136. {
  137. word[ Q_strlen( word ) - 1 ] = 0;
  138. }
  139. }
  140. char word[ 256 ];
  141. };
  142. struct ccpair
  143. {
  144. WordBuf token;
  145. WordBuf value;
  146. WordBuf fullpath;
  147. };
  148. static void VOX_BuildVirtualNameList( char *word, CUtlVector< WordBuf >& list );
  149. // This module depends on these engine calls:
  150. // DevMsg
  151. // S_FreeChannel
  152. // S_LoadSound
  153. // S_FindName
  154. // It also depends on vstdlib/RandomInt (all other random calls go through g_pSoundServices)
  155. void VOX_Init( void )
  156. {
  157. VOX_InitAllEntnames();
  158. g_SentenceFile.Purge();
  159. g_GroupLRU.Purge();
  160. g_Sentences.RemoveAll();
  161. g_Sentences.EnsureCapacity( MAX_EXPECTED_SENTENCES );
  162. VOX_ListClear();
  163. VOX_ReadSentenceFile( "scripts/sentences.txt" );
  164. VOX_LookupMapnames();
  165. }
  166. void VOX_Shutdown( void )
  167. {
  168. g_Sentences.RemoveAll();
  169. VOX_ListClear();
  170. g_SentenceGroups.RemoveAll();
  171. g_cmapnames = 0;
  172. }
  173. //-----------------------------------------------------------------------------
  174. // Purpose: This is kind of like strchr(), but we get the actual pointer to the
  175. // end of the string when it fails rather than NULL. This is useful
  176. // for parsing buffers containing multiple strings
  177. // Input : *string -
  178. // scan -
  179. // Output : char
  180. //-----------------------------------------------------------------------------
  181. char *ScanForwardUntil( char *string, char scan )
  182. {
  183. while( string[0] )
  184. {
  185. if ( string[0] == scan )
  186. return string;
  187. string++;
  188. }
  189. return string;
  190. }
  191. // parse a null terminated string of text into component words, with
  192. // pointers to each word stored in rgpparseword
  193. // note: this code actually alters the passed in string!
  194. char **VOX_ParseString(char *psz)
  195. {
  196. int i;
  197. int fdone = 0;
  198. char *pszscan = psz;
  199. char c;
  200. characterset_t nextWord, skip;
  201. memset(rgpparseword, 0, sizeof(char *) * CVOXWORDMAX);
  202. if (!psz)
  203. return NULL;
  204. i = 0;
  205. rgpparseword[i++] = psz;
  206. CharacterSetBuild( &nextWord, " ,.({" );
  207. CharacterSetBuild( &skip, "., " );
  208. while (!fdone && i < CVOXWORDMAX)
  209. {
  210. // scan up to next word
  211. c = *pszscan;
  212. while (c && !IN_CHARACTERSET(nextWord,c) )
  213. c = *(++pszscan);
  214. // if '(' then scan for matching ')'
  215. if ( c == '(' || c=='{' )
  216. {
  217. if ( c == '(' )
  218. pszscan = ScanForwardUntil( pszscan, ')' );
  219. else if ( c == '{' )
  220. pszscan = ScanForwardUntil( pszscan, '}' );
  221. c = *(++pszscan);
  222. if (!c)
  223. fdone = 1;
  224. }
  225. if (fdone || !c)
  226. fdone = 1;
  227. else
  228. {
  229. // if . or , insert pause into rgpparseword,
  230. // unless this is the last character
  231. if ((c == '.' || c == ',') && *(pszscan+1) != '\n' && *(pszscan+1) != '\r'
  232. && *(pszscan+1) != 0)
  233. {
  234. if (c == '.')
  235. rgpparseword[i++] = voxperiod;
  236. else
  237. rgpparseword[i++] = voxcomma;
  238. if (i >= CVOXWORDMAX)
  239. break;
  240. }
  241. // null terminate substring
  242. *pszscan++ = 0;
  243. // skip whitespace
  244. c = *pszscan;
  245. while (c && IN_CHARACTERSET(skip, c))
  246. c = *(++pszscan);
  247. if (!c)
  248. fdone = 1;
  249. else
  250. rgpparseword[i++] = pszscan;
  251. }
  252. }
  253. return rgpparseword;
  254. }
  255. // backwards scan psz for last '/'
  256. // return substring in szpath null terminated
  257. // if '/' not found, return 'vox/'
  258. char *VOX_GetDirectory(char *szpath, int maxpath, char *psz)
  259. {
  260. char c;
  261. int cb = 0;
  262. char *pszscan = psz + Q_strlen( psz ) - 1;
  263. // scan backwards until first '/' or start of string
  264. c = *pszscan;
  265. while (pszscan > psz && c != '/')
  266. {
  267. c = *(--pszscan);
  268. cb++;
  269. }
  270. if (c != '/')
  271. {
  272. // didn't find '/', return default directory
  273. Q_strncpy(szpath, "vox/", maxpath );
  274. return psz;
  275. }
  276. cb = Q_strlen(psz) - cb;
  277. cb = clamp( cb, 0, maxpath - 1 );
  278. // FIXME: Is this safe?
  279. Q_memcpy(szpath, psz, cb);
  280. szpath[cb] = 0;
  281. return pszscan + 1;
  282. }
  283. // get channel volume scale if word
  284. #ifndef SWDS
  285. float VOX_GetChanVol(channel_t *ch)
  286. {
  287. if ( !ch->pMixer )
  288. return 1.0;
  289. return ch->pMixer->GetVolumeScale();
  290. /*
  291. if ( scale == 1.0 )
  292. return;
  293. ch->rightvol = (int) (ch->rightvol * scale);
  294. ch->leftvol = (int) (ch->leftvol * scale);
  295. if ( g_AudioDevice->Should3DMix() )
  296. {
  297. ch->rrightvol = (int) (ch->rrightvol * scale);
  298. ch->rleftvol = (int) (ch->rleftvol * scale);
  299. ch->centervol = (int) (ch->centervol * scale);
  300. }
  301. else
  302. {
  303. ch->rrightvol = 0;
  304. ch->rleftvol = 0;
  305. ch->centervol = 0;
  306. }
  307. */
  308. }
  309. #endif
  310. //===============================================================================
  311. // Get any pitch, volume, start, end params into voxword
  312. // and null out trailing format characters
  313. // Format:
  314. // someword(v100 p110 s10 e20)
  315. //
  316. // v is volume, 0% to n%
  317. // p is pitch shift up 0% to n%
  318. // s is start wave offset %
  319. // e is end wave offset %
  320. // t is timecompression %
  321. //
  322. // pass fFirst == 1 if this is the first string in sentence
  323. // returns 1 if valid string, 0 if parameter block only.
  324. //
  325. // If a ( xxx ) parameter block does not directly follow a word,
  326. // then that 'default' parameter block will be used as the default value
  327. // for all following words. Default parameter values are reset
  328. // by another 'default' parameter block. Default parameter values
  329. // for a single word are overridden for that word if it has a parameter block.
  330. //
  331. //===============================================================================
  332. int VOX_ParseWordParams(char *psz, voxword_t *pvoxword, int fFirst)
  333. {
  334. char *pszsave = psz;
  335. char c;
  336. char ct;
  337. char sznum[8];
  338. int i;
  339. static voxword_t voxwordDefault;
  340. characterset_t commandSet, delimitSet;
  341. // List of valid commands
  342. CharacterSetBuild( &commandSet, "vpset)" );
  343. // init to defaults if this is the first word in string.
  344. if (fFirst)
  345. {
  346. voxwordDefault.pitch = -1;
  347. voxwordDefault.volume = 100;
  348. voxwordDefault.start = 0;
  349. voxwordDefault.end = 100;
  350. voxwordDefault.fKeepCached = 0;
  351. voxwordDefault.timecompress = 0;
  352. }
  353. *pvoxword = voxwordDefault;
  354. // look at next to last char to see if we have a
  355. // valid format:
  356. c = *(psz + strlen(psz) - 1);
  357. if (c != ')')
  358. return 1; // no formatting, return
  359. // scan forward to first '('
  360. CharacterSetBuild( &delimitSet, "()" );
  361. c = *psz;
  362. while ( !IN_CHARACTERSET(delimitSet, c) )
  363. c = *(++psz);
  364. if ( c == ')' )
  365. return 0; // bogus formatting
  366. // null terminate
  367. *psz = 0;
  368. ct = *(++psz);
  369. while (1)
  370. {
  371. // scan until we hit a character in the commandSet
  372. while (ct && !IN_CHARACTERSET(commandSet, ct) )
  373. ct = *(++psz);
  374. if (ct == ')')
  375. break;
  376. memset(sznum, 0, sizeof(sznum));
  377. i = 0;
  378. c = *(++psz);
  379. if (!V_isdigit(c))
  380. break;
  381. // read number
  382. while (V_isdigit(c) && i < sizeof(sznum) - 1)
  383. {
  384. sznum[i++] = c;
  385. c = *(++psz);
  386. }
  387. // get value of number
  388. i = atoi(sznum);
  389. switch (ct)
  390. {
  391. case 'v': pvoxword->volume = i; break;
  392. case 'p': pvoxword->pitch = i; break;
  393. case 's': pvoxword->start = i; break;
  394. case 'e': pvoxword->end = i; break;
  395. case 't': pvoxword->timecompress = i; break;
  396. }
  397. ct = c;
  398. }
  399. // if the string has zero length, this was an isolated
  400. // parameter block. Set default voxword to these
  401. // values
  402. if (strlen(pszsave) == 0)
  403. {
  404. voxwordDefault = *pvoxword;
  405. return 0;
  406. }
  407. else
  408. return 1;
  409. }
  410. #define CVOXSAVEDWORDSIZE 32
  411. // saved entity name/number based on type of entity & id
  412. #define CVOXGLOBMAX 4 // max number of rnd and seqential globals
  413. typedef struct _vox_entname
  414. {
  415. // type is defined by last character of group name.
  416. // for instance, V_MYNAME_S has type 'S', which is used for soldiers
  417. // V_MYNUM_M has type 'P' which is used for metrocops
  418. int type;
  419. SoundSource soundsource; // the enity emitting the sentence
  420. char *pszname; // a custom name for the entity (this is a word name)
  421. char *psznum; // a custom number for the entity (this is a word name)
  422. char *pszglobal[CVOXGLOBMAX]; // 1 global word, shared by this type of entity, picked randomly, expires after 5min
  423. char *pszglobalseq[CVOXGLOBMAX]; // 1 global word, shared by this type of entity, picked in sequence, expires after 5 min
  424. bool fdied; // true if ent died (don't clear, we need its name)
  425. int iseq[CVOXGLOBMAX]; // sequence index, for global sequential lookups
  426. float timestamp[CVOXGLOBMAX]; // latest update to this ent global timestamp
  427. float timestampseq[CVOXGLOBMAX]; // latest update to this ent global sequential timestamp
  428. float timedied; // timestamp of death
  429. } vox_entname;
  430. #define CENTNAMESMAX 64
  431. vox_entname g_entnames[CENTNAMESMAX];
  432. int g_entnamelastsaved = 0;
  433. // init all
  434. void VOX_InitAllEntnames( void )
  435. {
  436. g_entnamelastsaved = 0;
  437. Q_memset(g_entnames, 0, sizeof(g_entnames));
  438. Q_memset(g_rgmapnames, 0, sizeof(g_rgmapnames));
  439. g_cmapnames = 0;
  440. }
  441. // get new index
  442. int VOX_GetNextEntnameIndex( void )
  443. {
  444. g_entnamelastsaved++;
  445. if (g_entnamelastsaved >= CENTNAMESMAX)
  446. {
  447. g_entnamelastsaved = 0;
  448. }
  449. return g_entnamelastsaved;
  450. }
  451. // get index of this ent, or get a new index. if fallocnew is true,
  452. // get a new slot if none found.
  453. // NOTE: this routine always sets fdied to false - fdied is later
  454. // set to true by the caller if in IDIED routine. This
  455. // ensures that if an ent is reused, it won't be marked as fdied.
  456. int VOX_LookupEntIndex( int type, SoundSource soundsource, bool fallocnew)
  457. {
  458. int i;
  459. for (i = 0; i < CENTNAMESMAX; i++)
  460. {
  461. if ((g_entnames[i].type == type) && (g_entnames[i].soundsource == soundsource))
  462. {
  463. g_entnames[i].fdied = false;
  464. return i;
  465. }
  466. }
  467. if ( !fallocnew )
  468. return -1;
  469. // new index slot - init
  470. int inew = VOX_GetNextEntnameIndex();
  471. g_entnames[inew].type = type;
  472. g_entnames[inew].soundsource = soundsource;
  473. g_entnames[inew].timedied = 0;
  474. g_entnames[inew].fdied = 0;
  475. g_entnames[inew].pszname = NULL;
  476. g_entnames[inew].psznum = NULL;
  477. for (i = 0; i < CVOXGLOBMAX; i++)
  478. {
  479. g_entnames[inew].pszglobal[i] = NULL;
  480. g_entnames[inew].timestamp[i] = 0;
  481. g_entnames[inew].iseq[i] = 0;
  482. g_entnames[inew].timestampseq[i] = 0;
  483. g_entnames[inew].pszglobalseq[i] = NULL;
  484. }
  485. return inew;
  486. }
  487. // lookup random first word from this named group,
  488. // return static, null terminated string
  489. char * VOX_LookupRndVirtual( char *pGroupName )
  490. {
  491. // get group index
  492. int isentenceg = VOX_GroupIndexFromName( pGroupName );
  493. if ( isentenceg < 0)
  494. return NULL;
  495. char szsentencename[32];
  496. // get pointer to sentence name within group, using lru
  497. int isentence = VOX_GroupPick( isentenceg, szsentencename, sizeof(szsentencename)-1 );
  498. if (isentence < 0)
  499. return NULL;
  500. // get pointer to sentence data
  501. char *psz = VOX_LookupString( szsentencename[0] == '!' ? szsentencename+1 : szsentencename, NULL);
  502. // strip trailing whitespace
  503. if (!psz)
  504. return NULL;
  505. char *pend = Q_strstr(psz, " ");
  506. if (pend)
  507. *pend = 0;
  508. // return pointer to first (and only) word
  509. return psz;
  510. }
  511. // given groupname, get pointer to first word of n'th sentence in group
  512. char *VOX_LookupSentenceByIndex( char *pGroupname, int ipick, int *pipicknext )
  513. {
  514. // get group index
  515. int isentenceg = VOX_GroupIndexFromName( pGroupname );
  516. if ( isentenceg < 0)
  517. return NULL;
  518. char szsentencename[32];
  519. // get pointer to sentence name within group, using lru
  520. int isentence = VOX_GroupPickSequential( isentenceg, szsentencename, sizeof(szsentencename)-1, ipick, true );
  521. if (isentence < 0)
  522. return NULL;
  523. // get pointer to sentence data
  524. char *psz = VOX_LookupString( szsentencename[0] == '!' ? szsentencename+1 : szsentencename, NULL);
  525. // strip trailing whitespace
  526. char *pend = Q_strstr(psz, " ");
  527. if (pend)
  528. *pend = 0;
  529. if (pipicknext)
  530. *pipicknext = isentence;
  531. // return pointer to first (and only) word
  532. return psz;
  533. }
  534. // lookup first word from this named group, group entry 'ipick',
  535. // return static, null terminated string
  536. char * VOX_LookupNumber( char *pGroupName, int ipick )
  537. {
  538. // construct group name from V_NUMBERS + TYPE
  539. char sznumbers[16];
  540. int glen = Q_strlen(pGroupName);
  541. int slen = Q_strlen("V_NUMBERS");
  542. V_strcpy_safe(sznumbers, "V_NUMBERS");
  543. // insert type character
  544. sznumbers[slen] = pGroupName[glen-1];
  545. sznumbers[slen+1] = 0;
  546. return VOX_LookupSentenceByIndex( sznumbers, ipick, NULL );
  547. }
  548. // lookup ent & type, return static, null terminated string
  549. // if no saved string, create one.
  550. // UNDONE: init ent/type/string array, wrap when saving
  551. char * VOX_LookupMyVirtual( int iname, char *pGroupName, char chtype, SoundSource soundsource)
  552. {
  553. char *psz = NULL;
  554. char **ppsz = NULL;
  555. // get existing ent index, or index to new slot
  556. int ient = VOX_LookupEntIndex( (int)chtype, soundsource, true );
  557. if (iname == 1)
  558. {
  559. // lookup saved name
  560. psz = g_entnames[ient].pszname;
  561. ppsz = &(g_entnames[ient].pszname);
  562. }
  563. else
  564. {
  565. // lookup saved number
  566. psz = g_entnames[ient].psznum;
  567. ppsz = &(g_entnames[ient].psznum);
  568. }
  569. // if none found for this ent - pick one and save it
  570. if (psz == NULL)
  571. {
  572. // get new string
  573. psz = VOX_LookupRndVirtual( pGroupName );
  574. // save pointer to new string in g_entnames
  575. *ppsz = psz;
  576. }
  577. return psz;
  578. }
  579. // get range or heading from ent to player,
  580. // store range in from 1 to 3 words as ppszNew...ppszNew2
  581. // store count of words in pcnew
  582. // if fsimple is true, return numeric sequence based on ten digit max
  583. void VOX_LookupRangeHeadingOrGrid( int irhg, char *pGroupName, channel_t *pChannel, SoundSource soundsource, char **ppszNew, char **ppszNew1, char **ppszNew2, int *pcnew, bool fsimple )
  584. {
  585. Vector SL; // sound -> listener vector
  586. char *phundreds = NULL;
  587. char *ptens = NULL;
  588. char *pones = NULL;
  589. int cnew = 0;
  590. float dist;
  591. int dmeters = 0;
  592. int hundreds, tens, ones;
  593. VectorSubtract(listener_origin, pChannel->origin, SL);
  594. if (irhg == 0)
  595. {
  596. // get range
  597. dist = VectorLength(SL);
  598. dmeters = (int)((dist * 2.54 / 100.0)); // convert inches to meters
  599. dmeters = clamp(dmeters, 0, 900);
  600. }
  601. else if (irhg == 1)
  602. {
  603. // get heading
  604. QAngle source_angles;
  605. source_angles.Init(0.0, 0.0, 0.0);
  606. VectorAngles( SL, source_angles );
  607. dmeters = source_angles[YAW];
  608. } else if (irhg == 2)
  609. {
  610. // get gridx
  611. dmeters = (int)(((16384 + listener_origin.x) * 2.54 / 100.0) / 10) % 20;
  612. }
  613. else if (irhg == 3)
  614. {
  615. // get gridy
  616. dmeters = (int)(((16384 + listener_origin.y) * 2.54 / 100.0) / 10) % 20;
  617. }
  618. dmeters = clamp(dmeters, 0, 999);
  619. // get hundreds, tens, ones
  620. hundreds = dmeters / 100;
  621. tens = (dmeters - hundreds * 100) / 10;
  622. ones = (dmeters - hundreds * 100 - tens * 10);
  623. if (fsimple)
  624. {
  625. // just return simple ten digit lookups for ones, tens, hundreds
  626. pones = VOX_LookupNumber( pGroupName, ones);
  627. cnew++;
  628. if (tens || hundreds)
  629. {
  630. ptens = VOX_LookupNumber( pGroupName, tens);
  631. cnew++;
  632. }
  633. if (hundreds)
  634. {
  635. phundreds = VOX_LookupNumber( pGroupName, hundreds );
  636. cnew++;
  637. }
  638. goto LookupNumExit;
  639. }
  640. // get pointer to string from groupname and number
  641. // 100,200,300,400,500,600,700,800,900
  642. if (hundreds && !tens && !ones)
  643. {
  644. if (hundreds <= 3)
  645. {
  646. phundreds = VOX_LookupNumber( pGroupName, 27 + hundreds);
  647. cnew++;
  648. }
  649. else
  650. {
  651. phundreds = VOX_LookupNumber( pGroupName, hundreds );
  652. ptens = VOX_LookupNumber( pGroupName, 0);
  653. pones = VOX_LookupNumber( pGroupName, 0);
  654. cnew++;
  655. cnew++;
  656. }
  657. goto LookupNumExit;
  658. }
  659. if ( hundreds )
  660. {
  661. // 101..999
  662. if (hundreds <= 3 && !tens && ones)
  663. phundreds = VOX_LookupNumber( pGroupName, 27 + hundreds);
  664. else
  665. phundreds = VOX_LookupNumber( pGroupName, hundreds );
  666. cnew++;
  667. // 101..109 to 901..909
  668. if (!tens && ones)
  669. {
  670. pones = VOX_LookupNumber( pGroupName, ones);
  671. cnew++;
  672. if (hundreds > 3)
  673. {
  674. ptens = VOX_LookupNumber( pGroupName, 0);
  675. cnew++;
  676. }
  677. goto LookupNumExit;
  678. }
  679. }
  680. // 1..19
  681. if (tens <= 1 && (tens || ones))
  682. {
  683. pones = VOX_LookupNumber( pGroupName, ones + tens * 10 );
  684. cnew++;
  685. tens = 0;
  686. goto LookupNumExit;
  687. }
  688. // 20..99
  689. if (tens > 1)
  690. {
  691. if (ones)
  692. {
  693. pones = VOX_LookupNumber( pGroupName, ones );
  694. cnew++;
  695. }
  696. ptens = VOX_LookupNumber( pGroupName, 18 + tens);
  697. cnew++;
  698. }
  699. LookupNumExit:
  700. // return values
  701. *pcnew = cnew;
  702. // return
  703. switch (cnew)
  704. {
  705. default:
  706. *ppszNew = NULL;
  707. return;
  708. case 1: // 1..19,20,30,40,50,60,70,80,90,100,200,300
  709. *ppszNew = pones ? pones : (ptens ? ptens : (phundreds ? phundreds : NULL));
  710. return;
  711. case 2:
  712. if (ptens && pones)
  713. {
  714. *ppszNew = ptens;
  715. *ppszNew1 = pones;
  716. }
  717. else if (phundreds && pones)
  718. {
  719. *ppszNew = phundreds;
  720. *ppszNew1 = pones;
  721. }
  722. else if (phundreds && ptens)
  723. {
  724. *ppszNew = phundreds;
  725. *ppszNew1 = ptens;
  726. }
  727. return;
  728. case 3:
  729. *ppszNew = phundreds;
  730. *ppszNew1 = ptens;
  731. *ppszNew2 = pones;
  732. return;
  733. }
  734. }
  735. // find most recent ent of this type marked as dead
  736. int VOX_LookupLastDeadIndex( int type )
  737. {
  738. float timemax = -1;
  739. int ifound = -1;
  740. int i;
  741. for (i = 0; i < CENTNAMESMAX; i++)
  742. {
  743. if (g_entnames[i].type == type && g_entnames[i].fdied)
  744. {
  745. if (g_entnames[i].timedied >= timemax)
  746. {
  747. timemax = g_entnames[i].timedied;
  748. ifound = i;
  749. }
  750. }
  751. }
  752. return ifound;
  753. }
  754. ConVar snd_vox_globaltimeout("snd_vox_globaltimeout", "300"); // n second timeout to reset global vox words
  755. ConVar snd_vox_seqtimeout("snd_vox_seqtimetout", "300"); // n second timeout to reset global sequential vox words
  756. ConVar snd_vox_sectimeout("snd_vox_sectimetout", "300"); // n second timeout to reset global sector id
  757. ConVar snd_vox_captiontrace( "snd_vox_captiontrace", "0", 0, "Shows sentence name for sentences which are set not to show captions." );
  758. // return index to ent which knows the current sector.
  759. // if no ent found, alloc a new one and establish shector.
  760. // sectors expire after approx 5 minutes.
  761. #define VOXSECTORMAX 20
  762. static float g_vox_lastsectorupdate = 0;
  763. static int g_vox_isector = -1;
  764. char *VOX_LookupSectorVirtual( char *pGroupname )
  765. {
  766. float curtime = g_pSoundServices->GetClientTime();
  767. if (g_vox_isector == -1)
  768. {
  769. g_vox_isector = RandomInt(0, VOXSECTORMAX-1);
  770. }
  771. // update sector every 5 min
  772. if (curtime - g_vox_lastsectorupdate > snd_vox_sectimeout.GetInt())
  773. {
  774. g_vox_isector++;
  775. if (g_vox_isector > VOXSECTORMAX)
  776. g_vox_isector = 1;
  777. g_vox_lastsectorupdate = curtime;
  778. }
  779. return VOX_LookupNumber( pGroupname, g_vox_isector );
  780. }
  781. char *VOX_LookupGlobalVirtual( int type, SoundSource soundsource, char *pGroupName, int iglobal )
  782. {
  783. int i;
  784. float curtime = g_pSoundServices->GetClientTime();
  785. // look for ent of this type with un-expired global
  786. for (i = 0; i < CENTNAMESMAX; i++)
  787. {
  788. if (g_entnames[i].type == type)
  789. {
  790. if (curtime - g_entnames[i].timestamp[iglobal] <= snd_vox_globaltimeout.GetInt())
  791. {
  792. // if this ent has an un-expired global, return it, otherwise break
  793. if (g_entnames[i].pszglobal[iglobal])
  794. return g_entnames[i].pszglobal[iglobal];
  795. else
  796. break;
  797. }
  798. }
  799. }
  800. // if not found, construct a new global for this ent
  801. // pick random word from groupname
  802. char *psz = VOX_LookupRndVirtual( pGroupName );
  803. // get existing ent index, or index to new slot
  804. int ient = VOX_LookupEntIndex( type, soundsource, true );
  805. g_entnames[ient].timestamp[iglobal] = curtime;
  806. g_entnames[ient].pszglobal[iglobal] = psz;
  807. return psz;
  808. }
  809. // lookup global values in group in sequence - get next value
  810. // in sequence. sequence counter expires every 2.5 minutes.
  811. char *VOX_LookupGlobalSeqVirtual( int type, SoundSource soundsource, char *pGroupName, int iglobal )
  812. {
  813. int i;
  814. int ient;
  815. float curtime = g_pSoundServices->GetClientTime();
  816. // look for ent of this type with un-expired global
  817. for (i = 0; i < CENTNAMESMAX; i++)
  818. {
  819. if (g_entnames[i].type == type)
  820. {
  821. if (curtime - g_entnames[i].timestampseq[iglobal] <= (snd_vox_seqtimeout.GetInt()/2))
  822. {
  823. // if first ent found has an un-expired global sequence set,
  824. // get next value in sequence, otherwise break
  825. ient = i;
  826. goto Pick_next;
  827. }
  828. else
  829. {
  830. // global has expired - reset sequence
  831. ient = i;
  832. g_entnames[ient].iseq[iglobal] = 0;
  833. goto Pick_next;
  834. }
  835. }
  836. }
  837. // if not found, construct a new sequential global for this ent
  838. ient = VOX_LookupEntIndex( type, soundsource, true );
  839. // pick next word from groupname
  840. Pick_next:
  841. int ipick = g_entnames[ient].iseq[iglobal];
  842. int ipicknext = 0;
  843. char *psz = VOX_LookupSentenceByIndex( pGroupName, ipick, &ipicknext );
  844. g_entnames[ient].iseq[iglobal] = ipicknext;
  845. // get existing ent index, or index to new slot
  846. g_entnames[ient].timestampseq[iglobal] = curtime;
  847. g_entnames[ient].pszglobalseq[iglobal] = psz;
  848. return psz;
  849. }
  850. // insert new words into rgpparseword at 'ireplace' slot
  851. void VOX_InsertWords( int ireplace, int cnew, char *pszNew, char *pszNew1, char *pszNew2 )
  852. {
  853. if ( cnew )
  854. {
  855. // make space in rgpparseword for 'cnew - 1' new words
  856. int ccopy = cnew - 1; // number of new slots we need
  857. int j;
  858. if (ccopy)
  859. {
  860. for (j = CVOXWORDMAX-1; j > ireplace + ccopy; j--)
  861. rgpparseword[j] = rgpparseword[j - ccopy ];
  862. }
  863. // replace rgpparseword entry(s) with the substitued name(s)
  864. rgpparseword[ireplace] = pszNew;
  865. if ( cnew == 2 || cnew == 3)
  866. rgpparseword[ireplace+1] = pszNew1;
  867. if ( cnew == 3 )
  868. rgpparseword[ireplace+2] = pszNew2;
  869. }
  870. }
  871. // remove 'silent' word from rgpparseword
  872. void VOX_DeleteWord( int iword )
  873. {
  874. if (iword < 0 || iword >= CVOXWORDMAX)
  875. return;
  876. rgpparseword[iword] = 0;
  877. // slide all words > iword up into vacated slot
  878. for (int j = iword; j < CVOXWORDMAX-1; j++)
  879. rgpparseword[j] = rgpparseword[j+1];
  880. }
  881. // get global list of map names from sentences.txt
  882. // map names are stored in order in V_MAPNAMES group
  883. void VOX_LookupMapnames( void )
  884. {
  885. // get group V_MAPNAMES
  886. int i;
  887. char *psz;
  888. int inext = 0;
  889. for (i = 0; i < CVOXMAPNAMESMAX; i++)
  890. {
  891. // step sequentially through group - return ptr to 1st word in each group (map name)
  892. psz = VOX_LookupSentenceByIndex( "V_MAPNAME", i, &inext );
  893. if (!psz)
  894. return;
  895. g_rgmapnames[i] = psz;
  896. g_cmapnames++;
  897. }
  898. }
  899. // get index of current map name
  900. // return 0 as default index if not found
  901. int VOX_GetMapNameIndex( const char *pszmapname )
  902. {
  903. for (int i = 0; i < g_cmapnames; i++)
  904. {
  905. if ( Q_strstr( pszmapname, g_rgmapnames[i] ) )
  906. return i;
  907. }
  908. return 0;
  909. }
  910. // look for virtual 'V_' values in rgpparseword.
  911. // V_MYNAME - replace with saved name value (based on type + entity)
  912. // - if no saved name, create one and save
  913. // V_MYNUM - replace with saved number value (based on type + entity)
  914. // - if no saved num, create on and save
  915. // V_RNDNUM - grab a random number string from V_RNDNUM_<type>
  916. // V_RNDNAME - grab a random name string from V_RNDNAME_<type>
  917. // replace any 'V_' values with actual string names in rgpparseword
  918. extern ConVar host_map;
  919. inline bool IsVirtualName( const char *pName )
  920. {
  921. return (pName[0] == 'V' && pName[1] == '_');
  922. }
  923. void VOX_ReplaceVirtualNames( channel_t *pchan )
  924. {
  925. // for each word in the sentence, check for V_, if found
  926. // replace virtual word with saved word or rnd word
  927. int i = 0;
  928. char *pszNew = NULL;
  929. char *pszNew1 = NULL;
  930. char *pszNew2 = NULL;
  931. int iname = -1;
  932. int cnew = 0;
  933. bool fbymap;
  934. char *pszmaptoken;
  935. SoundSource soundsource = pchan ? pchan->soundsource : 0;
  936. const char *pszmap = host_map.GetString();
  937. // get global list of map names from sentences.txt
  938. while (rgpparseword[i])
  939. {
  940. if ( IsVirtualName( rgpparseword[i] ) )
  941. {
  942. iname = -1;
  943. cnew = 0;
  944. pszNew = NULL;
  945. pszNew1 = NULL;
  946. pszNew2 = NULL;
  947. char szparseword[256];
  948. int slen = Q_strlen(rgpparseword[i]);
  949. char chtype = rgpparseword[i][slen-1];
  950. // copy word to temp location so we can perform in-place substitutions
  951. V_strcpy_safe(szparseword, rgpparseword[i]);
  952. // fbymap is true if lookup is performed via mapname instead of via ordinal
  953. pszmaptoken = ( Q_strstr(szparseword, "_MAP__") );
  954. fbymap = (pszmaptoken == NULL ? false : true);
  955. if (fbymap)
  956. {
  957. int imap = VOX_GetMapNameIndex( pszmap );
  958. imap = clamp (imap, 0, 99);
  959. // replace last 2 characters in _MAP__ substring
  960. // with imap - this effectively makes all
  961. // '_map_' lookups relative to the mapname
  962. if ( imap >= 10 )
  963. {
  964. pszmaptoken[4] = (imap/10) + '0';
  965. pszmaptoken[5] = (imap%10) + '0';
  966. }
  967. else
  968. {
  969. pszmaptoken[4] = '0';
  970. pszmaptoken[5] = imap + '0';
  971. }
  972. }
  973. if ( Q_strstr(szparseword, "V_MYNAME") )
  974. {
  975. iname = 1;
  976. }
  977. else if ( Q_strstr(szparseword, "V_MYNUM") )
  978. {
  979. iname = 0;
  980. }
  981. if ( iname >= 0 )
  982. {
  983. // lookup ent & type, return static, null terminated string
  984. // if no saved string, create one
  985. pszNew = VOX_LookupMyVirtual( iname, szparseword, chtype, soundsource);
  986. cnew = 1;
  987. }
  988. else
  989. {
  990. if ( Q_strstr(szparseword, "V_RND") )
  991. {
  992. // lookup random first word from this named group,
  993. // return static, null terminated string
  994. pszNew = VOX_LookupRndVirtual( szparseword );
  995. cnew = 1;
  996. }
  997. else if ( Q_strstr(szparseword, "V_DIST") )
  998. {
  999. // get range from ent to player, return pointers to new words
  1000. VOX_LookupRangeHeadingOrGrid( 0, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, true );
  1001. }
  1002. else if ( Q_strstr(szparseword, "V_DIR") )
  1003. {
  1004. // get heading from ent to player, return pointers to new words
  1005. VOX_LookupRangeHeadingOrGrid( 1, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, false);
  1006. }
  1007. else if ( Q_strstr(szparseword, "V_IDIED") )
  1008. {
  1009. // SILENT MARKER - this ent died - mark as dead and timestamp
  1010. int ient = VOX_LookupEntIndex( chtype, soundsource, false);
  1011. if (ient < 0)
  1012. {
  1013. // if not found, allocate new ent, give him a name & number, mark as dead
  1014. char szgroup1[32];
  1015. char szgroup2[32];
  1016. V_strcpy_safe(szgroup1, "V_MYNAME");
  1017. szgroup1[8] = chtype;
  1018. szgroup1[9] = 0;
  1019. V_strcpy_safe(szgroup2, "V_MYNUM");
  1020. szgroup2[7] = chtype;
  1021. szgroup2[8] = 0;
  1022. ient = VOX_LookupEntIndex( chtype, soundsource, true);
  1023. g_entnames[ient].pszname = VOX_LookupRndVirtual( szgroup1 );
  1024. g_entnames[ient].psznum = VOX_LookupRndVirtual( szgroup2 );
  1025. }
  1026. g_entnames[ient].fdied = true;
  1027. g_entnames[ient].timedied = g_pSoundServices->GetClientTime();
  1028. // clear this 'silent' word from rgpparseword
  1029. VOX_DeleteWord(i);
  1030. }
  1031. else if ( Q_strstr(szparseword, "V_WHODIED") )
  1032. {
  1033. // get last dead unit of this type
  1034. int ient = VOX_LookupLastDeadIndex( chtype );
  1035. // get name and number
  1036. if (ient >= 0)
  1037. {
  1038. cnew = 1;
  1039. pszNew = g_entnames[ient].pszname;
  1040. pszNew1 = g_entnames[ient].psznum;
  1041. if (pszNew1)
  1042. cnew++;
  1043. }
  1044. else
  1045. {
  1046. // no dead units, just clear V_WHODIED
  1047. VOX_DeleteWord(i);
  1048. }
  1049. }
  1050. else if ( Q_strstr(szparseword, "V_SECTOR") )
  1051. {
  1052. // sectors are fictional - they simply
  1053. // increase sequentially and expire every 5 minutes
  1054. pszNew = VOX_LookupSectorVirtual( szparseword );
  1055. if (pszNew)
  1056. cnew = 1;
  1057. }
  1058. else if ( Q_strstr(szparseword, "V_GRIDX") )
  1059. {
  1060. // player x position in 10 meter increments
  1061. VOX_LookupRangeHeadingOrGrid( 2, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, true );
  1062. }
  1063. else if ( Q_strstr(szparseword, "V_GRIDY") )
  1064. {
  1065. // player y position in 10 meter increments
  1066. VOX_LookupRangeHeadingOrGrid( 3, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, true );
  1067. }
  1068. else if ( Q_strstr(szparseword, "V_G0_") )
  1069. {
  1070. // 4 rnd globals per type, globals expire after 5 minutes
  1071. // used for target designation, master sector code name etc.
  1072. pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 0 );
  1073. if (pszNew)
  1074. cnew = 1;
  1075. }
  1076. else if ( Q_strstr(szparseword, "V_G1_") )
  1077. {
  1078. // 4 rnd globals per type, globals expire after 5 minutes
  1079. // used for target designation, master sector code name etc.
  1080. pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 1 );
  1081. if (pszNew)
  1082. cnew = 1;
  1083. }
  1084. else if ( Q_strstr(szparseword, "V_G2_") )
  1085. {
  1086. // 4 rnd globals per type, globals expire after 5 minutes
  1087. // used for target designation, master sector code name etc.
  1088. pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 2 );
  1089. if (pszNew)
  1090. cnew = 1;
  1091. }
  1092. else if ( Q_strstr(szparseword, "V_G3_") )
  1093. {
  1094. // 4 rnd globals per type, globals expire after 5 minutes
  1095. // used for target designation, master sector code name etc.
  1096. pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 3 );
  1097. if (pszNew)
  1098. cnew = 1;
  1099. }
  1100. else if ( Q_strstr(szparseword, "V_SEQG0_") )
  1101. {
  1102. // 4 sequential globals per type, selected sequentially in list
  1103. // used for total target hit count etc.
  1104. pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 0 );
  1105. if (pszNew)
  1106. cnew = 1;
  1107. }
  1108. else if ( Q_strstr(szparseword, "V_SEQG1_") )
  1109. {
  1110. // 4 sequential globals per type, selected sequentially in list
  1111. // used for total target hit count etc.
  1112. pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 1 );
  1113. if (pszNew)
  1114. cnew = 1;
  1115. }
  1116. else if ( Q_strstr(szparseword, "V_SEQG2_") )
  1117. {
  1118. // 4 sequential globals per type, selected sequentially in list
  1119. // used for total target hit count etc.
  1120. pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 2 );
  1121. if (pszNew)
  1122. cnew = 1;
  1123. }
  1124. else if ( Q_strstr(szparseword, "V_SEQG3_") )
  1125. {
  1126. // 4 sequential globals per type, selected sequentially in list
  1127. // used for total target hit count etc.
  1128. pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 3 );
  1129. if (pszNew)
  1130. cnew = 1;
  1131. }
  1132. }
  1133. // insert up to 3 new words into rgpparseword at 'i' location
  1134. VOX_InsertWords( i, cnew, pszNew, pszNew1, pszNew2 );
  1135. }
  1136. i++;
  1137. }
  1138. }
  1139. void VOX_Precache( IEngineSound *pSoundSystem, int sentenceIndex, const char *pPathOverride = NULL )
  1140. {
  1141. voxword_t rgvoxword[CVOXWORDMAX];
  1142. char buffer[512];
  1143. char szpath[MAX_PATH];
  1144. char pathbuffer[MAX_PATH];
  1145. char *pWords[CVOXWORDMAX]; // array of pointers to parsed words
  1146. if ( !IsVirtualName(g_Sentences[sentenceIndex].pName))
  1147. {
  1148. g_Sentences[sentenceIndex].isPrecached = true;
  1149. }
  1150. memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX);
  1151. char *psz = (char *)(g_Sentences[sentenceIndex].pName + Q_strlen(g_Sentences[sentenceIndex].pName) + 1);
  1152. // get directory from string, advance psz
  1153. psz = VOX_GetDirectory(szpath, sizeof( szpath ), psz );
  1154. Q_strncpy(buffer, psz, sizeof( buffer ) );
  1155. psz = buffer;
  1156. if ( pPathOverride )
  1157. {
  1158. Q_strncpy(szpath, pPathOverride, sizeof(szpath));
  1159. }
  1160. // parse sentence (also inserts null terminators between words)
  1161. VOX_ParseString(psz);
  1162. int i = 0, count = 0;
  1163. // copy the parsed words out of the globals
  1164. for ( i = 0; rgpparseword[i]; i++ )
  1165. {
  1166. pWords[i] = rgpparseword[i];
  1167. count++;
  1168. }
  1169. int cword = 0;
  1170. for ( i = 0; i < count; i++ )
  1171. {
  1172. if ( IsVirtualName(pWords[i]) )
  1173. {
  1174. CUtlVector< WordBuf > list;
  1175. VOX_BuildVirtualNameList( pWords[i], list );
  1176. int c = list.Count();
  1177. for ( int j = 0 ; j < c; ++j )
  1178. {
  1179. Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, list[j].word );
  1180. pSoundSystem->PrecacheSound( pathbuffer, false );
  1181. }
  1182. }
  1183. else
  1184. {
  1185. // Get any pitch, volume, start, end params into voxword
  1186. if (VOX_ParseWordParams(pWords[i], &rgvoxword[cword], i == 0))
  1187. {
  1188. // this is a valid word (as opposed to a parameter block)
  1189. Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, pWords[i] );
  1190. // find name, if already in cache, mark voxword
  1191. // so we don't discard when word is done playing
  1192. pSoundSystem->PrecacheSound( pathbuffer, false );
  1193. cword++;
  1194. }
  1195. }
  1196. }
  1197. }
  1198. void VOX_PrecacheSentenceGroup( IEngineSound *pSoundSystem, const char *pGroupName, const char *pPathOverride )
  1199. {
  1200. int i;
  1201. int len = Q_strlen( pGroupName );
  1202. for ( i = 0; i < g_Sentences.Count(); i++ )
  1203. {
  1204. if ( !g_Sentences[i].isPrecached && !Q_strncasecmp( g_Sentences[i].pName, pGroupName, len ) )
  1205. {
  1206. VOX_Precache( pSoundSystem, i, pPathOverride );
  1207. }
  1208. }
  1209. }
  1210. // link all sounds in sentence, start playing first word.
  1211. // return number of words loaded
  1212. void VOX_LoadSound( channel_t *pchan, const char *pszin )
  1213. {
  1214. #ifndef SWDS
  1215. char buffer[512];
  1216. int i, cword;
  1217. char pathbuffer[MAX_PATH];
  1218. char szpath[MAX_PATH];
  1219. voxword_t rgvoxword[CVOXWORDMAX];
  1220. char *psz;
  1221. bool emitcaption = false;
  1222. CUtlSymbol captionSymbol = UTL_INVAL_SYMBOL;
  1223. float duration = 0.0f;
  1224. if (!pszin)
  1225. return;
  1226. memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX);
  1227. memset(buffer, 0, sizeof(buffer));
  1228. // lookup actual string in g_Sentences,
  1229. // set pointer to string data
  1230. psz = VOX_LookupString(pszin, NULL, &emitcaption, &captionSymbol, &duration );
  1231. if (!psz)
  1232. {
  1233. DevMsg ("VOX_LoadSound: no sentence named %s\n",pszin);
  1234. return;
  1235. }
  1236. // get directory from string, advance psz
  1237. psz = VOX_GetDirectory(szpath, sizeof( szpath ), psz );
  1238. if ( Q_strlen(psz) > sizeof(buffer) - 1 )
  1239. {
  1240. DevMsg ("VOX_LoadSound: sentence is too long %s\n",psz);
  1241. return;
  1242. }
  1243. // copy into buffer
  1244. Q_strncpy(buffer, psz, sizeof( buffer ) );
  1245. psz = buffer;
  1246. // parse sentence (also inserts null terminators between words)
  1247. VOX_ParseString(psz);
  1248. // replace any 'V_' values with actual string names in rgpparseword
  1249. VOX_ReplaceVirtualNames( pchan );
  1250. // for each word in the sentence, construct the filename,
  1251. // lookup the sfx and save each pointer in a temp array
  1252. i = 0;
  1253. cword = 0;
  1254. char captionstream[ 1024 ];
  1255. char groupname[ 512 ];
  1256. Q_strncpy( groupname, pszin, sizeof( groupname ) );
  1257. int len = Q_strlen( groupname );
  1258. while ( len > 0 && V_isdigit( groupname[ len - 1 ] ) )
  1259. {
  1260. groupname[ len - 1 ] = 0;
  1261. --len;
  1262. }
  1263. Q_snprintf( captionstream, sizeof( captionstream ), "%s ", groupname );
  1264. while (rgpparseword[i])
  1265. {
  1266. // Get any pitch, volume, start, end params into voxword
  1267. if (VOX_ParseWordParams(rgpparseword[i], &rgvoxword[cword], i == 0))
  1268. {
  1269. // this is a valid word (as opposed to a parameter block)
  1270. Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, rgpparseword[i] );
  1271. // find name, if already in cache, mark voxword
  1272. // so we don't discard when word is done playing
  1273. rgvoxword[cword].sfx = S_FindName(pathbuffer,
  1274. &(rgvoxword[cword].fKeepCached));
  1275. // JAY: HACKHACK: Keep all sentences cached for now
  1276. rgvoxword[cword].fKeepCached = 1;
  1277. char captiontoken[ 128 ];
  1278. Q_snprintf( captiontoken, sizeof( captiontoken ), "S(%s%s) ", szpath, rgpparseword[i] );
  1279. Q_strncat( captionstream, captiontoken, sizeof( captionstream ), COPY_ALL_CHARACTERS );
  1280. cword++;
  1281. }
  1282. i++;
  1283. }
  1284. pchan->pMixer = NULL;
  1285. if (cword)
  1286. {
  1287. // some 'virtual' sentences can end up with 0 words
  1288. // if no words, then pchan->pMixer is null; chan will be released right away.
  1289. pchan->pMixer = CreateSentenceMixer( rgvoxword );
  1290. if ( !pchan->pMixer )
  1291. return;
  1292. pchan->flags.isSentence = true;
  1293. pchan->sfx = rgvoxword[0].sfx;
  1294. Assert(pchan->sfx);
  1295. if ( g_pSoundServices )
  1296. {
  1297. if ( emitcaption )
  1298. {
  1299. if ( captionSymbol != UTL_INVAL_SYMBOL )
  1300. {
  1301. g_pSoundServices->EmitCloseCaption( captionSymbol.String(), duration );
  1302. if ( snd_vox_captiontrace.GetBool() )
  1303. {
  1304. Msg( "Vox: caption '%s'\n", captionSymbol.String() );
  1305. }
  1306. }
  1307. else
  1308. {
  1309. g_pSoundServices->EmitSentenceCloseCaption( captionstream );
  1310. if ( snd_vox_captiontrace.GetBool() )
  1311. {
  1312. Msg( "Vox: captionstream '%s'\n", captionstream );
  1313. }
  1314. }
  1315. }
  1316. else
  1317. {
  1318. if ( snd_vox_captiontrace.GetBool() )
  1319. {
  1320. Msg( "Vox: No caption for '%s'\n", pszin ? pszin : "NULL" );
  1321. }
  1322. }
  1323. }
  1324. }
  1325. #endif
  1326. }
  1327. static bool CCPairLessFunc( const ccpair& lhs, const ccpair& rhs )
  1328. {
  1329. return Q_stricmp( lhs.token.word, rhs.token.word ) < 0;
  1330. }
  1331. void VOX_AddNumbers( char *pGroupName, CUtlVector< WordBuf >& list )
  1332. {
  1333. // construct group name from V_NUMBERS + TYPE
  1334. for ( int i = 0; i <= 30; ++i )
  1335. {
  1336. char sznumbers[16];
  1337. int glen = Q_strlen(pGroupName);
  1338. int slen = Q_strlen("V_NUMBERS");
  1339. V_strcpy_safe(sznumbers, "V_NUMBERS");
  1340. // insert type character
  1341. sznumbers[slen] = pGroupName[glen-1];
  1342. sznumbers[slen+1] = 0;
  1343. WordBuf w;
  1344. // w.Set( VOX_LookupString( VOX_LookupSentenceByIndex( sznumbers, i, NULL ), NULL ) );
  1345. w.Set( VOX_LookupSentenceByIndex( sznumbers, i, NULL ) );
  1346. list.AddToTail( w );
  1347. }
  1348. }
  1349. void VOX_AddRndVirtual( char *pGroupName, CUtlVector< WordBuf >& list )
  1350. {
  1351. // get group index
  1352. int isentenceg = VOX_GroupIndexFromName( pGroupName );
  1353. if ( isentenceg < 0)
  1354. return;
  1355. char szsentencename[32];
  1356. char const *szgroupname = g_SentenceGroups[ isentenceg ].GroupName();
  1357. // get pointer to sentence name within group, using lru
  1358. for ( int snum = 0; snum < g_SentenceGroups[ isentenceg ].count; ++snum )
  1359. {
  1360. Q_snprintf( szsentencename, sizeof( szsentencename ), "%s%d", szgroupname, snum );
  1361. char *psz = VOX_LookupString( szsentencename[0] == '!' ? szsentencename+1 : szsentencename, NULL);
  1362. if ( psz )
  1363. {
  1364. WordBuf w;
  1365. w.Set( psz );
  1366. list.AddToTail( w );
  1367. }
  1368. }
  1369. }
  1370. void VOX_AddMyVirtualWords( int iname, char *pGroupName, char chtype, CUtlVector< WordBuf >& list )
  1371. {
  1372. VOX_AddRndVirtual( pGroupName, list );
  1373. }
  1374. void VOX_BuildVirtualNameList( char *word, CUtlVector< WordBuf >& list )
  1375. {
  1376. // for each word in the sentence, check for V_, if found
  1377. // replace virtual word with saved word or rnd word
  1378. int iname = -1;
  1379. bool fbymap;
  1380. char *pszmaptoken;
  1381. char szparseword[256];
  1382. int slen = Q_strlen(word);
  1383. char chtype = word[slen-1];
  1384. // copy word to temp location so we can perform in-place substitutions
  1385. Q_strncpy( szparseword, word, sizeof( szparseword ) );
  1386. // fbymap is true if lookup is performed via mapname instead of via ordinal
  1387. pszmaptoken = ( Q_strstr(szparseword, "_MAP__") );
  1388. fbymap = (pszmaptoken == NULL ? false : true);
  1389. if (fbymap)
  1390. {
  1391. for ( int imap = 0; imap < g_cmapnames; ++imap )
  1392. {
  1393. // replace last 2 characters in _MAP__ substring
  1394. // with imap - this effectively makes all
  1395. // '_map_' lookups relative to the mapname
  1396. pszmaptoken[4] = '0';
  1397. if (imap < 10)
  1398. Q_snprintf( &(pszmaptoken[5]), 1, "%1d", imap );
  1399. else
  1400. Q_snprintf( &(pszmaptoken[4]), 2, "%d", imap );
  1401. // Recurse...
  1402. VOX_BuildVirtualNameList( szparseword, list );
  1403. }
  1404. return;
  1405. }
  1406. if ( Q_strstr(szparseword, "V_MYNAME") )
  1407. {
  1408. iname = 1;
  1409. }
  1410. else if ( Q_strstr(szparseword, "V_MYNUM") )
  1411. {
  1412. iname = 0;
  1413. }
  1414. if ( iname >= 0 )
  1415. {
  1416. // lookup ent & type, return static, null terminated string
  1417. // if no saved string, create one
  1418. VOX_AddMyVirtualWords( iname, szparseword, chtype, list );
  1419. }
  1420. else
  1421. {
  1422. if ( Q_strstr(szparseword, "V_RND") )
  1423. {
  1424. // lookup random first word from this named group,
  1425. // return static, null terminated string
  1426. VOX_AddRndVirtual( szparseword, list );
  1427. }
  1428. else if ( Q_strstr(szparseword, "V_DIST") )
  1429. {
  1430. VOX_AddNumbers( szparseword, list );
  1431. }
  1432. else if ( Q_strstr(szparseword, "V_DIR") )
  1433. {
  1434. VOX_AddNumbers( szparseword, list );
  1435. }
  1436. else if ( Q_strstr(szparseword, "V_IDIED") )
  1437. {
  1438. // SILENT MARKER - this ent died - mark as dead and timestamp
  1439. // if not found, allocate new ent, give him a name & number, mark as dead
  1440. char szgroup1[32];
  1441. char szgroup2[32];
  1442. V_strcpy_safe(szgroup1, "V_MYNAME");
  1443. szgroup1[8] = chtype;
  1444. szgroup1[9] = 0;
  1445. V_strcpy_safe(szgroup2, "V_MYNUM");
  1446. szgroup2[7] = chtype;
  1447. szgroup2[8] = 0;
  1448. VOX_BuildVirtualNameList( szgroup1, list );
  1449. VOX_BuildVirtualNameList( szgroup2, list );
  1450. return;
  1451. }
  1452. else if ( Q_strstr(szparseword, "V_WHODIED") )
  1453. {
  1454. // get last dead unit of this type
  1455. /*
  1456. int ient = VOX_LookupLastDeadIndex( chtype );
  1457. // get name and number
  1458. if (ient >= 0)
  1459. {
  1460. cnew = 1;
  1461. pszNew = g_entnames[ient].pszname;
  1462. pszNew1 = g_entnames[ient].psznum;
  1463. if (pszNew1)
  1464. cnew++;
  1465. }
  1466. else
  1467. {
  1468. // no dead units, just clear V_WHODIED
  1469. VOX_DeleteWord(i);
  1470. }
  1471. */
  1472. }
  1473. else if ( Q_strstr(szparseword, "V_SECTOR") )
  1474. {
  1475. VOX_AddNumbers( szparseword, list );
  1476. }
  1477. else if ( Q_strstr(szparseword, "V_GRIDX") )
  1478. {
  1479. VOX_AddNumbers( szparseword, list );
  1480. }
  1481. else if ( Q_strstr(szparseword, "V_GRIDY") )
  1482. {
  1483. VOX_AddNumbers( szparseword, list );
  1484. }
  1485. else if ( Q_strstr(szparseword, "V_G0_") )
  1486. {
  1487. VOX_AddRndVirtual( szparseword, list );
  1488. }
  1489. else if ( Q_strstr(szparseword, "V_G1_") )
  1490. {
  1491. VOX_AddRndVirtual( szparseword, list );
  1492. }
  1493. else if ( Q_strstr(szparseword, "V_G2_") )
  1494. {
  1495. VOX_AddRndVirtual( szparseword, list );
  1496. }
  1497. else if ( Q_strstr(szparseword, "V_G3_") )
  1498. {
  1499. VOX_AddRndVirtual( szparseword, list );
  1500. }
  1501. else if ( Q_strstr(szparseword, "V_SEQG0_") )
  1502. {
  1503. VOX_AddRndVirtual( szparseword, list );
  1504. }
  1505. else if ( Q_strstr(szparseword, "V_SEQG1_") )
  1506. {
  1507. VOX_AddRndVirtual( szparseword, list );
  1508. }
  1509. else if ( Q_strstr(szparseword, "V_SEQG2_") )
  1510. {
  1511. VOX_AddRndVirtual( szparseword, list );
  1512. }
  1513. else if ( Q_strstr(szparseword, "V_SEQG3_") )
  1514. {
  1515. VOX_AddRndVirtual( szparseword, list );
  1516. }
  1517. }
  1518. if ( Q_strnicmp( szparseword, "V_", 2 ) )
  1519. {
  1520. WordBuf w;
  1521. w.Set( szparseword );
  1522. list.AddToTail( w );
  1523. }
  1524. }
  1525. //-----------------------------------------------------------------------------
  1526. // Purpose: For generating reslists, adds the wavefile to the dictionary
  1527. // Input : *fn -
  1528. //-----------------------------------------------------------------------------
  1529. void VOX_Touch( char const *fn, CUtlDict< int, int >& list )
  1530. {
  1531. if ( list.Find( fn ) == list.InvalidIndex() )
  1532. {
  1533. list.Insert( fn );
  1534. }
  1535. }
  1536. //-----------------------------------------------------------------------------
  1537. // Purpose: Iterates the touch list and touches all referenced .wav files.
  1538. // Input : int -
  1539. // list -
  1540. //-----------------------------------------------------------------------------
  1541. void VOX_TouchSounds( CUtlDict< int, int >& list, CUtlRBTree< ccpair, int >& ccpairs, bool spewsentences )
  1542. {
  1543. int i;
  1544. for ( i = list.First(); i != list.InvalidIndex(); i = list.Next( i ) )
  1545. {
  1546. char const *fn = list.GetElementName( i );
  1547. // Msg( "touch %s\n", fn );
  1548. char expanded[ 512 ];
  1549. Q_snprintf( expanded, sizeof( expanded ), "sound/%s", fn );
  1550. FileHandle_t fh = g_pFileSystem->Open( expanded, "rb" );
  1551. if ( FILESYSTEM_INVALID_HANDLE != fh )
  1552. {
  1553. g_pFileSystem->Close( fh );
  1554. }
  1555. }
  1556. if ( spewsentences )
  1557. {
  1558. for ( i = ccpairs.FirstInorder() ; i != ccpairs.InvalidIndex(); i = ccpairs.NextInorder( i ) )
  1559. {
  1560. ccpair& pair = ccpairs[ i ];
  1561. Msg( "\"%s\"\t\"%s\"\n",
  1562. pair.token.word,
  1563. pair.value.word );
  1564. }
  1565. FileHandle_t fh = g_pFileSystem->Open( "sentences.m3u", "wt", "GAME" );
  1566. if ( FILESYSTEM_INVALID_HANDLE != fh )
  1567. {
  1568. for ( i = ccpairs.FirstInorder() ; i != ccpairs.InvalidIndex(); i = ccpairs.NextInorder( i ) )
  1569. {
  1570. ccpair& pair = ccpairs[ i ];
  1571. char outline[ 512 ];
  1572. Q_snprintf( outline, sizeof( outline ), "%s\n", pair.fullpath.word );
  1573. g_pFileSystem->Write( outline, Q_strlen(outline), fh );
  1574. }
  1575. g_pFileSystem->Close( fh );
  1576. }
  1577. }
  1578. }
  1579. // link all sounds in sentence, start playing first word.
  1580. // return number of words loaded
  1581. void VOX_TouchSound( const char *pszin, CUtlDict< int, int >& filelist, CUtlRBTree< ccpair, int >& ccpairs, bool spewsentences )
  1582. {
  1583. #ifndef SWDS
  1584. char buffer[512];
  1585. int i, cword;
  1586. char pathbuffer[MAX_PATH];
  1587. char szpath[MAX_PATH];
  1588. voxword_t rgvoxword[CVOXWORDMAX];
  1589. char *psz;
  1590. if (!pszin)
  1591. return;
  1592. memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX);
  1593. memset(buffer, 0, sizeof(buffer));
  1594. // lookup actual string in g_Sentences,
  1595. // set pointer to string data
  1596. psz = VOX_LookupString(pszin, NULL);
  1597. if (!psz)
  1598. {
  1599. DevMsg ("VOX_TouchSound: no sentence named %s\n",pszin);
  1600. return;
  1601. }
  1602. // get directory from string, advance psz
  1603. psz = VOX_GetDirectory(szpath, sizeof( szpath ), psz );
  1604. if ( Q_strlen(psz) > sizeof(buffer) - 1 )
  1605. {
  1606. DevMsg ("VOX_TouchSound: sentence is too long %s\n",psz);
  1607. return;
  1608. }
  1609. // copy into buffer
  1610. Q_strncpy(buffer, psz, sizeof( buffer ) );
  1611. psz = buffer;
  1612. // parse sentence (also inserts null terminators between words)
  1613. VOX_ParseString(psz);
  1614. // for each word in the sentence, construct the filename,
  1615. // lookup the sfx and save each pointer in a temp array
  1616. i = 0;
  1617. cword = 0;
  1618. CUtlVector< WordBuf > rep;
  1619. while (rgpparseword[i])
  1620. {
  1621. // Get any pitch, volume, start, end params into voxword
  1622. if ( VOX_ParseWordParams(rgpparseword[i], &rgvoxword[cword], i == 0 ) )
  1623. {
  1624. // Iterate all virtuals here...
  1625. if ( !Q_strnicmp( rgpparseword[i], "V_", 2 ) )
  1626. {
  1627. CUtlVector< WordBuf > list;
  1628. VOX_BuildVirtualNameList( rgpparseword[i], list );
  1629. int c = list.Count();
  1630. for ( int j = 0 ; j < c; ++j )
  1631. {
  1632. char name[ 256 ];
  1633. Q_snprintf( name, sizeof( name ), "%s", list[ j ].word );
  1634. if ( !Q_strnicmp( name, "V_", 2 ) )
  1635. {
  1636. Warning( "VOX_TouchSound didn't resolve virtual token %s!\n", name );
  1637. }
  1638. Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, name );
  1639. VOX_Touch( pathbuffer, filelist );
  1640. WordBuf w;
  1641. if ( j == 0 )
  1642. {
  1643. w.Set( name );
  1644. rep.AddToTail( w );
  1645. }
  1646. ccpair pair;
  1647. Q_snprintf( pair.token.word, sizeof( pair.token.word ), "S(%s%s)", szpath, name );
  1648. pair.value.Set( name );
  1649. Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s/sound/%s%s.wav", g_pSoundServices->GetGameDir(), szpath, name );
  1650. Q_FixSlashes( pathbuffer, '\\' );
  1651. pair.fullpath.Set( pathbuffer );
  1652. if ( ccpairs.Find( pair ) == ccpairs.InvalidIndex() )
  1653. {
  1654. ccpairs.Insert( pair );
  1655. }
  1656. }
  1657. }
  1658. else
  1659. {
  1660. // this is a valid word (as opposed to a parameter block)
  1661. Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, rgpparseword[i] );
  1662. VOX_Touch( pathbuffer, filelist );
  1663. WordBuf w;
  1664. w.Set( rgpparseword[ i ] );
  1665. rep.AddToTail( w );
  1666. ccpair pair;
  1667. Q_snprintf( pair.token.word, sizeof( pair.token.word ), "S(%s%s)", szpath, rgpparseword[i] );
  1668. pair.value.Set( rgpparseword[i] );
  1669. Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s/sound/%s%s.wav", g_pSoundServices->GetGameDir(), szpath, rgpparseword[ i ] );
  1670. Q_FixSlashes( pathbuffer, CORRECT_PATH_SEPARATOR );
  1671. pair.fullpath.Set( pathbuffer );
  1672. if ( ccpairs.Find( pair ) == ccpairs.InvalidIndex() )
  1673. {
  1674. ccpairs.Insert( pair );
  1675. }
  1676. }
  1677. }
  1678. i++;
  1679. }
  1680. if ( spewsentences )
  1681. {
  1682. char outbuf[ 1024 ];
  1683. // Build representative text
  1684. outbuf[ 0 ] = 0;
  1685. for ( i = 0; i < rep.Count(); ++i )
  1686. {
  1687. /*
  1688. if ( !Q_stricmp( rep[ i ].word, "_comma" ) )
  1689. {
  1690. if ( i != 0 && Q_strlen( outbuf ) >= 1 )
  1691. {
  1692. outbuf[ Q_strlen( outbuf ) - 1 ] =0;
  1693. }
  1694. // Don't end sentence with comma..
  1695. if ( i != rep.Count() - 1 )
  1696. {
  1697. Q_strncat( outbuf, ", ", sizeof( outbuf ), COPY_ALL_CHARACTERS );
  1698. }
  1699. continue;
  1700. }
  1701. */
  1702. Q_strncat( outbuf, rep[ i ].word, sizeof( outbuf ), COPY_ALL_CHARACTERS );
  1703. if ( i != rep.Count() - 1 )
  1704. {
  1705. Q_strncat( outbuf, " ", sizeof( outbuf ), COPY_ALL_CHARACTERS );
  1706. }
  1707. }
  1708. Msg( " %s\n", outbuf );
  1709. }
  1710. #endif
  1711. }
  1712. //-----------------------------------------------------------------------------
  1713. // Purpose: Take a NULL terminated sentence, and parse any commands contained in
  1714. // {}. The string is rewritten in place with those commands removed.
  1715. //
  1716. // Input : *pSentenceData - sentence data to be modified in place
  1717. // sentenceIndex - global sentence table index for any data that is
  1718. // parsed out
  1719. //-----------------------------------------------------------------------------
  1720. void VOX_ParseLineCommands( char *pSentenceData, int sentenceIndex )
  1721. {
  1722. char tempBuffer[512];
  1723. char *pNext, *pStart;
  1724. int length, tempBufferPos = 0;
  1725. if ( !pSentenceData )
  1726. return;
  1727. pStart = pSentenceData;
  1728. while ( *pSentenceData )
  1729. {
  1730. pNext = ScanForwardUntil( pSentenceData, '{' );
  1731. // Find length of "good" portion of the string (not a {} command)
  1732. length = pNext - pSentenceData;
  1733. if ( tempBufferPos + length > sizeof(tempBuffer) )
  1734. {
  1735. DevMsg("Error! sentence too long!\n" );
  1736. return;
  1737. }
  1738. // Copy good string to temp buffer
  1739. memcpy( tempBuffer + tempBufferPos, pSentenceData, length );
  1740. // Move the copy position
  1741. tempBufferPos += length;
  1742. pSentenceData = pNext;
  1743. // Skip ahead of the opening brace
  1744. if ( *pSentenceData )
  1745. {
  1746. pSentenceData++;
  1747. }
  1748. while ( 1 )
  1749. {
  1750. // Skip whitespace
  1751. while ( *pSentenceData && *pSentenceData <= 32 )
  1752. {
  1753. pSentenceData++;
  1754. }
  1755. // Simple comparison of string commands:
  1756. switch( tolower( *pSentenceData ) )
  1757. {
  1758. case 'l':
  1759. // All commands starting with the letter 'l' here
  1760. if ( !Q_strnicmp( pSentenceData, "len", 3 ) )
  1761. {
  1762. g_Sentences[sentenceIndex].length = atof( pSentenceData + 3 ) ;
  1763. // "len " len + space
  1764. pSentenceData += 4;
  1765. // Skip until next } or whitespace character
  1766. while ( *pSentenceData && ( *pSentenceData != '}' && !( *pSentenceData <= 32 ) ) )
  1767. pSentenceData++;
  1768. }
  1769. break;
  1770. case 'c':
  1771. // This sentence should emit a close caption
  1772. if ( !Q_strnicmp( pSentenceData, "closecaption", 12 ) )
  1773. {
  1774. g_Sentences[sentenceIndex].closecaption = true;
  1775. pSentenceData += 12;
  1776. pSentenceData = (char *)COM_Parse( pSentenceData );
  1777. // Skip until next } or whitespace character
  1778. while ( *pSentenceData && ( *pSentenceData != '}' && !( *pSentenceData <= 32 ) ) )
  1779. pSentenceData++;
  1780. if ( Q_strlen( com_token ) > 0 )
  1781. {
  1782. g_Sentences[sentenceIndex].caption = com_token;
  1783. }
  1784. else
  1785. {
  1786. g_Sentences[sentenceIndex].caption = UTL_INVAL_SYMBOL;
  1787. }
  1788. }
  1789. break;
  1790. case 0:
  1791. default:
  1792. {
  1793. // Skip until next } or whitespace character
  1794. while ( *pSentenceData && ( *pSentenceData != '}' && !( *pSentenceData <= 32 ) ) )
  1795. pSentenceData++;
  1796. }
  1797. break;
  1798. }
  1799. // Done?
  1800. if ( !*pSentenceData || *pSentenceData == '}' )
  1801. {
  1802. break;
  1803. }
  1804. }
  1805. // pSentenceData = ScanForwardUntil( pSentenceData, '}' );
  1806. // Skip the closing brace
  1807. if ( *pSentenceData )
  1808. pSentenceData++;
  1809. // Skip trailing whitespace
  1810. while ( *pSentenceData && *pSentenceData <= 32 )
  1811. pSentenceData++;
  1812. }
  1813. if ( tempBufferPos < sizeof(tempBuffer) )
  1814. {
  1815. // terminate cleaned up copy
  1816. tempBuffer[ tempBufferPos ] = 0;
  1817. // Copy it over the original data
  1818. Q_strcpy( pStart, tempBuffer );
  1819. }
  1820. }
  1821. //-----------------------------------------------------------------------------
  1822. // Purpose: Add a new group or increment count of the existing one
  1823. // Input : *pSentenceName - text of the sentence name
  1824. //-----------------------------------------------------------------------------
  1825. int VOX_GroupAdd( const char *pSentenceName )
  1826. {
  1827. int len = strlen( pSentenceName ) - 1;
  1828. // group members end in a number
  1829. if ( len <= 0 || !V_isdigit(pSentenceName[len]) )
  1830. return -1;
  1831. // truncate away the index
  1832. while ( len > 0 && V_isdigit(pSentenceName[len]) )
  1833. {
  1834. len--;
  1835. }
  1836. // make a copy of the actual group name
  1837. char *groupName = (char *)stackalloc( len + 2 );
  1838. Q_strncpy( groupName, pSentenceName, len+2 );
  1839. // check for it in the list
  1840. int i;
  1841. sentencegroup_t *pGroup;
  1842. CUtlSymbol symGroupName = sentencegroup_t::GetSymbol( groupName );
  1843. int groupCount = g_SentenceGroups.Size();
  1844. for ( i = 0; i < groupCount; i++ )
  1845. {
  1846. int groupIndex = (i + groupCount-1) % groupCount;
  1847. // Start at the last group a loop around
  1848. pGroup = &g_SentenceGroups[groupIndex];
  1849. if ( symGroupName == pGroup->GroupNameSymbol() )
  1850. {
  1851. // Matches previous group, bump count
  1852. pGroup->count++;
  1853. return i;
  1854. }
  1855. }
  1856. // new group
  1857. int addIndex = g_SentenceGroups.AddToTail();
  1858. sentencegroup_t *group = &g_SentenceGroups[addIndex];
  1859. group->SetGroupName( groupName );
  1860. group->count = 1;
  1861. return addIndex;
  1862. }
  1863. #if DEAD
  1864. //-----------------------------------------------------------------------------
  1865. // Purpose: clear the sentence groups
  1866. //-----------------------------------------------------------------------------
  1867. void VOX_GroupClear( void )
  1868. {
  1869. g_SentenceGroups.RemoveAll();
  1870. }
  1871. #endif
  1872. void VOX_LRUInit( sentencegroup_t *pGroup )
  1873. {
  1874. int i, n1, n2, temp;
  1875. if ( pGroup->count )
  1876. {
  1877. unsigned char *pLRU = &g_GroupLRU[pGroup->lru];
  1878. for (i = 0; i < pGroup->count; i++)
  1879. pLRU[i] = (unsigned char) i;
  1880. // randomize array by swapping random elements
  1881. for (i = 0; i < (pGroup->count * 4); i++)
  1882. {
  1883. // FIXME: This should probably call through g_pSoundServices
  1884. // or some other such call?
  1885. n1 = RandomInt(0,pGroup->count-1);
  1886. n2 = RandomInt(0,pGroup->count-1);
  1887. temp = pLRU[n1];
  1888. pLRU[n1] = pLRU[n2];
  1889. pLRU[n2] = temp;
  1890. }
  1891. }
  1892. }
  1893. //-----------------------------------------------------------------------------
  1894. // Purpose: Init the LRU for each sentence group
  1895. //-----------------------------------------------------------------------------
  1896. void VOX_GroupInitAllLRUs( void )
  1897. {
  1898. int i;
  1899. int totalCount = 0;
  1900. for ( i = 0; i < g_SentenceGroups.Size(); i++ )
  1901. {
  1902. g_SentenceGroups[i].lru = totalCount;
  1903. totalCount += g_SentenceGroups[i].count;
  1904. }
  1905. g_GroupLRU.Purge();
  1906. g_GroupLRU.EnsureCount( totalCount );
  1907. for ( i = 0; i < g_SentenceGroups.Size(); i++ )
  1908. {
  1909. VOX_LRUInit( &g_SentenceGroups[i] );
  1910. }
  1911. }
  1912. //-----------------------------------------------------------------------------
  1913. // Purpose: Only during reslist generation
  1914. //-----------------------------------------------------------------------------
  1915. void VOX_AddSentenceWavesToResList( void )
  1916. {
  1917. if ( !CommandLine()->FindParm( "-makereslists" ) &&
  1918. !CommandLine()->FindParm( "-spewsentences" ) )
  1919. {
  1920. return;
  1921. }
  1922. bool spewsentences = CommandLine()->FindParm( "-spewsentences" ) != 0 ? true : false;
  1923. CUtlDict< int, int > list;
  1924. CUtlRBTree< ccpair, int > ccpairs( 0, 0, CCPairLessFunc );
  1925. int i;
  1926. int sentencecount = g_Sentences.Count();
  1927. for ( i = 0; i < sentencecount; i++ )
  1928. {
  1929. // Walk through all nonvirtual sentences and touch the referenced sounds...
  1930. sentence_t *pSentence = &g_Sentences[i];
  1931. if ( !Q_strnicmp( pSentence->pName, "V_", 2 ) )
  1932. {
  1933. continue;
  1934. }
  1935. if ( spewsentences )
  1936. {
  1937. const char *psz = VOX_LookupString(pSentence->pName, NULL);
  1938. if ( psz )
  1939. {
  1940. Msg( "%s : %s\n", pSentence->pName, psz );
  1941. }
  1942. }
  1943. VOX_TouchSound( pSentence->pName, list, ccpairs, spewsentences );
  1944. }
  1945. VOX_TouchSounds( list, ccpairs, spewsentences );
  1946. list.RemoveAll();
  1947. }
  1948. //-----------------------------------------------------------------------------
  1949. // Purpose: Given a group name, return that group's index
  1950. // Input : *pGroupName - name of the group
  1951. // Output : int - index in group table, returns -1 if no matching group is found
  1952. //-----------------------------------------------------------------------------
  1953. int VOX_GroupIndexFromName( const char *pGroupName )
  1954. {
  1955. int i;
  1956. if ( pGroupName )
  1957. {
  1958. // search rgsentenceg for match on szgroupname
  1959. CUtlSymbol symGroupName = sentencegroup_t::GetSymbol( pGroupName );
  1960. for ( i = 0; i < g_SentenceGroups.Size(); i++ )
  1961. {
  1962. if ( symGroupName == g_SentenceGroups[i].GroupNameSymbol() )
  1963. return i;
  1964. }
  1965. }
  1966. return -1;
  1967. }
  1968. //-----------------------------------------------------------------------------
  1969. // Purpose: return the group's name
  1970. // Input : groupIndex - index of the group
  1971. // Output : const char * - name pointer
  1972. //-----------------------------------------------------------------------------
  1973. const char *VOX_GroupNameFromIndex( int groupIndex )
  1974. {
  1975. if ( groupIndex >= 0 && groupIndex < g_SentenceGroups.Size() )
  1976. return g_SentenceGroups[groupIndex].GroupName();
  1977. return NULL;
  1978. }
  1979. // ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence,
  1980. // then repeat list if freset is true. If freset is false, then repeat last sentence.
  1981. // ipick is passed in as the requested sentence ordinal.
  1982. // ipick 'next' is returned.
  1983. // return of -1 indicates an error.
  1984. int VOX_GroupPickSequential( int isentenceg, char *szfound, int szfoundLen, int ipick, int freset )
  1985. {
  1986. const char *szgroupname;
  1987. unsigned char count;
  1988. if (isentenceg < 0 || isentenceg > g_SentenceGroups.Size())
  1989. return -1;
  1990. szgroupname = g_SentenceGroups[isentenceg].GroupName();
  1991. count = g_SentenceGroups[isentenceg].count;
  1992. if (count == 0)
  1993. return -1;
  1994. if (ipick >= count)
  1995. ipick = count-1;
  1996. Q_snprintf( szfound, szfoundLen, "!%s%d", szgroupname, ipick );
  1997. if (ipick >= count)
  1998. {
  1999. if (freset)
  2000. // reset at end of list
  2001. return 0;
  2002. else
  2003. return count;
  2004. }
  2005. return ipick + 1;
  2006. }
  2007. // pick a random sentence from rootname0 to rootnameX.
  2008. // picks from the rgsentenceg[isentenceg] least
  2009. // recently used, modifies lru array. returns the sentencename.
  2010. // note, lru must be seeded with 0-n randomized sentence numbers, with the
  2011. // rest of the lru filled with -1. The first integer in the lru is
  2012. // actually the size of the list. Returns ipick, the ordinal
  2013. // of the picked sentence within the group.
  2014. int VOX_GroupPick( int isentenceg, char *szfound, int strLen )
  2015. {
  2016. const char *szgroupname;
  2017. unsigned char *plru;
  2018. unsigned char i;
  2019. unsigned char count;
  2020. unsigned char ipick=0;
  2021. int ffound = FALSE;
  2022. if (isentenceg < 0 || isentenceg > g_SentenceGroups.Size())
  2023. return -1;
  2024. szgroupname = g_SentenceGroups[isentenceg].GroupName();
  2025. count = g_SentenceGroups[isentenceg].count;
  2026. plru = &g_GroupLRU[g_SentenceGroups[isentenceg].lru];
  2027. while (!ffound)
  2028. {
  2029. for (i = 0; i < count; i++)
  2030. if (plru[i] != 0xFF)
  2031. {
  2032. ipick = plru[i];
  2033. plru[i] = 0xFF;
  2034. ffound = TRUE;
  2035. break;
  2036. }
  2037. if (!ffound)
  2038. {
  2039. VOX_LRUInit( &g_SentenceGroups[isentenceg] );
  2040. }
  2041. else
  2042. {
  2043. Q_snprintf( szfound, strLen, "!%s%d", szgroupname, ipick );
  2044. return ipick;
  2045. }
  2046. }
  2047. return -1;
  2048. }
  2049. struct filelist_t
  2050. {
  2051. const char *pFileName;
  2052. filelist_t *pNext;
  2053. };
  2054. static filelist_t *g_pSentenceFileList = NULL;
  2055. //-----------------------------------------------------------------------------
  2056. // Purpose: clear / reinitialize the vox list
  2057. //-----------------------------------------------------------------------------
  2058. void VOX_ListClear( void )
  2059. {
  2060. filelist_t *pList, *pNext;
  2061. pList = g_pSentenceFileList;
  2062. while ( pList )
  2063. {
  2064. pNext = pList->pNext;
  2065. free( pList );
  2066. pList = pNext;
  2067. }
  2068. g_pSentenceFileList = NULL;
  2069. }
  2070. //-----------------------------------------------------------------------------
  2071. // Purpose: Check to see if this file is in the list
  2072. // Input : *psentenceFileName -
  2073. // Output : int, true if the file is in the list, false if not
  2074. //-----------------------------------------------------------------------------
  2075. int VOX_ListFileIsLoaded( const char *psentenceFileName )
  2076. {
  2077. filelist_t *pList = g_pSentenceFileList;
  2078. while ( pList )
  2079. {
  2080. if ( !strcmp( psentenceFileName, pList->pFileName ) )
  2081. return true;
  2082. pList = pList->pNext;
  2083. }
  2084. return false;
  2085. }
  2086. //-----------------------------------------------------------------------------
  2087. // Purpose: Add this file name to the sentence list
  2088. // Input : *psentenceFileName -
  2089. //-----------------------------------------------------------------------------
  2090. void VOX_ListMarkFileLoaded( const char *psentenceFileName )
  2091. {
  2092. filelist_t *pEntry;
  2093. char *pName;
  2094. pEntry = (filelist_t *)malloc( sizeof(filelist_t) + strlen( psentenceFileName ) + 1);
  2095. if ( pEntry )
  2096. {
  2097. pName = (char *)(pEntry+1);
  2098. Q_strcpy( pName, psentenceFileName );
  2099. pEntry->pFileName = pName;
  2100. pEntry->pNext = g_pSentenceFileList;
  2101. g_pSentenceFileList = pEntry;
  2102. }
  2103. }
  2104. // This creates a compact copy of the sentence file in memory with only the necessary data
  2105. void VOX_CompactSentenceFile()
  2106. {
  2107. int totalMem = 0;
  2108. int i;
  2109. for ( i = 0; i < g_Sentences.Count(); i++ )
  2110. {
  2111. int len = Q_strlen( g_Sentences[i].pName ) + 1;
  2112. const char *pData = g_Sentences[i].pName + len;
  2113. int dataLen = Q_strlen( pData ) + 1;
  2114. totalMem += len + dataLen;
  2115. }
  2116. g_SentenceFile.EnsureCount( totalMem );
  2117. totalMem = 0;
  2118. for ( i = 0; i < g_Sentences.Count(); i++ )
  2119. {
  2120. int len = Q_strlen( g_Sentences[i].pName ) + 1;
  2121. const char *pData = g_Sentences[i].pName + len;
  2122. int dataLen = Q_strlen( pData ) + 1;
  2123. char *pDest = &g_SentenceFile[totalMem];
  2124. memcpy( pDest, g_Sentences[i].pName, len + dataLen );
  2125. g_Sentences[i].pName = pDest;
  2126. totalMem += len + dataLen;
  2127. }
  2128. }
  2129. // Load sentence file into memory, insert null terminators to
  2130. // delimit sentence name/sentence pairs. Keep pointer to each
  2131. // sentence name so we can search later.
  2132. void VOX_ReadSentenceFile( const char *psentenceFileName )
  2133. {
  2134. char *pch;
  2135. byte *pFileData;
  2136. int fileSize;
  2137. char c;
  2138. char *pchlast, *pSentenceData;
  2139. characterset_t whitespace;
  2140. // Have we already loaded this file?
  2141. if ( VOX_ListFileIsLoaded( psentenceFileName ) )
  2142. {
  2143. // must touch any sentence wavs again to ensure the map's init path gets the results
  2144. if ( MapReslistGenerator().IsLoggingToMap() )
  2145. {
  2146. VOX_AddSentenceWavesToResList();
  2147. }
  2148. return;
  2149. }
  2150. // load file
  2151. FileHandle_t file;
  2152. file = g_pFileSystem->Open( psentenceFileName, "rb" );
  2153. if ( FILESYSTEM_INVALID_HANDLE == file )
  2154. {
  2155. DevMsg ("Couldn't load %s\n", psentenceFileName);
  2156. return;
  2157. }
  2158. fileSize = g_pFileSystem->Size( file );
  2159. if ( fileSize <= 0 )
  2160. {
  2161. DevMsg ("VOX_ReadSentenceFile: %s has invalid size %i\n", psentenceFileName, fileSize );
  2162. g_pFileSystem->Close( file );
  2163. return;
  2164. }
  2165. pFileData = (byte *)g_pFileSystem->AllocOptimalReadBuffer( file, fileSize + 1 );
  2166. if ( !pFileData )
  2167. {
  2168. DevMsg ("VOX_ReadSentenceFile: %s couldn't allocate %i bytes for data\n", psentenceFileName, fileSize );
  2169. g_pFileSystem->Close( file );
  2170. return;
  2171. }
  2172. // Read the data and close the file
  2173. g_pFileSystem->ReadEx( pFileData, g_pFileSystem->GetOptimalReadSize( file, fileSize ), fileSize, file );
  2174. g_pFileSystem->Close( file );
  2175. // Make sure we end with a null terminator
  2176. pFileData[ fileSize ] = 0;
  2177. pch = (char *)pFileData;
  2178. pchlast = pch + fileSize;
  2179. CharacterSetBuild( &whitespace, "\n\r\t " );
  2180. const char *pName = 0;
  2181. while (pch < pchlast)
  2182. {
  2183. // Only process this pass on sentences
  2184. pSentenceData = NULL;
  2185. // skip newline, cr, tab, space
  2186. c = *pch;
  2187. while (pch < pchlast && IN_CHARACTERSET( whitespace, c ))
  2188. c = *(++pch);
  2189. // YWB: Fix possible crashes reading past end of file if the last line has only whitespace on it...
  2190. if ( !*pch )
  2191. break;
  2192. // skip entire line if first char is /
  2193. if (*pch != '/')
  2194. {
  2195. int addIndex = g_Sentences.AddToTail();
  2196. sentence_t *pSentence = &g_Sentences[addIndex];
  2197. pName = pch;
  2198. pSentence->pName = pch;
  2199. pSentence->length = 0;
  2200. pSentence->closecaption = false;
  2201. pSentence->isPrecached = false;
  2202. pSentence->caption = UTL_INVAL_SYMBOL;
  2203. // scan forward to first space, insert null terminator
  2204. // after sentence name
  2205. c = *pch;
  2206. while (pch < pchlast && c != ' ')
  2207. c = *(++pch);
  2208. if (pch < pchlast)
  2209. *pch++ = 0;
  2210. // A sentence may have some line commands, make an extra pass
  2211. pSentenceData = pch;
  2212. }
  2213. // scan forward to end of sentence or eof
  2214. while (pch < pchlast && pch[0] != '\n' && pch[0] != '\r')
  2215. pch++;
  2216. // insert null terminator
  2217. if (pch < pchlast)
  2218. *pch++ = 0;
  2219. // If we have some sentence data, parse out any line commands
  2220. if ( pSentenceData && pSentenceData < pchlast )
  2221. {
  2222. // Add a new group or increment count of the existing one
  2223. VOX_GroupAdd( pName );
  2224. int index = g_Sentences.Size()-1;
  2225. // The current sentence has an index of count-1
  2226. VOX_ParseLineCommands( pSentenceData, index );
  2227. }
  2228. }
  2229. // now compact the file data in memory
  2230. VOX_CompactSentenceFile();
  2231. g_pFileSystem->FreeOptimalReadBuffer( pFileData );
  2232. VOX_GroupInitAllLRUs();
  2233. // This only does stuff during reslist generation...
  2234. VOX_AddSentenceWavesToResList();
  2235. VOX_ListMarkFileLoaded( psentenceFileName );
  2236. }
  2237. //-----------------------------------------------------------------------------
  2238. // Purpose: Get the current number of sentences in the database
  2239. // Output : int
  2240. //-----------------------------------------------------------------------------
  2241. int VOX_SentenceCount( void )
  2242. {
  2243. return g_Sentences.Size();
  2244. }
  2245. float VOX_SentenceLength( int sentence_num )
  2246. {
  2247. if ( sentence_num < 0 || sentence_num > g_Sentences.Size()-1 )
  2248. return 0.0f;
  2249. return g_Sentences[ sentence_num ].length;
  2250. }
  2251. // scan g_Sentences, looking for pszin sentence name
  2252. // return pointer to sentence data if found, null if not
  2253. // CONSIDER: if we have a large number of sentences, should
  2254. // CONSIDER: sort strings in g_Sentences and do binary search.
  2255. char *VOX_LookupString(const char *pSentenceName, int *psentencenum, bool *pbEmitCaption /*=NULL*/, CUtlSymbol *pCaptionSymbol /*=NULL*/, float *pflDuration /*= NULL*/ )
  2256. {
  2257. if ( pbEmitCaption )
  2258. {
  2259. *pbEmitCaption = false;
  2260. }
  2261. if ( pCaptionSymbol )
  2262. {
  2263. *pCaptionSymbol = UTL_INVAL_SYMBOL;
  2264. }
  2265. if ( pflDuration )
  2266. {
  2267. *pflDuration = 0.0f;
  2268. }
  2269. int i;
  2270. int c = g_Sentences.Size();
  2271. for (i = 0; i < c; i++)
  2272. {
  2273. char const *name = g_Sentences[i].pName;
  2274. if (!stricmp(pSentenceName, name))
  2275. {
  2276. if (psentencenum)
  2277. {
  2278. *psentencenum = i;
  2279. }
  2280. if ( pbEmitCaption )
  2281. {
  2282. *pbEmitCaption = g_Sentences[ i ].closecaption;
  2283. }
  2284. if ( pCaptionSymbol )
  2285. {
  2286. *pCaptionSymbol = g_Sentences[ i ].caption;
  2287. }
  2288. if ( pflDuration )
  2289. {
  2290. *pflDuration = g_Sentences[ i ].length;
  2291. }
  2292. return (char *)(name + Q_strlen(name) + 1);
  2293. }
  2294. }
  2295. return NULL;
  2296. }
  2297. // Abstraction for sentence name array
  2298. const char *VOX_SentenceNameFromIndex( int sentencenum )
  2299. {
  2300. if ( sentencenum < g_Sentences.Size() )
  2301. return g_Sentences[sentencenum].pName;
  2302. return NULL;
  2303. }