diff options
Diffstat (limited to 'engine/audio/private/vox.cpp')
| -rw-r--r-- | engine/audio/private/vox.cpp | 2862 |
1 files changed, 2862 insertions, 0 deletions
diff --git a/engine/audio/private/vox.cpp b/engine/audio/private/vox.cpp new file mode 100644 index 0000000..80a3a2b --- /dev/null +++ b/engine/audio/private/vox.cpp @@ -0,0 +1,2862 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Voice / Sentence streaming & parsing code +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +//=============================================================================== +// VOX. Algorithms to load and play spoken text sentences from a file: +// +// In ambient sounds or entity sounds, precache the +// name of the sentence instead of the wave name, ie: !C1A2S4 +// +// During sound system init, the 'sentences.txt' is read. +// This file has the format: +// +// C1A2S4 agrunt/vox/You will be exterminated, surrender NOW. +// C1A2s5 hgrunt/vox/Radio check, over. +// ... +// +// There must be at least one space between the sentence name and the sentence. +// Sentences may be separated by one or more lines +// There may be tabs or spaces preceding the sentence name +// The sentence must end in a /n or /r +// Lines beginning with // are ignored as comments +// +// Period or comma will insert a pause in the wave unless +// the period or comma is the last character in the string. +// +// If first 2 chars of a word are upper case, word volume increased by 25% +// +// If last char of a word is a number from 0 to 9 +// then word will be pitch-shifted up by 0 to 9, where 0 is a small shift +// and 9 is a very high pitch shift. +// +// We alloc heap space to contain this data, and track total +// sentences read. A pointer to each sentence is maintained in g_Sentences. +// +// When sound is played back in S_StartDynamicSound or s_startstaticsound, we detect the !name +// format and lookup the actual sentence in the sentences array +// +// To play, we parse each word in the sentence, chain the words, and play the sentence +// each word's data is loaded directy from disk and freed right after playback. +//=============================================================================== + +#include "audio_pch.h" +#include "vox_private.h" +#include "characterset.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "utlsymbol.h" +#include "utldict.h" +#include "../../MapReslistGenerator.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// In other C files. +// Globals +extern IFileSystem *g_pFileSystem; + +// This is the initial capacity for sentences, the array will grow if necessary +#define MAX_EXPECTED_SENTENCES 900 + +CUtlVector<sentence_t> g_Sentences; +// FIXME: could get this through common includes +const char *COM_Parse (const char *data); +extern char com_token[1024]; + +// Module Locals +static char *rgpparseword[CVOXWORDMAX]; // array of pointers to parsed words +static char voxperiod[] = "_period"; // vocal pause +static char voxcomma[] = "_comma"; // vocal pause + +#define CVOXMAPNAMESMAX 24 +static char *g_rgmapnames[CVOXMAPNAMESMAX]; +static int g_cmapnames = 0; + +// Sentence file list management +static void VOX_ListClear( void ); +static int VOX_ListFileIsLoaded( const char *psentenceFileName ); +static void VOX_ListMarkFileLoaded( const char *psentenceFileName ); +static void VOX_InitAllEntnames( void ); + +void VOX_LookupMapnames( void ); + +static void VOX_Reload() +{ + VOX_Shutdown(); + VOX_Init(); +} +static ConCommand vox_reload( "vox_reload", VOX_Reload, "Reload sentences.txt file", FCVAR_CHEAT ); + +static CUtlVector<unsigned char> g_GroupLRU; +static CUtlVector<char> g_SentenceFile; + +struct sentencegroup_t +{ + short count; + +public: + short lru; + const char *GroupName() const; + CUtlSymbol GroupNameSymbol() const; + void SetGroupName( const char *pName ); + static CUtlSymbol GetSymbol( const char *pName ); + +private: + CUtlSymbol groupname; + static CUtlSymbolTable s_SymbolTable; +}; + +const char *sentencegroup_t::GroupName() const +{ + return s_SymbolTable.String( groupname ); +} + +void sentencegroup_t::SetGroupName( const char *pName ) +{ + groupname = s_SymbolTable.AddString( pName ); +} + +CUtlSymbol sentencegroup_t::GroupNameSymbol() const +{ + return groupname; +} + +CUtlSymbol sentencegroup_t::GetSymbol( const char *pName ) +{ + return s_SymbolTable.AddString( pName ); +} + +CUtlVector<sentencegroup_t> g_SentenceGroups; +CUtlSymbolTable sentencegroup_t::s_SymbolTable( 0, 256, true ); + +struct WordBuf +{ + WordBuf() + { + word[ 0 ] = 0; + } + + WordBuf( const WordBuf& src ) + { + Q_strncpy( word, src.word, sizeof( word ) ); + } + + void Set( char const *w ) + { + if ( !w ) + { + word[ 0 ] = 0; + return; + } + Q_strncpy( word, w, sizeof( word ) ); + while ( Q_strlen( word ) >= 1 && word[ Q_strlen( word ) - 1 ] == ' ' ) + { + word[ Q_strlen( word ) - 1 ] = 0; + } + } + + char word[ 256 ]; +}; + +struct ccpair +{ + WordBuf token; + WordBuf value; + + WordBuf fullpath; +}; + +static void VOX_BuildVirtualNameList( char *word, CUtlVector< WordBuf >& list ); + +// This module depends on these engine calls: +// DevMsg +// S_FreeChannel +// S_LoadSound +// S_FindName +// It also depends on vstdlib/RandomInt (all other random calls go through g_pSoundServices) + +void VOX_Init( void ) +{ + VOX_InitAllEntnames(); + + g_SentenceFile.Purge(); + g_GroupLRU.Purge(); + g_Sentences.RemoveAll(); + g_Sentences.EnsureCapacity( MAX_EXPECTED_SENTENCES ); + + VOX_ListClear(); + + VOX_ReadSentenceFile( "scripts/sentences.txt" ); + VOX_LookupMapnames(); +} + + +void VOX_Shutdown( void ) +{ + g_Sentences.RemoveAll(); + VOX_ListClear(); + g_SentenceGroups.RemoveAll(); + g_cmapnames = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: This is kind of like strchr(), but we get the actual pointer to the +// end of the string when it fails rather than NULL. This is useful +// for parsing buffers containing multiple strings +// Input : *string - +// scan - +// Output : char +//----------------------------------------------------------------------------- +char *ScanForwardUntil( char *string, char scan ) +{ + while( string[0] ) + { + if ( string[0] == scan ) + return string; + + string++; + } + return string; +} + +// parse a null terminated string of text into component words, with +// pointers to each word stored in rgpparseword +// note: this code actually alters the passed in string! + +char **VOX_ParseString(char *psz) +{ + int i; + int fdone = 0; + char *pszscan = psz; + char c; + characterset_t nextWord, skip; + + memset(rgpparseword, 0, sizeof(char *) * CVOXWORDMAX); + + if (!psz) + return NULL; + + i = 0; + rgpparseword[i++] = psz; + + CharacterSetBuild( &nextWord, " ,.({" ); + CharacterSetBuild( &skip, "., " ); + while (!fdone && i < CVOXWORDMAX) + { + // scan up to next word + c = *pszscan; + while (c && !IN_CHARACTERSET(nextWord,c) ) + c = *(++pszscan); + + // if '(' then scan for matching ')' + if ( c == '(' || c=='{' ) + { + if ( c == '(' ) + pszscan = ScanForwardUntil( pszscan, ')' ); + else if ( c == '{' ) + pszscan = ScanForwardUntil( pszscan, '}' ); + + c = *(++pszscan); + if (!c) + fdone = 1; + } + + if (fdone || !c) + fdone = 1; + else + { + // if . or , insert pause into rgpparseword, + // unless this is the last character + if ((c == '.' || c == ',') && *(pszscan+1) != '\n' && *(pszscan+1) != '\r' + && *(pszscan+1) != 0) + { + if (c == '.') + rgpparseword[i++] = voxperiod; + else + rgpparseword[i++] = voxcomma; + + if (i >= CVOXWORDMAX) + break; + } + + // null terminate substring + *pszscan++ = 0; + + // skip whitespace + c = *pszscan; + while (c && IN_CHARACTERSET(skip, c)) + c = *(++pszscan); + + if (!c) + fdone = 1; + else + rgpparseword[i++] = pszscan; + } + } + return rgpparseword; +} + +// backwards scan psz for last '/' +// return substring in szpath null terminated +// if '/' not found, return 'vox/' + +char *VOX_GetDirectory(char *szpath, int maxpath, char *psz) +{ + char c; + int cb = 0; + char *pszscan = psz + Q_strlen( psz ) - 1; + + // scan backwards until first '/' or start of string + c = *pszscan; + while (pszscan > psz && c != '/') + { + c = *(--pszscan); + cb++; + } + + if (c != '/') + { + // didn't find '/', return default directory + Q_strncpy(szpath, "vox/", maxpath ); + return psz; + } + + cb = Q_strlen(psz) - cb; + + cb = clamp( cb, 0, maxpath - 1 ); + + // FIXME: Is this safe? + Q_memcpy(szpath, psz, cb); + szpath[cb] = 0; + return pszscan + 1; +} + +// get channel volume scale if word + +#ifndef SWDS +float VOX_GetChanVol(channel_t *ch) +{ + if ( !ch->pMixer ) + return 1.0; + + return ch->pMixer->GetVolumeScale(); +/* + + if ( scale == 1.0 ) + return; + + ch->rightvol = (int) (ch->rightvol * scale); + ch->leftvol = (int) (ch->leftvol * scale); + + if ( g_AudioDevice->Should3DMix() ) + { + ch->rrightvol = (int) (ch->rrightvol * scale); + ch->rleftvol = (int) (ch->rleftvol * scale); + ch->centervol = (int) (ch->centervol * scale); + } + else + { + ch->rrightvol = 0; + ch->rleftvol = 0; + ch->centervol = 0; + } +*/ +} +#endif + +//=============================================================================== +// Get any pitch, volume, start, end params into voxword +// and null out trailing format characters +// Format: +// someword(v100 p110 s10 e20) +// +// v is volume, 0% to n% +// p is pitch shift up 0% to n% +// s is start wave offset % +// e is end wave offset % +// t is timecompression % +// +// pass fFirst == 1 if this is the first string in sentence +// returns 1 if valid string, 0 if parameter block only. +// +// If a ( xxx ) parameter block does not directly follow a word, +// then that 'default' parameter block will be used as the default value +// for all following words. Default parameter values are reset +// by another 'default' parameter block. Default parameter values +// for a single word are overridden for that word if it has a parameter block. +// +//=============================================================================== + +int VOX_ParseWordParams(char *psz, voxword_t *pvoxword, int fFirst) +{ + char *pszsave = psz; + char c; + char ct; + char sznum[8]; + int i; + static voxword_t voxwordDefault; + characterset_t commandSet, delimitSet; + + // List of valid commands + CharacterSetBuild( &commandSet, "vpset)" ); + + // init to defaults if this is the first word in string. + if (fFirst) + { + voxwordDefault.pitch = -1; + voxwordDefault.volume = 100; + voxwordDefault.start = 0; + voxwordDefault.end = 100; + voxwordDefault.fKeepCached = 0; + voxwordDefault.timecompress = 0; + } + + *pvoxword = voxwordDefault; + + // look at next to last char to see if we have a + // valid format: + + c = *(psz + strlen(psz) - 1); + + if (c != ')') + return 1; // no formatting, return + + // scan forward to first '(' + CharacterSetBuild( &delimitSet, "()" ); + c = *psz; + while ( !IN_CHARACTERSET(delimitSet, c) ) + c = *(++psz); + + if ( c == ')' ) + return 0; // bogus formatting + + // null terminate + + *psz = 0; + ct = *(++psz); + + while (1) + { + // scan until we hit a character in the commandSet + + while (ct && !IN_CHARACTERSET(commandSet, ct) ) + ct = *(++psz); + + if (ct == ')') + break; + + memset(sznum, 0, sizeof(sznum)); + i = 0; + + c = *(++psz); + + if (!V_isdigit(c)) + break; + + // read number + while (V_isdigit(c) && i < sizeof(sznum) - 1) + { + sznum[i++] = c; + c = *(++psz); + } + + // get value of number + i = atoi(sznum); + + switch (ct) + { + case 'v': pvoxword->volume = i; break; + case 'p': pvoxword->pitch = i; break; + case 's': pvoxword->start = i; break; + case 'e': pvoxword->end = i; break; + case 't': pvoxword->timecompress = i; break; + } + + ct = c; + } + + // if the string has zero length, this was an isolated + // parameter block. Set default voxword to these + // values + + if (strlen(pszsave) == 0) + { + voxwordDefault = *pvoxword; + return 0; + } + else + return 1; +} + +#define CVOXSAVEDWORDSIZE 32 + +// saved entity name/number based on type of entity & id + +#define CVOXGLOBMAX 4 // max number of rnd and seqential globals + +typedef struct _vox_entname +{ + // type is defined by last character of group name. + // for instance, V_MYNAME_S has type 'S', which is used for soldiers + // V_MYNUM_M has type 'P' which is used for metrocops + + int type; + + SoundSource soundsource; // the enity emitting the sentence + char *pszname; // a custom name for the entity (this is a word name) + char *psznum; // a custom number for the entity (this is a word name) + char *pszglobal[CVOXGLOBMAX]; // 1 global word, shared by this type of entity, picked randomly, expires after 5min + char *pszglobalseq[CVOXGLOBMAX]; // 1 global word, shared by this type of entity, picked in sequence, expires after 5 min + bool fdied; // true if ent died (don't clear, we need its name) + int iseq[CVOXGLOBMAX]; // sequence index, for global sequential lookups + float timestamp[CVOXGLOBMAX]; // latest update to this ent global timestamp + float timestampseq[CVOXGLOBMAX]; // latest update to this ent global sequential timestamp + float timedied; // timestamp of death + +} vox_entname; + +#define CENTNAMESMAX 64 + +vox_entname g_entnames[CENTNAMESMAX]; + +int g_entnamelastsaved = 0; + +// init all + +void VOX_InitAllEntnames( void ) +{ + g_entnamelastsaved = 0; + Q_memset(g_entnames, 0, sizeof(g_entnames)); + Q_memset(g_rgmapnames, 0, sizeof(g_rgmapnames)); + g_cmapnames = 0; +} + +// get new index + +int VOX_GetNextEntnameIndex( void ) +{ + g_entnamelastsaved++; + + if (g_entnamelastsaved >= CENTNAMESMAX) + { + g_entnamelastsaved = 0; + } + + return g_entnamelastsaved; +} + +// get index of this ent, or get a new index. if fallocnew is true, +// get a new slot if none found. +// NOTE: this routine always sets fdied to false - fdied is later +// set to true by the caller if in IDIED routine. This +// ensures that if an ent is reused, it won't be marked as fdied. + +int VOX_LookupEntIndex( int type, SoundSource soundsource, bool fallocnew) +{ + int i; + + for (i = 0; i < CENTNAMESMAX; i++) + { + if ((g_entnames[i].type == type) && (g_entnames[i].soundsource == soundsource)) + { + g_entnames[i].fdied = false; + return i; + } + } + + if ( !fallocnew ) + return -1; + + // new index slot - init + + int inew = VOX_GetNextEntnameIndex(); + + g_entnames[inew].type = type; + g_entnames[inew].soundsource = soundsource; + g_entnames[inew].timedied = 0; + g_entnames[inew].fdied = 0; + g_entnames[inew].pszname = NULL; + g_entnames[inew].psznum = NULL; + + for (i = 0; i < CVOXGLOBMAX; i++) + { + g_entnames[inew].pszglobal[i] = NULL; + g_entnames[inew].timestamp[i] = 0; + g_entnames[inew].iseq[i] = 0; + g_entnames[inew].timestampseq[i] = 0; + g_entnames[inew].pszglobalseq[i] = NULL; + } + + return inew; +} + +// lookup random first word from this named group, +// return static, null terminated string + +char * VOX_LookupRndVirtual( char *pGroupName ) +{ + // get group index + + int isentenceg = VOX_GroupIndexFromName( pGroupName ); + + if ( isentenceg < 0) + return NULL; + + char szsentencename[32]; + + // get pointer to sentence name within group, using lru + + int isentence = VOX_GroupPick( isentenceg, szsentencename, sizeof(szsentencename)-1 ); + + if (isentence < 0) + return NULL; + + // get pointer to sentence data + + char *psz = VOX_LookupString( szsentencename[0] == '!' ? szsentencename+1 : szsentencename, NULL); + + // strip trailing whitespace + + if (!psz) + return NULL; + + char *pend = Q_strstr(psz, " "); + if (pend) + *pend = 0; + + // return pointer to first (and only) word + + return psz; +} + +// given groupname, get pointer to first word of n'th sentence in group + +char *VOX_LookupSentenceByIndex( char *pGroupname, int ipick, int *pipicknext ) +{ + // get group index + + int isentenceg = VOX_GroupIndexFromName( pGroupname ); + + if ( isentenceg < 0) + return NULL; + + char szsentencename[32]; + + // get pointer to sentence name within group, using lru + + int isentence = VOX_GroupPickSequential( isentenceg, szsentencename, sizeof(szsentencename)-1, ipick, true ); + + if (isentence < 0) + return NULL; + + // get pointer to sentence data + + char *psz = VOX_LookupString( szsentencename[0] == '!' ? szsentencename+1 : szsentencename, NULL); + + // strip trailing whitespace + + char *pend = Q_strstr(psz, " "); + if (pend) + *pend = 0; + + if (pipicknext) + *pipicknext = isentence; + + // return pointer to first (and only) word + return psz; +} + +// lookup first word from this named group, group entry 'ipick', +// return static, null terminated string + +char * VOX_LookupNumber( char *pGroupName, int ipick ) +{ + // construct group name from V_NUMBERS + TYPE + + char sznumbers[16]; + int glen = Q_strlen(pGroupName); + int slen = Q_strlen("V_NUMBERS"); + + V_strcpy_safe(sznumbers, "V_NUMBERS"); + + // insert type character + sznumbers[slen] = pGroupName[glen-1]; + sznumbers[slen+1] = 0; + + return VOX_LookupSentenceByIndex( sznumbers, ipick, NULL ); +} + +// lookup ent & type, return static, null terminated string +// if no saved string, create one. +// UNDONE: init ent/type/string array, wrap when saving + +char * VOX_LookupMyVirtual( int iname, char *pGroupName, char chtype, SoundSource soundsource) +{ + char *psz = NULL; + char **ppsz = NULL; + + // get existing ent index, or index to new slot + + int ient = VOX_LookupEntIndex( (int)chtype, soundsource, true ); + + if (iname == 1) + { + // lookup saved name + + psz = g_entnames[ient].pszname; + ppsz = &(g_entnames[ient].pszname); + } + else + { + // lookup saved number + + psz = g_entnames[ient].psznum; + ppsz = &(g_entnames[ient].psznum); + } + + // if none found for this ent - pick one and save it + + if (psz == NULL) + { + // get new string + psz = VOX_LookupRndVirtual( pGroupName ); + + // save pointer to new string in g_entnames + *ppsz = psz; + } + + return psz; +} + +// get range or heading from ent to player, +// store range in from 1 to 3 words as ppszNew...ppszNew2 +// store count of words in pcnew +// if fsimple is true, return numeric sequence based on ten digit max + +void VOX_LookupRangeHeadingOrGrid( int irhg, char *pGroupName, channel_t *pChannel, SoundSource soundsource, char **ppszNew, char **ppszNew1, char **ppszNew2, int *pcnew, bool fsimple ) +{ + Vector SL; // sound -> listener vector + char *phundreds = NULL; + char *ptens = NULL; + char *pones = NULL; + int cnew = 0; + float dist; + int dmeters = 0; + int hundreds, tens, ones; + + VectorSubtract(listener_origin, pChannel->origin, SL); + + if (irhg == 0) + { + // get range + dist = VectorLength(SL); + + dmeters = (int)((dist * 2.54 / 100.0)); // convert inches to meters + + dmeters = clamp(dmeters, 0, 900); + } + else if (irhg == 1) + { + // get heading + QAngle source_angles; + + source_angles.Init(0.0, 0.0, 0.0); + + VectorAngles( SL, source_angles ); + + dmeters = source_angles[YAW]; + } else if (irhg == 2) + { + // get gridx + dmeters = (int)(((16384 + listener_origin.x) * 2.54 / 100.0) / 10) % 20; + } + else if (irhg == 3) + { + // get gridy + dmeters = (int)(((16384 + listener_origin.y) * 2.54 / 100.0) / 10) % 20; + } + + dmeters = clamp(dmeters, 0, 999); + + // get hundreds, tens, ones + + hundreds = dmeters / 100; + tens = (dmeters - hundreds * 100) / 10; + ones = (dmeters - hundreds * 100 - tens * 10); + + + if (fsimple) + { + // just return simple ten digit lookups for ones, tens, hundreds + + pones = VOX_LookupNumber( pGroupName, ones); + cnew++; + + if (tens || hundreds) + { + ptens = VOX_LookupNumber( pGroupName, tens); + cnew++; + } + + if (hundreds) + { + phundreds = VOX_LookupNumber( pGroupName, hundreds ); + cnew++; + } + + goto LookupNumExit; + } + + // get pointer to string from groupname and number + + // 100,200,300,400,500,600,700,800,900 + if (hundreds && !tens && !ones) + { + if (hundreds <= 3) + { + phundreds = VOX_LookupNumber( pGroupName, 27 + hundreds); + cnew++; + } + else + { + phundreds = VOX_LookupNumber( pGroupName, hundreds ); + ptens = VOX_LookupNumber( pGroupName, 0); + pones = VOX_LookupNumber( pGroupName, 0); + cnew++; + cnew++; + + } + goto LookupNumExit; + } + + + if ( hundreds ) + { + // 101..999 + if (hundreds <= 3 && !tens && ones) + phundreds = VOX_LookupNumber( pGroupName, 27 + hundreds); + else + phundreds = VOX_LookupNumber( pGroupName, hundreds ); + + cnew++; + + // 101..109 to 901..909 + if (!tens && ones) + { + pones = VOX_LookupNumber( pGroupName, ones); + cnew++; + if (hundreds > 3) + { + ptens = VOX_LookupNumber( pGroupName, 0); + cnew++; + } + goto LookupNumExit; + } + } + + // 1..19 + if (tens <= 1 && (tens || ones)) + { + pones = VOX_LookupNumber( pGroupName, ones + tens * 10 ); + cnew++; + tens = 0; + goto LookupNumExit; + } + + // 20..99 + if (tens > 1) + { + if (ones) + { + pones = VOX_LookupNumber( pGroupName, ones ); + cnew++; + } + + ptens = VOX_LookupNumber( pGroupName, 18 + tens); + cnew++; + } + + +LookupNumExit: + // return values + + *pcnew = cnew; + + // return + switch (cnew) + { + default: + *ppszNew = NULL; + return; + case 1: // 1..19,20,30,40,50,60,70,80,90,100,200,300 + *ppszNew = pones ? pones : (ptens ? ptens : (phundreds ? phundreds : NULL)); + return; + case 2: + if (ptens && pones) + { + *ppszNew = ptens; + *ppszNew1 = pones; + } + else if (phundreds && pones) + { + *ppszNew = phundreds; + *ppszNew1 = pones; + } + else if (phundreds && ptens) + { + *ppszNew = phundreds; + *ppszNew1 = ptens; + } + return; + case 3: + *ppszNew = phundreds; + *ppszNew1 = ptens; + *ppszNew2 = pones; + return; + } +} + +// find most recent ent of this type marked as dead + +int VOX_LookupLastDeadIndex( int type ) +{ + float timemax = -1; + int ifound = -1; + int i; + + for (i = 0; i < CENTNAMESMAX; i++) + { + if (g_entnames[i].type == type && g_entnames[i].fdied) + { + if (g_entnames[i].timedied >= timemax) + { + timemax = g_entnames[i].timedied; + ifound = i; + } + } + } + + return ifound; +} + +ConVar snd_vox_globaltimeout("snd_vox_globaltimeout", "300"); // n second timeout to reset global vox words +ConVar snd_vox_seqtimeout("snd_vox_seqtimetout", "300"); // n second timeout to reset global sequential vox words +ConVar snd_vox_sectimeout("snd_vox_sectimetout", "300"); // n second timeout to reset global sector id +ConVar snd_vox_captiontrace( "snd_vox_captiontrace", "0", 0, "Shows sentence name for sentences which are set not to show captions." ); + +// return index to ent which knows the current sector. +// if no ent found, alloc a new one and establish shector. +// sectors expire after approx 5 minutes. + +#define VOXSECTORMAX 20 + +static float g_vox_lastsectorupdate = 0; +static int g_vox_isector = -1; + +char *VOX_LookupSectorVirtual( char *pGroupname ) +{ + float curtime = g_pSoundServices->GetClientTime(); + + if (g_vox_isector == -1) + { + g_vox_isector = RandomInt(0, VOXSECTORMAX-1); + } + +// update sector every 5 min + + if (curtime - g_vox_lastsectorupdate > snd_vox_sectimeout.GetInt()) + { + g_vox_isector++; + if (g_vox_isector > VOXSECTORMAX) + g_vox_isector = 1; + g_vox_lastsectorupdate = curtime; + } + + return VOX_LookupNumber( pGroupname, g_vox_isector ); +} + + + +char *VOX_LookupGlobalVirtual( int type, SoundSource soundsource, char *pGroupName, int iglobal ) +{ + int i; + float curtime = g_pSoundServices->GetClientTime(); + + // look for ent of this type with un-expired global + + for (i = 0; i < CENTNAMESMAX; i++) + { + if (g_entnames[i].type == type) + { + if (curtime - g_entnames[i].timestamp[iglobal] <= snd_vox_globaltimeout.GetInt()) + { + // if this ent has an un-expired global, return it, otherwise break + + if (g_entnames[i].pszglobal[iglobal]) + return g_entnames[i].pszglobal[iglobal]; + else + break; + } + } + } + + // if not found, construct a new global for this ent + + // pick random word from groupname + + char *psz = VOX_LookupRndVirtual( pGroupName ); + + // get existing ent index, or index to new slot + + int ient = VOX_LookupEntIndex( type, soundsource, true ); + + g_entnames[ient].timestamp[iglobal] = curtime; + g_entnames[ient].pszglobal[iglobal] = psz; + + return psz; +} + +// lookup global values in group in sequence - get next value +// in sequence. sequence counter expires every 2.5 minutes. + +char *VOX_LookupGlobalSeqVirtual( int type, SoundSource soundsource, char *pGroupName, int iglobal ) +{ + + int i; + int ient; + float curtime = g_pSoundServices->GetClientTime(); + + // look for ent of this type with un-expired global + + for (i = 0; i < CENTNAMESMAX; i++) + { + if (g_entnames[i].type == type) + { + if (curtime - g_entnames[i].timestampseq[iglobal] <= (snd_vox_seqtimeout.GetInt()/2)) + { + // if first ent found has an un-expired global sequence set, + // get next value in sequence, otherwise break + + ient = i; + goto Pick_next; + } + else + { + // global has expired - reset sequence + + ient = i; + g_entnames[ient].iseq[iglobal] = 0; + goto Pick_next; + } + } + } + + // if not found, construct a new sequential global for this ent + + ient = VOX_LookupEntIndex( type, soundsource, true ); + + // pick next word from groupname +Pick_next: + int ipick = g_entnames[ient].iseq[iglobal]; + int ipicknext = 0; + + char *psz = VOX_LookupSentenceByIndex( pGroupName, ipick, &ipicknext ); + g_entnames[ient].iseq[iglobal] = ipicknext; + + // get existing ent index, or index to new slot + + g_entnames[ient].timestampseq[iglobal] = curtime; + g_entnames[ient].pszglobalseq[iglobal] = psz; + + return psz; +} + +// insert new words into rgpparseword at 'ireplace' slot + +void VOX_InsertWords( int ireplace, int cnew, char *pszNew, char *pszNew1, char *pszNew2 ) +{ + if ( cnew ) + { + // make space in rgpparseword for 'cnew - 1' new words + int ccopy = cnew - 1; // number of new slots we need + int j; + + if (ccopy) + { + for (j = CVOXWORDMAX-1; j > ireplace + ccopy; j--) + rgpparseword[j] = rgpparseword[j - ccopy ]; + } + + // replace rgpparseword entry(s) with the substitued name(s) + + rgpparseword[ireplace] = pszNew; + + if ( cnew == 2 || cnew == 3) + rgpparseword[ireplace+1] = pszNew1; + + if ( cnew == 3 ) + rgpparseword[ireplace+2] = pszNew2; + } +} + +// remove 'silent' word from rgpparseword + +void VOX_DeleteWord( int iword ) +{ + if (iword < 0 || iword >= CVOXWORDMAX) + return; + + rgpparseword[iword] = 0; + + // slide all words > iword up into vacated slot + + for (int j = iword; j < CVOXWORDMAX-1; j++) + rgpparseword[j] = rgpparseword[j+1]; +} + + +// get global list of map names from sentences.txt +// map names are stored in order in V_MAPNAMES group + +void VOX_LookupMapnames( void ) +{ + // get group V_MAPNAMES + + int i; + char *psz; + int inext = 0; + + for (i = 0; i < CVOXMAPNAMESMAX; i++) + { + // step sequentially through group - return ptr to 1st word in each group (map name) + + psz = VOX_LookupSentenceByIndex( "V_MAPNAME", i, &inext ); + + if (!psz) + return; + + g_rgmapnames[i] = psz; + g_cmapnames++; + } +} + +// get index of current map name +// return 0 as default index if not found + +int VOX_GetMapNameIndex( const char *pszmapname ) +{ + for (int i = 0; i < g_cmapnames; i++) + { + if ( Q_strstr( pszmapname, g_rgmapnames[i] ) ) + return i; + } + return 0; +} + +// look for virtual 'V_' values in rgpparseword. + // V_MYNAME - replace with saved name value (based on type + entity) + // - if no saved name, create one and save + // V_MYNUM - replace with saved number value (based on type + entity) + // - if no saved num, create on and save + // V_RNDNUM - grab a random number string from V_RNDNUM_<type> + // V_RNDNAME - grab a random name string from V_RNDNAME_<type> + + // replace any 'V_' values with actual string names in rgpparseword + +extern ConVar host_map; +inline bool IsVirtualName( const char *pName ) +{ + return (pName[0] == 'V' && pName[1] == '_'); +} + +void VOX_ReplaceVirtualNames( channel_t *pchan ) +{ + // for each word in the sentence, check for V_, if found + // replace virtual word with saved word or rnd word + + int i = 0; + char *pszNew = NULL; + char *pszNew1 = NULL; + char *pszNew2 = NULL; + int iname = -1; + int cnew = 0; + bool fbymap; + char *pszmaptoken; + SoundSource soundsource = pchan ? pchan->soundsource : 0; + + const char *pszmap = host_map.GetString(); + + // get global list of map names from sentences.txt + + while (rgpparseword[i]) + { + + if ( IsVirtualName( rgpparseword[i] ) ) + { + iname = -1; + cnew = 0; + pszNew = NULL; + pszNew1 = NULL; + pszNew2 = NULL; + char szparseword[256]; + + int slen = Q_strlen(rgpparseword[i]); + char chtype = rgpparseword[i][slen-1]; + + // copy word to temp location so we can perform in-place substitutions + + V_strcpy_safe(szparseword, rgpparseword[i]); + + // fbymap is true if lookup is performed via mapname instead of via ordinal + + pszmaptoken = ( Q_strstr(szparseword, "_MAP__") ); + + fbymap = (pszmaptoken == NULL ? false : true); + + if (fbymap) + { + int imap = VOX_GetMapNameIndex( pszmap ); + imap = clamp (imap, 0, 99); + + // replace last 2 characters in _MAP__ substring + // with imap - this effectively makes all + // '_map_' lookups relative to the mapname + if ( imap >= 10 ) + { + pszmaptoken[4] = (imap/10) + '0'; + pszmaptoken[5] = (imap%10) + '0'; + } + else + { + pszmaptoken[4] = '0'; + pszmaptoken[5] = imap + '0'; + } + } + + if ( Q_strstr(szparseword, "V_MYNAME") ) + { + iname = 1; + } + else if ( Q_strstr(szparseword, "V_MYNUM") ) + { + iname = 0; + } + + if ( iname >= 0 ) + { + + // lookup ent & type, return static, null terminated string + // if no saved string, create one + + pszNew = VOX_LookupMyVirtual( iname, szparseword, chtype, soundsource); + cnew = 1; + } + else + { + if ( Q_strstr(szparseword, "V_RND") ) + { + // lookup random first word from this named group, + // return static, null terminated string + + pszNew = VOX_LookupRndVirtual( szparseword ); + cnew = 1; + } + else if ( Q_strstr(szparseword, "V_DIST") ) + { + // get range from ent to player, return pointers to new words + VOX_LookupRangeHeadingOrGrid( 0, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, true ); + } + else if ( Q_strstr(szparseword, "V_DIR") ) + { + // get heading from ent to player, return pointers to new words + VOX_LookupRangeHeadingOrGrid( 1, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, false); + } + else if ( Q_strstr(szparseword, "V_IDIED") ) + { + // SILENT MARKER - this ent died - mark as dead and timestamp + + int ient = VOX_LookupEntIndex( chtype, soundsource, false); + if (ient < 0) + { + // if not found, allocate new ent, give him a name & number, mark as dead + char szgroup1[32]; + char szgroup2[32]; + V_strcpy_safe(szgroup1, "V_MYNAME"); + szgroup1[8] = chtype; + szgroup1[9] = 0; + + V_strcpy_safe(szgroup2, "V_MYNUM"); + szgroup2[7] = chtype; + szgroup2[8] = 0; + + ient = VOX_LookupEntIndex( chtype, soundsource, true); + g_entnames[ient].pszname = VOX_LookupRndVirtual( szgroup1 ); + g_entnames[ient].psznum = VOX_LookupRndVirtual( szgroup2 ); + } + + g_entnames[ient].fdied = true; + g_entnames[ient].timedied = g_pSoundServices->GetClientTime(); + + // clear this 'silent' word from rgpparseword + + VOX_DeleteWord(i); + + } + else if ( Q_strstr(szparseword, "V_WHODIED") ) + { + // get last dead unit of this type + + int ient = VOX_LookupLastDeadIndex( chtype ); + + // get name and number + + if (ient >= 0) + { + cnew = 1; + pszNew = g_entnames[ient].pszname; + pszNew1 = g_entnames[ient].psznum; + if (pszNew1) + cnew++; + } + else + { + // no dead units, just clear V_WHODIED + + VOX_DeleteWord(i); + } + + } + else if ( Q_strstr(szparseword, "V_SECTOR") ) + { + // sectors are fictional - they simply + // increase sequentially and expire every 5 minutes + + pszNew = VOX_LookupSectorVirtual( szparseword ); + if (pszNew) + cnew = 1; + } + else if ( Q_strstr(szparseword, "V_GRIDX") ) + { + // player x position in 10 meter increments + VOX_LookupRangeHeadingOrGrid( 2, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, true ); + } + else if ( Q_strstr(szparseword, "V_GRIDY") ) + { + // player y position in 10 meter increments + VOX_LookupRangeHeadingOrGrid( 3, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, true ); + + } + else if ( Q_strstr(szparseword, "V_G0_") ) + { + // 4 rnd globals per type, globals expire after 5 minutes + // used for target designation, master sector code name etc. + + pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 0 ); + if (pszNew) + cnew = 1; + } + else if ( Q_strstr(szparseword, "V_G1_") ) + { + // 4 rnd globals per type, globals expire after 5 minutes + // used for target designation, master sector code name etc. + + pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 1 ); + if (pszNew) + cnew = 1; + } + else if ( Q_strstr(szparseword, "V_G2_") ) + { + // 4 rnd globals per type, globals expire after 5 minutes + // used for target designation, master sector code name etc. + + pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 2 ); + if (pszNew) + cnew = 1; + } + else if ( Q_strstr(szparseword, "V_G3_") ) + { + // 4 rnd globals per type, globals expire after 5 minutes + // used for target designation, master sector code name etc. + + pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 3 ); + if (pszNew) + cnew = 1; + } + else if ( Q_strstr(szparseword, "V_SEQG0_") ) + { + // 4 sequential globals per type, selected sequentially in list + // used for total target hit count etc. + + pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 0 ); + if (pszNew) + cnew = 1; + } + else if ( Q_strstr(szparseword, "V_SEQG1_") ) + { + // 4 sequential globals per type, selected sequentially in list + // used for total target hit count etc. + + pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 1 ); + if (pszNew) + cnew = 1; + } + else if ( Q_strstr(szparseword, "V_SEQG2_") ) + { + // 4 sequential globals per type, selected sequentially in list + // used for total target hit count etc. + + pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 2 ); + if (pszNew) + cnew = 1; + } + else if ( Q_strstr(szparseword, "V_SEQG3_") ) + { + // 4 sequential globals per type, selected sequentially in list + // used for total target hit count etc. + + pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 3 ); + if (pszNew) + cnew = 1; + } + + } + + // insert up to 3 new words into rgpparseword at 'i' location + + VOX_InsertWords( i, cnew, pszNew, pszNew1, pszNew2 ); + } + i++; + } +} + +void VOX_Precache( IEngineSound *pSoundSystem, int sentenceIndex, const char *pPathOverride = NULL ) +{ + voxword_t rgvoxword[CVOXWORDMAX]; + char buffer[512]; + char szpath[MAX_PATH]; + char pathbuffer[MAX_PATH]; + char *pWords[CVOXWORDMAX]; // array of pointers to parsed words + + if ( !IsVirtualName(g_Sentences[sentenceIndex].pName)) + { + g_Sentences[sentenceIndex].isPrecached = true; + } + + memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX); + char *psz = (char *)(g_Sentences[sentenceIndex].pName + Q_strlen(g_Sentences[sentenceIndex].pName) + 1); + // get directory from string, advance psz + psz = VOX_GetDirectory(szpath, sizeof( szpath ), psz ); + Q_strncpy(buffer, psz, sizeof( buffer ) ); + psz = buffer; + if ( pPathOverride ) + { + Q_strncpy(szpath, pPathOverride, sizeof(szpath)); + } + + // parse sentence (also inserts null terminators between words) + + VOX_ParseString(psz); + int i = 0, count = 0; + // copy the parsed words out of the globals + for ( i = 0; rgpparseword[i]; i++ ) + { + pWords[i] = rgpparseword[i]; + count++; + } + int cword = 0; + for ( i = 0; i < count; i++ ) + { + if ( IsVirtualName(pWords[i]) ) + { + CUtlVector< WordBuf > list; + + VOX_BuildVirtualNameList( pWords[i], list ); + + int c = list.Count(); + for ( int j = 0 ; j < c; ++j ) + { + Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, list[j].word ); + pSoundSystem->PrecacheSound( pathbuffer, false ); + } + } + else + { + // Get any pitch, volume, start, end params into voxword + if (VOX_ParseWordParams(pWords[i], &rgvoxword[cword], i == 0)) + { + // this is a valid word (as opposed to a parameter block) + Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, pWords[i] ); + // find name, if already in cache, mark voxword + // so we don't discard when word is done playing + pSoundSystem->PrecacheSound( pathbuffer, false ); + cword++; + } + } + } +} + +void VOX_PrecacheSentenceGroup( IEngineSound *pSoundSystem, const char *pGroupName, const char *pPathOverride ) +{ + int i; + + int len = Q_strlen( pGroupName ); + for ( i = 0; i < g_Sentences.Count(); i++ ) + { + if ( !g_Sentences[i].isPrecached && !Q_strncasecmp( g_Sentences[i].pName, pGroupName, len ) ) + { + VOX_Precache( pSoundSystem, i, pPathOverride ); + } + } +} + + +// link all sounds in sentence, start playing first word. +// return number of words loaded +void VOX_LoadSound( channel_t *pchan, const char *pszin ) +{ +#ifndef SWDS + char buffer[512]; + int i, cword; + char pathbuffer[MAX_PATH]; + char szpath[MAX_PATH]; + voxword_t rgvoxword[CVOXWORDMAX]; + char *psz; + bool emitcaption = false; + CUtlSymbol captionSymbol = UTL_INVAL_SYMBOL; + float duration = 0.0f; + + if (!pszin) + return; + + memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX); + memset(buffer, 0, sizeof(buffer)); + + // lookup actual string in g_Sentences, + // set pointer to string data + + psz = VOX_LookupString(pszin, NULL, &emitcaption, &captionSymbol, &duration ); + + if (!psz) + { + DevMsg ("VOX_LoadSound: no sentence named %s\n",pszin); + return; + } + + // get directory from string, advance psz + psz = VOX_GetDirectory(szpath, sizeof( szpath ), psz ); + + if ( Q_strlen(psz) > sizeof(buffer) - 1 ) + { + DevMsg ("VOX_LoadSound: sentence is too long %s\n",psz); + return; + } + + // copy into buffer + Q_strncpy(buffer, psz, sizeof( buffer ) ); + psz = buffer; + + // parse sentence (also inserts null terminators between words) + + VOX_ParseString(psz); + + // replace any 'V_' values with actual string names in rgpparseword + + VOX_ReplaceVirtualNames( pchan ); + + // for each word in the sentence, construct the filename, + // lookup the sfx and save each pointer in a temp array + + i = 0; + cword = 0; + + char captionstream[ 1024 ]; + + char groupname[ 512 ]; + Q_strncpy( groupname, pszin, sizeof( groupname ) ); + + int len = Q_strlen( groupname ); + + while ( len > 0 && V_isdigit( groupname[ len - 1 ] ) ) + { + groupname[ len - 1 ] = 0; + --len; + } + + Q_snprintf( captionstream, sizeof( captionstream ), "%s ", groupname ); + + while (rgpparseword[i]) + { + // Get any pitch, volume, start, end params into voxword + + if (VOX_ParseWordParams(rgpparseword[i], &rgvoxword[cword], i == 0)) + { + // this is a valid word (as opposed to a parameter block) + Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, rgpparseword[i] ); + + // find name, if already in cache, mark voxword + // so we don't discard when word is done playing + rgvoxword[cword].sfx = S_FindName(pathbuffer, + &(rgvoxword[cword].fKeepCached)); + // JAY: HACKHACK: Keep all sentences cached for now + rgvoxword[cword].fKeepCached = 1; + + char captiontoken[ 128 ]; + Q_snprintf( captiontoken, sizeof( captiontoken ), "S(%s%s) ", szpath, rgpparseword[i] ); + + Q_strncat( captionstream, captiontoken, sizeof( captionstream ), COPY_ALL_CHARACTERS ); + + cword++; + } + i++; + } + + pchan->pMixer = NULL; + + if (cword) + { + // some 'virtual' sentences can end up with 0 words + // if no words, then pchan->pMixer is null; chan will be released right away. + + pchan->pMixer = CreateSentenceMixer( rgvoxword ); + if ( !pchan->pMixer ) + return; + + pchan->flags.isSentence = true; + pchan->sfx = rgvoxword[0].sfx; + Assert(pchan->sfx); + + if ( g_pSoundServices ) + { + if ( emitcaption ) + { + if ( captionSymbol != UTL_INVAL_SYMBOL ) + { + g_pSoundServices->EmitCloseCaption( captionSymbol.String(), duration ); + + if ( snd_vox_captiontrace.GetBool() ) + { + Msg( "Vox: caption '%s'\n", captionSymbol.String() ); + } + } + else + { + g_pSoundServices->EmitSentenceCloseCaption( captionstream ); + + if ( snd_vox_captiontrace.GetBool() ) + { + Msg( "Vox: captionstream '%s'\n", captionstream ); + } + } + } + else + { + if ( snd_vox_captiontrace.GetBool() ) + { + Msg( "Vox: No caption for '%s'\n", pszin ? pszin : "NULL" ); + } + } + } + } + +#endif +} + +static bool CCPairLessFunc( const ccpair& lhs, const ccpair& rhs ) +{ + return Q_stricmp( lhs.token.word, rhs.token.word ) < 0; +} + +void VOX_AddNumbers( char *pGroupName, CUtlVector< WordBuf >& list ) +{ + // construct group name from V_NUMBERS + TYPE + for ( int i = 0; i <= 30; ++i ) + { + char sznumbers[16]; + int glen = Q_strlen(pGroupName); + int slen = Q_strlen("V_NUMBERS"); + + V_strcpy_safe(sznumbers, "V_NUMBERS"); + + // insert type character + sznumbers[slen] = pGroupName[glen-1]; + sznumbers[slen+1] = 0; + + WordBuf w; + // w.Set( VOX_LookupString( VOX_LookupSentenceByIndex( sznumbers, i, NULL ), NULL ) ); + w.Set( VOX_LookupSentenceByIndex( sznumbers, i, NULL ) ); + list.AddToTail( w ); + } +} + +void VOX_AddRndVirtual( char *pGroupName, CUtlVector< WordBuf >& list ) +{ + // get group index + + int isentenceg = VOX_GroupIndexFromName( pGroupName ); + + if ( isentenceg < 0) + return; + + char szsentencename[32]; + + char const *szgroupname = g_SentenceGroups[ isentenceg ].GroupName(); + + // get pointer to sentence name within group, using lru + for ( int snum = 0; snum < g_SentenceGroups[ isentenceg ].count; ++snum ) + { + Q_snprintf( szsentencename, sizeof( szsentencename ), "%s%d", szgroupname, snum ); + + char *psz = VOX_LookupString( szsentencename[0] == '!' ? szsentencename+1 : szsentencename, NULL); + + if ( psz ) + { + WordBuf w; + w.Set( psz ); + list.AddToTail( w ); + } + } +} + +void VOX_AddMyVirtualWords( int iname, char *pGroupName, char chtype, CUtlVector< WordBuf >& list ) +{ + VOX_AddRndVirtual( pGroupName, list ); +} + +void VOX_BuildVirtualNameList( char *word, CUtlVector< WordBuf >& list ) +{ + // for each word in the sentence, check for V_, if found + // replace virtual word with saved word or rnd word + + int iname = -1; + bool fbymap; + char *pszmaptoken; + + + char szparseword[256]; + + int slen = Q_strlen(word); + char chtype = word[slen-1]; + + // copy word to temp location so we can perform in-place substitutions + + Q_strncpy( szparseword, word, sizeof( szparseword ) ); + + // fbymap is true if lookup is performed via mapname instead of via ordinal + + pszmaptoken = ( Q_strstr(szparseword, "_MAP__") ); + + fbymap = (pszmaptoken == NULL ? false : true); + + if (fbymap) + { + for ( int imap = 0; imap < g_cmapnames; ++imap ) + { + // replace last 2 characters in _MAP__ substring + // with imap - this effectively makes all + // '_map_' lookups relative to the mapname + pszmaptoken[4] = '0'; + if (imap < 10) + Q_snprintf( &(pszmaptoken[5]), 1, "%1d", imap ); + else + Q_snprintf( &(pszmaptoken[4]), 2, "%d", imap ); + + // Recurse... + VOX_BuildVirtualNameList( szparseword, list ); + } + return; + } + + if ( Q_strstr(szparseword, "V_MYNAME") ) + { + iname = 1; + } + else if ( Q_strstr(szparseword, "V_MYNUM") ) + { + iname = 0; + } + + if ( iname >= 0 ) + { + + // lookup ent & type, return static, null terminated string + // if no saved string, create one + + VOX_AddMyVirtualWords( iname, szparseword, chtype, list ); + } + else + { + if ( Q_strstr(szparseword, "V_RND") ) + { + // lookup random first word from this named group, + // return static, null terminated string + VOX_AddRndVirtual( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_DIST") ) + { + VOX_AddNumbers( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_DIR") ) + { + VOX_AddNumbers( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_IDIED") ) + { + // SILENT MARKER - this ent died - mark as dead and timestamp + + // if not found, allocate new ent, give him a name & number, mark as dead + char szgroup1[32]; + char szgroup2[32]; + V_strcpy_safe(szgroup1, "V_MYNAME"); + szgroup1[8] = chtype; + szgroup1[9] = 0; + + V_strcpy_safe(szgroup2, "V_MYNUM"); + szgroup2[7] = chtype; + szgroup2[8] = 0; + + VOX_BuildVirtualNameList( szgroup1, list ); + VOX_BuildVirtualNameList( szgroup2, list ); + return; + + } + else if ( Q_strstr(szparseword, "V_WHODIED") ) + { + // get last dead unit of this type + /* + + int ient = VOX_LookupLastDeadIndex( chtype ); + + // get name and number + + if (ient >= 0) + { + cnew = 1; + pszNew = g_entnames[ient].pszname; + pszNew1 = g_entnames[ient].psznum; + if (pszNew1) + cnew++; + } + else + { + // no dead units, just clear V_WHODIED + + VOX_DeleteWord(i); + } + */ + + } + else if ( Q_strstr(szparseword, "V_SECTOR") ) + { + VOX_AddNumbers( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_GRIDX") ) + { + VOX_AddNumbers( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_GRIDY") ) + { + VOX_AddNumbers( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_G0_") ) + { + VOX_AddRndVirtual( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_G1_") ) + { + VOX_AddRndVirtual( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_G2_") ) + { + VOX_AddRndVirtual( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_G3_") ) + { + VOX_AddRndVirtual( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_SEQG0_") ) + { + VOX_AddRndVirtual( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_SEQG1_") ) + { + VOX_AddRndVirtual( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_SEQG2_") ) + { + VOX_AddRndVirtual( szparseword, list ); + } + else if ( Q_strstr(szparseword, "V_SEQG3_") ) + { + VOX_AddRndVirtual( szparseword, list ); + } + + } + + if ( Q_strnicmp( szparseword, "V_", 2 ) ) + { + WordBuf w; + w.Set( szparseword ); + list.AddToTail( w ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: For generating reslists, adds the wavefile to the dictionary +// Input : *fn - +//----------------------------------------------------------------------------- +void VOX_Touch( char const *fn, CUtlDict< int, int >& list ) +{ + if ( list.Find( fn ) == list.InvalidIndex() ) + { + list.Insert( fn ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Iterates the touch list and touches all referenced .wav files. +// Input : int - +// list - +//----------------------------------------------------------------------------- +void VOX_TouchSounds( CUtlDict< int, int >& list, CUtlRBTree< ccpair, int >& ccpairs, bool spewsentences ) +{ + int i; + for ( i = list.First(); i != list.InvalidIndex(); i = list.Next( i ) ) + { + char const *fn = list.GetElementName( i ); + + // Msg( "touch %s\n", fn ); + char expanded[ 512 ]; + Q_snprintf( expanded, sizeof( expanded ), "sound/%s", fn ); + + FileHandle_t fh = g_pFileSystem->Open( expanded, "rb" ); + if ( FILESYSTEM_INVALID_HANDLE != fh ) + { + g_pFileSystem->Close( fh ); + } + } + + if ( spewsentences ) + { + for ( i = ccpairs.FirstInorder() ; i != ccpairs.InvalidIndex(); i = ccpairs.NextInorder( i ) ) + { + ccpair& pair = ccpairs[ i ]; + + Msg( "\"%s\"\t\"%s\"\n", + pair.token.word, + pair.value.word ); + } + + FileHandle_t fh = g_pFileSystem->Open( "sentences.m3u", "wt", "GAME" ); + if ( FILESYSTEM_INVALID_HANDLE != fh ) + { + for ( i = ccpairs.FirstInorder() ; i != ccpairs.InvalidIndex(); i = ccpairs.NextInorder( i ) ) + { + ccpair& pair = ccpairs[ i ]; + + char outline[ 512 ]; + Q_snprintf( outline, sizeof( outline ), "%s\n", pair.fullpath.word ); + + g_pFileSystem->Write( outline, Q_strlen(outline), fh ); + } + + g_pFileSystem->Close( fh ); + } + } +} + +// link all sounds in sentence, start playing first word. +// return number of words loaded +void VOX_TouchSound( const char *pszin, CUtlDict< int, int >& filelist, CUtlRBTree< ccpair, int >& ccpairs, bool spewsentences ) +{ +#ifndef SWDS + char buffer[512]; + int i, cword; + char pathbuffer[MAX_PATH]; + char szpath[MAX_PATH]; + voxword_t rgvoxword[CVOXWORDMAX]; + char *psz; + + if (!pszin) + return; + + memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX); + memset(buffer, 0, sizeof(buffer)); + + // lookup actual string in g_Sentences, + // set pointer to string data + + psz = VOX_LookupString(pszin, NULL); + + if (!psz) + { + DevMsg ("VOX_TouchSound: no sentence named %s\n",pszin); + return; + } + + // get directory from string, advance psz + psz = VOX_GetDirectory(szpath, sizeof( szpath ), psz ); + + if ( Q_strlen(psz) > sizeof(buffer) - 1 ) + { + DevMsg ("VOX_TouchSound: sentence is too long %s\n",psz); + return; + } + + // copy into buffer + Q_strncpy(buffer, psz, sizeof( buffer ) ); + psz = buffer; + + // parse sentence (also inserts null terminators between words) + + VOX_ParseString(psz); + + // for each word in the sentence, construct the filename, + // lookup the sfx and save each pointer in a temp array + + i = 0; + cword = 0; + + CUtlVector< WordBuf > rep; + + while (rgpparseword[i]) + { + // Get any pitch, volume, start, end params into voxword + + if ( VOX_ParseWordParams(rgpparseword[i], &rgvoxword[cword], i == 0 ) ) + { + // Iterate all virtuals here... + if ( !Q_strnicmp( rgpparseword[i], "V_", 2 ) ) + { + CUtlVector< WordBuf > list; + + VOX_BuildVirtualNameList( rgpparseword[i], list ); + + int c = list.Count(); + for ( int j = 0 ; j < c; ++j ) + { + char name[ 256 ]; + Q_snprintf( name, sizeof( name ), "%s", list[ j ].word ); + + if ( !Q_strnicmp( name, "V_", 2 ) ) + { + Warning( "VOX_TouchSound didn't resolve virtual token %s!\n", name ); + } + + Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, name ); + VOX_Touch( pathbuffer, filelist ); + + WordBuf w; + if ( j == 0 ) + { + w.Set( name ); + rep.AddToTail( w ); + } + ccpair pair; + Q_snprintf( pair.token.word, sizeof( pair.token.word ), "S(%s%s)", szpath, name ); + pair.value.Set( name ); + + Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s/sound/%s%s.wav", g_pSoundServices->GetGameDir(), szpath, name ); + Q_FixSlashes( pathbuffer, '\\' ); + pair.fullpath.Set( pathbuffer ); + + if ( ccpairs.Find( pair ) == ccpairs.InvalidIndex() ) + { + ccpairs.Insert( pair ); + } + } + } + else + { + // this is a valid word (as opposed to a parameter block) + Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, rgpparseword[i] ); + VOX_Touch( pathbuffer, filelist ); + + WordBuf w; + w.Set( rgpparseword[ i ] ); + rep.AddToTail( w ); + + ccpair pair; + Q_snprintf( pair.token.word, sizeof( pair.token.word ), "S(%s%s)", szpath, rgpparseword[i] ); + pair.value.Set( rgpparseword[i] ); + + Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s/sound/%s%s.wav", g_pSoundServices->GetGameDir(), szpath, rgpparseword[ i ] ); + Q_FixSlashes( pathbuffer, CORRECT_PATH_SEPARATOR ); + pair.fullpath.Set( pathbuffer ); + + if ( ccpairs.Find( pair ) == ccpairs.InvalidIndex() ) + { + ccpairs.Insert( pair ); + } + } + } + i++; + } + + if ( spewsentences ) + { + char outbuf[ 1024 ]; + // Build representative text + outbuf[ 0 ] = 0; + for ( i = 0; i < rep.Count(); ++i ) + { + /* + if ( !Q_stricmp( rep[ i ].word, "_comma" ) ) + { + if ( i != 0 && Q_strlen( outbuf ) >= 1 ) + { + outbuf[ Q_strlen( outbuf ) - 1 ] =0; + } + + // Don't end sentence with comma.. + if ( i != rep.Count() - 1 ) + { + Q_strncat( outbuf, ", ", sizeof( outbuf ), COPY_ALL_CHARACTERS ); + } + continue; + } + */ + + Q_strncat( outbuf, rep[ i ].word, sizeof( outbuf ), COPY_ALL_CHARACTERS ); + if ( i != rep.Count() - 1 ) + { + Q_strncat( outbuf, " ", sizeof( outbuf ), COPY_ALL_CHARACTERS ); + } + } + + Msg( " %s\n", outbuf ); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Take a NULL terminated sentence, and parse any commands contained in +// {}. The string is rewritten in place with those commands removed. +// +// Input : *pSentenceData - sentence data to be modified in place +// sentenceIndex - global sentence table index for any data that is +// parsed out +//----------------------------------------------------------------------------- +void VOX_ParseLineCommands( char *pSentenceData, int sentenceIndex ) +{ + char tempBuffer[512]; + char *pNext, *pStart; + int length, tempBufferPos = 0; + + if ( !pSentenceData ) + return; + + pStart = pSentenceData; + + while ( *pSentenceData ) + { + pNext = ScanForwardUntil( pSentenceData, '{' ); + + // Find length of "good" portion of the string (not a {} command) + length = pNext - pSentenceData; + if ( tempBufferPos + length > sizeof(tempBuffer) ) + { + DevMsg("Error! sentence too long!\n" ); + return; + } + + // Copy good string to temp buffer + memcpy( tempBuffer + tempBufferPos, pSentenceData, length ); + + // Move the copy position + tempBufferPos += length; + + pSentenceData = pNext; + + // Skip ahead of the opening brace + if ( *pSentenceData ) + { + pSentenceData++; + } + + while ( 1 ) + { + // Skip whitespace + while ( *pSentenceData && *pSentenceData <= 32 ) + { + pSentenceData++; + } + + // Simple comparison of string commands: + switch( tolower( *pSentenceData ) ) + { + case 'l': + // All commands starting with the letter 'l' here + if ( !Q_strnicmp( pSentenceData, "len", 3 ) ) + { + g_Sentences[sentenceIndex].length = atof( pSentenceData + 3 ) ; + + // "len " len + space + pSentenceData += 4; + + // Skip until next } or whitespace character + while ( *pSentenceData && ( *pSentenceData != '}' && !( *pSentenceData <= 32 ) ) ) + pSentenceData++; + } + break; + case 'c': + // This sentence should emit a close caption + if ( !Q_strnicmp( pSentenceData, "closecaption", 12 ) ) + { + g_Sentences[sentenceIndex].closecaption = true; + + pSentenceData += 12; + + pSentenceData = (char *)COM_Parse( pSentenceData ); + + // Skip until next } or whitespace character + while ( *pSentenceData && ( *pSentenceData != '}' && !( *pSentenceData <= 32 ) ) ) + pSentenceData++; + + if ( Q_strlen( com_token ) > 0 ) + { + g_Sentences[sentenceIndex].caption = com_token; + } + else + { + g_Sentences[sentenceIndex].caption = UTL_INVAL_SYMBOL; + } + } + break; + case 0: + default: + { + // Skip until next } or whitespace character + while ( *pSentenceData && ( *pSentenceData != '}' && !( *pSentenceData <= 32 ) ) ) + pSentenceData++; + } + break; + } + + // Done? + if ( !*pSentenceData || *pSentenceData == '}' ) + { + break; + } + } + + // pSentenceData = ScanForwardUntil( pSentenceData, '}' ); + + // Skip the closing brace + if ( *pSentenceData ) + pSentenceData++; + + // Skip trailing whitespace + while ( *pSentenceData && *pSentenceData <= 32 ) + pSentenceData++; + } + + if ( tempBufferPos < sizeof(tempBuffer) ) + { + // terminate cleaned up copy + tempBuffer[ tempBufferPos ] = 0; + + // Copy it over the original data + Q_strcpy( pStart, tempBuffer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Add a new group or increment count of the existing one +// Input : *pSentenceName - text of the sentence name +//----------------------------------------------------------------------------- +int VOX_GroupAdd( const char *pSentenceName ) +{ + int len = strlen( pSentenceName ) - 1; + + // group members end in a number + if ( len <= 0 || !V_isdigit(pSentenceName[len]) ) + return -1; + + // truncate away the index + while ( len > 0 && V_isdigit(pSentenceName[len]) ) + { + len--; + } + + // make a copy of the actual group name + char *groupName = (char *)stackalloc( len + 2 ); + Q_strncpy( groupName, pSentenceName, len+2 ); + + // check for it in the list + int i; + sentencegroup_t *pGroup; + + CUtlSymbol symGroupName = sentencegroup_t::GetSymbol( groupName ); + int groupCount = g_SentenceGroups.Size(); + for ( i = 0; i < groupCount; i++ ) + { + int groupIndex = (i + groupCount-1) % groupCount; + + // Start at the last group a loop around + pGroup = &g_SentenceGroups[groupIndex]; + if ( symGroupName == pGroup->GroupNameSymbol() ) + { + // Matches previous group, bump count + pGroup->count++; + return i; + } + } + + // new group + int addIndex = g_SentenceGroups.AddToTail(); + sentencegroup_t *group = &g_SentenceGroups[addIndex]; + group->SetGroupName( groupName ); + group->count = 1; + return addIndex; +} + +#if DEAD +//----------------------------------------------------------------------------- +// Purpose: clear the sentence groups +//----------------------------------------------------------------------------- +void VOX_GroupClear( void ) +{ + g_SentenceGroups.RemoveAll(); +} +#endif + + +void VOX_LRUInit( sentencegroup_t *pGroup ) +{ + int i, n1, n2, temp; + + if ( pGroup->count ) + { + unsigned char *pLRU = &g_GroupLRU[pGroup->lru]; + for (i = 0; i < pGroup->count; i++) + pLRU[i] = (unsigned char) i; + + // randomize array by swapping random elements + for (i = 0; i < (pGroup->count * 4); i++) + { + // FIXME: This should probably call through g_pSoundServices + // or some other such call? + n1 = RandomInt(0,pGroup->count-1); + n2 = RandomInt(0,pGroup->count-1); + temp = pLRU[n1]; + pLRU[n1] = pLRU[n2]; + pLRU[n2] = temp; + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Init the LRU for each sentence group +//----------------------------------------------------------------------------- +void VOX_GroupInitAllLRUs( void ) +{ + int i; + + int totalCount = 0; + for ( i = 0; i < g_SentenceGroups.Size(); i++ ) + { + g_SentenceGroups[i].lru = totalCount; + totalCount += g_SentenceGroups[i].count; + } + g_GroupLRU.Purge(); + g_GroupLRU.EnsureCount( totalCount ); + for ( i = 0; i < g_SentenceGroups.Size(); i++ ) + { + VOX_LRUInit( &g_SentenceGroups[i] ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Only during reslist generation +//----------------------------------------------------------------------------- +void VOX_AddSentenceWavesToResList( void ) +{ + if ( !CommandLine()->FindParm( "-makereslists" ) && + !CommandLine()->FindParm( "-spewsentences" ) ) + { + return; + } + + bool spewsentences = CommandLine()->FindParm( "-spewsentences" ) != 0 ? true : false; + + CUtlDict< int, int > list; + CUtlRBTree< ccpair, int > ccpairs( 0, 0, CCPairLessFunc ); + + int i; + int sentencecount = g_Sentences.Count(); + + for ( i = 0; i < sentencecount; i++ ) + { + // Walk through all nonvirtual sentences and touch the referenced sounds... + sentence_t *pSentence = &g_Sentences[i]; + + if ( !Q_strnicmp( pSentence->pName, "V_", 2 ) ) + { + continue; + } + + if ( spewsentences ) + { + const char *psz = VOX_LookupString(pSentence->pName, NULL); + if ( psz ) + { + Msg( "%s : %s\n", pSentence->pName, psz ); + } + } + + VOX_TouchSound( pSentence->pName, list, ccpairs, spewsentences ); + + } + + VOX_TouchSounds( list, ccpairs, spewsentences ); + + list.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Given a group name, return that group's index +// Input : *pGroupName - name of the group +// Output : int - index in group table, returns -1 if no matching group is found +//----------------------------------------------------------------------------- +int VOX_GroupIndexFromName( const char *pGroupName ) +{ + int i; + + if ( pGroupName ) + { + // search rgsentenceg for match on szgroupname + CUtlSymbol symGroupName = sentencegroup_t::GetSymbol( pGroupName ); + for ( i = 0; i < g_SentenceGroups.Size(); i++ ) + { + if ( symGroupName == g_SentenceGroups[i].GroupNameSymbol() ) + return i; + } + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: return the group's name +// Input : groupIndex - index of the group +// Output : const char * - name pointer +//----------------------------------------------------------------------------- +const char *VOX_GroupNameFromIndex( int groupIndex ) +{ + if ( groupIndex >= 0 && groupIndex < g_SentenceGroups.Size() ) + return g_SentenceGroups[groupIndex].GroupName(); + + return NULL; +} + +// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence, +// then repeat list if freset is true. If freset is false, then repeat last sentence. +// ipick is passed in as the requested sentence ordinal. +// ipick 'next' is returned. +// return of -1 indicates an error. + +int VOX_GroupPickSequential( int isentenceg, char *szfound, int szfoundLen, int ipick, int freset ) +{ + const char *szgroupname; + unsigned char count; + + if (isentenceg < 0 || isentenceg > g_SentenceGroups.Size()) + return -1; + + szgroupname = g_SentenceGroups[isentenceg].GroupName(); + count = g_SentenceGroups[isentenceg].count; + + if (count == 0) + return -1; + + if (ipick >= count) + ipick = count-1; + + Q_snprintf( szfound, szfoundLen, "!%s%d", szgroupname, ipick ); + + if (ipick >= count) + { + if (freset) + // reset at end of list + return 0; + else + return count; + } + + return ipick + 1; +} + + + +// pick a random sentence from rootname0 to rootnameX. +// picks from the rgsentenceg[isentenceg] least +// recently used, modifies lru array. returns the sentencename. +// note, lru must be seeded with 0-n randomized sentence numbers, with the +// rest of the lru filled with -1. The first integer in the lru is +// actually the size of the list. Returns ipick, the ordinal +// of the picked sentence within the group. + +int VOX_GroupPick( int isentenceg, char *szfound, int strLen ) +{ + const char *szgroupname; + unsigned char *plru; + unsigned char i; + unsigned char count; + unsigned char ipick=0; + int ffound = FALSE; + + if (isentenceg < 0 || isentenceg > g_SentenceGroups.Size()) + return -1; + + szgroupname = g_SentenceGroups[isentenceg].GroupName(); + count = g_SentenceGroups[isentenceg].count; + plru = &g_GroupLRU[g_SentenceGroups[isentenceg].lru]; + + while (!ffound) + { + for (i = 0; i < count; i++) + if (plru[i] != 0xFF) + { + ipick = plru[i]; + plru[i] = 0xFF; + ffound = TRUE; + break; + } + + if (!ffound) + { + VOX_LRUInit( &g_SentenceGroups[isentenceg] ); + } + else + { + Q_snprintf( szfound, strLen, "!%s%d", szgroupname, ipick ); + return ipick; + } + } + return -1; +} + + +struct filelist_t +{ + const char *pFileName; + filelist_t *pNext; +}; + +static filelist_t *g_pSentenceFileList = NULL; + +//----------------------------------------------------------------------------- +// Purpose: clear / reinitialize the vox list +//----------------------------------------------------------------------------- +void VOX_ListClear( void ) +{ + filelist_t *pList, *pNext; + + pList = g_pSentenceFileList; + + while ( pList ) + { + pNext = pList->pNext; + free( pList ); + + pList = pNext; + } + + g_pSentenceFileList = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if this file is in the list +// Input : *psentenceFileName - +// Output : int, true if the file is in the list, false if not +//----------------------------------------------------------------------------- +int VOX_ListFileIsLoaded( const char *psentenceFileName ) +{ + filelist_t *pList = g_pSentenceFileList; + while ( pList ) + { + if ( !strcmp( psentenceFileName, pList->pFileName ) ) + return true; + + pList = pList->pNext; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Add this file name to the sentence list +// Input : *psentenceFileName - +//----------------------------------------------------------------------------- +void VOX_ListMarkFileLoaded( const char *psentenceFileName ) +{ + filelist_t *pEntry; + char *pName; + + pEntry = (filelist_t *)malloc( sizeof(filelist_t) + strlen( psentenceFileName ) + 1); + + if ( pEntry ) + { + pName = (char *)(pEntry+1); + Q_strcpy( pName, psentenceFileName ); + + pEntry->pFileName = pName; + pEntry->pNext = g_pSentenceFileList; + + g_pSentenceFileList = pEntry; + } +} + +// This creates a compact copy of the sentence file in memory with only the necessary data +void VOX_CompactSentenceFile() +{ + int totalMem = 0; + int i; + for ( i = 0; i < g_Sentences.Count(); i++ ) + { + int len = Q_strlen( g_Sentences[i].pName ) + 1; + const char *pData = g_Sentences[i].pName + len; + int dataLen = Q_strlen( pData ) + 1; + totalMem += len + dataLen; + } + g_SentenceFile.EnsureCount( totalMem ); + totalMem = 0; + for ( i = 0; i < g_Sentences.Count(); i++ ) + { + int len = Q_strlen( g_Sentences[i].pName ) + 1; + const char *pData = g_Sentences[i].pName + len; + int dataLen = Q_strlen( pData ) + 1; + char *pDest = &g_SentenceFile[totalMem]; + memcpy( pDest, g_Sentences[i].pName, len + dataLen ); + g_Sentences[i].pName = pDest; + totalMem += len + dataLen; + } +} + +// Load sentence file into memory, insert null terminators to +// delimit sentence name/sentence pairs. Keep pointer to each +// sentence name so we can search later. + +void VOX_ReadSentenceFile( const char *psentenceFileName ) +{ + char *pch; + byte *pFileData; + int fileSize; + char c; + char *pchlast, *pSentenceData; + characterset_t whitespace; + + // Have we already loaded this file? + if ( VOX_ListFileIsLoaded( psentenceFileName ) ) + { + // must touch any sentence wavs again to ensure the map's init path gets the results + if ( MapReslistGenerator().IsLoggingToMap() ) + { + VOX_AddSentenceWavesToResList(); + } + return; + } + + // load file + + FileHandle_t file; + file = g_pFileSystem->Open( psentenceFileName, "rb" ); + if ( FILESYSTEM_INVALID_HANDLE == file ) + { + DevMsg ("Couldn't load %s\n", psentenceFileName); + return; + } + + fileSize = g_pFileSystem->Size( file ); + if ( fileSize <= 0 ) + { + DevMsg ("VOX_ReadSentenceFile: %s has invalid size %i\n", psentenceFileName, fileSize ); + g_pFileSystem->Close( file ); + return; + } + + pFileData = (byte *)g_pFileSystem->AllocOptimalReadBuffer( file, fileSize + 1 ); + if ( !pFileData ) + { + DevMsg ("VOX_ReadSentenceFile: %s couldn't allocate %i bytes for data\n", psentenceFileName, fileSize ); + g_pFileSystem->Close( file ); + return; + } + + // Read the data and close the file + g_pFileSystem->ReadEx( pFileData, g_pFileSystem->GetOptimalReadSize( file, fileSize ), fileSize, file ); + g_pFileSystem->Close( file ); + + // Make sure we end with a null terminator + pFileData[ fileSize ] = 0; + + pch = (char *)pFileData; + pchlast = pch + fileSize; + CharacterSetBuild( &whitespace, "\n\r\t " ); + const char *pName = 0; + while (pch < pchlast) + { + // Only process this pass on sentences + pSentenceData = NULL; + + // skip newline, cr, tab, space + + c = *pch; + while (pch < pchlast && IN_CHARACTERSET( whitespace, c )) + c = *(++pch); + + // YWB: Fix possible crashes reading past end of file if the last line has only whitespace on it... + if ( !*pch ) + break; + + // skip entire line if first char is / + if (*pch != '/') + { + int addIndex = g_Sentences.AddToTail(); + sentence_t *pSentence = &g_Sentences[addIndex]; + pName = pch; + pSentence->pName = pch; + pSentence->length = 0; + pSentence->closecaption = false; + pSentence->isPrecached = false; + pSentence->caption = UTL_INVAL_SYMBOL; + + // scan forward to first space, insert null terminator + // after sentence name + + c = *pch; + while (pch < pchlast && c != ' ') + c = *(++pch); + + if (pch < pchlast) + *pch++ = 0; + + // A sentence may have some line commands, make an extra pass + pSentenceData = pch; + } + // scan forward to end of sentence or eof + while (pch < pchlast && pch[0] != '\n' && pch[0] != '\r') + pch++; + + // insert null terminator + if (pch < pchlast) + *pch++ = 0; + + // If we have some sentence data, parse out any line commands + if ( pSentenceData && pSentenceData < pchlast ) + { + // Add a new group or increment count of the existing one + VOX_GroupAdd( pName ); + int index = g_Sentences.Size()-1; + // The current sentence has an index of count-1 + VOX_ParseLineCommands( pSentenceData, index ); + + } + } + // now compact the file data in memory + VOX_CompactSentenceFile(); + g_pFileSystem->FreeOptimalReadBuffer( pFileData ); + + VOX_GroupInitAllLRUs(); + + // This only does stuff during reslist generation... + VOX_AddSentenceWavesToResList(); + + VOX_ListMarkFileLoaded( psentenceFileName ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the current number of sentences in the database +// Output : int +//----------------------------------------------------------------------------- +int VOX_SentenceCount( void ) +{ + return g_Sentences.Size(); +} + + +float VOX_SentenceLength( int sentence_num ) +{ + if ( sentence_num < 0 || sentence_num > g_Sentences.Size()-1 ) + return 0.0f; + + return g_Sentences[ sentence_num ].length; +} + +// scan g_Sentences, looking for pszin sentence name +// return pointer to sentence data if found, null if not +// CONSIDER: if we have a large number of sentences, should +// CONSIDER: sort strings in g_Sentences and do binary search. +char *VOX_LookupString(const char *pSentenceName, int *psentencenum, bool *pbEmitCaption /*=NULL*/, CUtlSymbol *pCaptionSymbol /*=NULL*/, float *pflDuration /*= NULL*/ ) +{ + if ( pbEmitCaption ) + { + *pbEmitCaption = false; + } + + if ( pCaptionSymbol ) + { + *pCaptionSymbol = UTL_INVAL_SYMBOL; + } + + if ( pflDuration ) + { + *pflDuration = 0.0f; + } + + int i; + int c = g_Sentences.Size(); + for (i = 0; i < c; i++) + { + char const *name = g_Sentences[i].pName; + + if (!stricmp(pSentenceName, name)) + { + if (psentencenum) + { + *psentencenum = i; + } + + if ( pbEmitCaption ) + { + *pbEmitCaption = g_Sentences[ i ].closecaption; + } + + if ( pCaptionSymbol ) + { + *pCaptionSymbol = g_Sentences[ i ].caption; + } + + if ( pflDuration ) + { + *pflDuration = g_Sentences[ i ].length; + } + + return (char *)(name + Q_strlen(name) + 1); + } + } + return NULL; +} + + +// Abstraction for sentence name array +const char *VOX_SentenceNameFromIndex( int sentencenum ) +{ + if ( sentencenum < g_Sentences.Size() ) + return g_Sentences[sentencenum].pName; + return NULL; +} + + + |