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

2860 lines
68 KiB

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