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.
621 lines
16 KiB
621 lines
16 KiB
![]()
4 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
//=====================================================================================//
|
||
|
|
||
|
#include "cbase.h"
|
||
|
#include <ctype.h>
|
||
|
#include "tf_autorp.h"
|
||
|
#include "filesystem.h"
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include "tier0/memdbgon.h"
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CTFAutoRP *AutoRP( void )
|
||
|
{
|
||
|
static CTFAutoRP *pSystem = NULL;
|
||
|
if ( !pSystem )
|
||
|
{
|
||
|
pSystem = new CTFAutoRP();
|
||
|
pSystem->ParseDataFile();
|
||
|
}
|
||
|
|
||
|
return pSystem;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFAutoRP::ParseDataFile( void )
|
||
|
{
|
||
|
Assert( !m_pDataFileKV );
|
||
|
|
||
|
// Load & parse the word files
|
||
|
KeyValues *pFileKV = new KeyValues( "AutoRPFile" );
|
||
|
if ( pFileKV->LoadFromFile( filesystem, "scripts/autorp.txt", "MOD" ) == false )
|
||
|
return;
|
||
|
|
||
|
m_pDataFileKV = pFileKV->MakeCopy();
|
||
|
|
||
|
// Prepended word list
|
||
|
KeyValues *pKVPrepended = m_pDataFileKV->FindKey( "prepended_words" );
|
||
|
if ( pKVPrepended )
|
||
|
{
|
||
|
FOR_EACH_SUBKEY( pKVPrepended, pKVKey )
|
||
|
{
|
||
|
m_a_pszPrependedWords.AddToTail( pKVKey->GetName() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Appended word list
|
||
|
KeyValues *pKVAppended = m_pDataFileKV->FindKey( "appended_words" );
|
||
|
if ( pKVAppended )
|
||
|
{
|
||
|
FOR_EACH_SUBKEY( pKVAppended, pKVKey )
|
||
|
{
|
||
|
m_a_pszAppendedWords.AddToTail( pKVKey->GetName() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Word replacements
|
||
|
KeyValues *pKVReplacements = m_pDataFileKV->FindKey( "word_replacements" );
|
||
|
if ( pKVReplacements )
|
||
|
{
|
||
|
FOR_EACH_SUBKEY( pKVReplacements, pKVEntry )
|
||
|
{
|
||
|
int iIdx = m_a_Replacements.AddToTail();
|
||
|
m_a_Replacements[iIdx].iChance = 1;
|
||
|
m_a_Replacements[iIdx].iPrePendCount = 1;
|
||
|
FOR_EACH_SUBKEY( pKVEntry, pKVKey )
|
||
|
{
|
||
|
const char *pszKey = pKVKey->GetName();
|
||
|
const char *pszValue = pKVKey->GetString();
|
||
|
|
||
|
if ( FStrEq(pszKey,"replacement") )
|
||
|
{
|
||
|
m_a_Replacements[iIdx].a_pszReplacements.AddToTail( pszValue );
|
||
|
}
|
||
|
else if ( FStrEq(pszKey,"replacement_prepend") )
|
||
|
{
|
||
|
m_a_Replacements[iIdx].a_pszPrepended.AddToTail( pszValue );
|
||
|
}
|
||
|
else if ( FStrEq(pszKey,"replacement_plural") )
|
||
|
{
|
||
|
m_a_Replacements[iIdx].a_pszPluralReplacements.AddToTail( pszValue );
|
||
|
}
|
||
|
else if ( FStrEq(pszKey,"prepend_count") )
|
||
|
{
|
||
|
m_a_Replacements[iIdx].iPrePendCount = pKVKey->GetInt();
|
||
|
}
|
||
|
else if ( FStrEq(pszKey,"chance") )
|
||
|
{
|
||
|
m_a_Replacements[iIdx].iChance = pKVKey->GetInt();
|
||
|
}
|
||
|
else if ( FStrEq(pszKey,"word") )
|
||
|
{
|
||
|
m_a_Replacements[iIdx].m_Words.AddToTail( m_pWordTable->AddString( pszValue ) );
|
||
|
}
|
||
|
else if ( FStrEq(pszKey,"word_plural") )
|
||
|
{
|
||
|
m_a_Replacements[iIdx].m_Plurals.AddToTail( m_pWordTable->AddString( pszValue ) );
|
||
|
}
|
||
|
else if ( FStrEq(pszKey,"prev") )
|
||
|
{
|
||
|
m_a_Replacements[iIdx].m_PrevWords.AddToTail( m_pWordTable->AddString( pszValue ) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Assert(0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFAutoRP::ApplyRPTo( char *pBuf, int iBufSize )
|
||
|
{
|
||
|
if ( !m_pDataFileKV )
|
||
|
return;
|
||
|
if ( !pBuf || !pBuf[0] )
|
||
|
return;
|
||
|
|
||
|
// Ignore sourceMod commands
|
||
|
if ( pBuf[0] == '!' || pBuf[0] == '/' )
|
||
|
return;
|
||
|
|
||
|
bool bDoPends = true;
|
||
|
|
||
|
char *pszIn = new char[iBufSize];
|
||
|
if ( pBuf[0] == '-' )
|
||
|
{
|
||
|
bDoPends = false;
|
||
|
Q_strncpy( pszIn, pBuf+1, iBufSize-1 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncpy( pszIn, pBuf, iBufSize );
|
||
|
}
|
||
|
pBuf[0] = '\0';
|
||
|
|
||
|
ModifySpeech( pszIn, pBuf, iBufSize, bDoPends, false );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
const char *CTFAutoRP::GetRandomPre( void )
|
||
|
{
|
||
|
if ( RandomInt(1,4) != 1 )
|
||
|
return NULL;
|
||
|
|
||
|
if ( !m_a_pszPrependedWords.Count() )
|
||
|
return NULL;
|
||
|
|
||
|
static int iPrevPre = 0;
|
||
|
iPrevPre += RandomInt(1,4);
|
||
|
while ( iPrevPre >= m_a_pszPrependedWords.Count() )
|
||
|
{
|
||
|
iPrevPre -= m_a_pszPrependedWords.Count();
|
||
|
}
|
||
|
|
||
|
return m_a_pszPrependedWords[iPrevPre];
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
const char *CTFAutoRP::GetRandomPost( void )
|
||
|
{
|
||
|
if ( RandomInt(1,5) != 1 )
|
||
|
return NULL;
|
||
|
|
||
|
if ( !m_a_pszAppendedWords.Count() )
|
||
|
return NULL;
|
||
|
|
||
|
static int iPrevPost = 0;
|
||
|
iPrevPost += RandomInt(1,3);
|
||
|
while ( iPrevPost >= m_a_pszAppendedWords.Count() )
|
||
|
{
|
||
|
iPrevPost -= m_a_pszAppendedWords.Count();
|
||
|
}
|
||
|
|
||
|
return m_a_pszAppendedWords[iPrevPost];
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
matchresult_t CTFAutoRP::WordMatches( wordreplacement_t *pRep, replacementcheck_t *pCheck )
|
||
|
{
|
||
|
if ( pRep->iChance != 1 )
|
||
|
{
|
||
|
if ( RandomInt( 1, pRep->iChance ) > 1 )
|
||
|
return MATCHES_NOT;
|
||
|
}
|
||
|
|
||
|
// If it has prewords, make sure the preword matches first
|
||
|
if ( pRep->m_PrevWords.Count() > 0 )
|
||
|
{
|
||
|
if ( pCheck->iPrevLen <= 0 )
|
||
|
return MATCHES_NOT;
|
||
|
|
||
|
CUtlSymbol sym = m_pWordTable->Find( pCheck->szPrevWord );
|
||
|
if ( UTL_INVAL_SYMBOL == sym )
|
||
|
return MATCHES_NOT;
|
||
|
|
||
|
bool bMatchPrev = false;
|
||
|
FOR_EACH_VEC( pRep->m_PrevWords, i )
|
||
|
{
|
||
|
if ( pRep->m_PrevWords[i] == sym )
|
||
|
{
|
||
|
bMatchPrev = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !bMatchPrev )
|
||
|
return MATCHES_NOT;
|
||
|
|
||
|
pCheck->bUsedPrevWord = true;
|
||
|
}
|
||
|
|
||
|
CUtlSymbol sym = m_pWordTable->Find( pCheck->szWord );
|
||
|
FOR_EACH_VEC( pRep->m_Words, i )
|
||
|
{
|
||
|
if ( pRep->m_Words[i] == sym )
|
||
|
return MATCHES_SINGULAR;
|
||
|
}
|
||
|
|
||
|
CUtlSymbol pluralsym = m_pWordTable->Find( pCheck->szWord );
|
||
|
FOR_EACH_VEC( pRep->m_Plurals, i )
|
||
|
{
|
||
|
if ( pRep->m_Plurals[i] == pluralsym )
|
||
|
return MATCHES_PLURAL;
|
||
|
}
|
||
|
|
||
|
pCheck->bUsedPrevWord = false;
|
||
|
return MATCHES_NOT;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CTFAutoRP::ReplaceWord( replacementcheck_t *pCheck, char *szRep, int iRepSize, bool bSymbols, bool bWordListOnly )
|
||
|
{
|
||
|
szRep[0] = '\0';
|
||
|
|
||
|
// First, see if we have a replacement
|
||
|
FOR_EACH_VEC( m_a_Replacements, i )
|
||
|
{
|
||
|
wordreplacement_t *pRep = &m_a_Replacements[i];
|
||
|
matchresult_t iRes = WordMatches( pRep, pCheck );
|
||
|
if ( iRes == MATCHES_NOT )
|
||
|
continue;
|
||
|
|
||
|
if ( pRep->a_pszPrepended.Count() > 0 )
|
||
|
{
|
||
|
CUtlVector<int> vecUsed;
|
||
|
for ( int iCount = 0; iCount < pRep->iPrePendCount; iCount++ )
|
||
|
{
|
||
|
// Ensure we don't choose two of the same prepends
|
||
|
int rnd = 0;
|
||
|
do
|
||
|
{
|
||
|
rnd = RandomInt( 0, (int)pRep->a_pszPrepended.Count() - 1 );
|
||
|
} while ( vecUsed.Find(rnd) != vecUsed.InvalidIndex() );
|
||
|
vecUsed.AddToTail(rnd);
|
||
|
|
||
|
Q_strncat( szRep, pRep->a_pszPrepended[rnd], iRepSize );
|
||
|
if ( (iCount+1) < pRep->iPrePendCount )
|
||
|
{
|
||
|
Q_strncat( szRep, ", ", iRepSize );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncat( szRep, " ", iRepSize );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( iRes == MATCHES_SINGULAR )
|
||
|
{
|
||
|
int rnd = RandomInt( 0, (int)pRep->a_pszReplacements.Count() - 1 );
|
||
|
Q_strncat( szRep, pRep->a_pszReplacements[rnd], iRepSize );
|
||
|
}
|
||
|
else if ( iRes == MATCHES_PLURAL )
|
||
|
{
|
||
|
int rnd = RandomInt( 0, (int)pRep->a_pszPluralReplacements.Count() - 1 );
|
||
|
Q_strncat( szRep, pRep->a_pszPluralReplacements[rnd], iRepSize );
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( !bSymbols && !bWordListOnly )
|
||
|
{
|
||
|
char fc = pCheck->szWord[0];
|
||
|
|
||
|
// Randomly replace h's at the front of words with apostrophes
|
||
|
if ( fc == 'h' && RandomInt(1,2) == 1 )
|
||
|
{
|
||
|
Q_strncpy( szRep, pCheck->szWord, MIN( iRepSize, pCheck->iWordLen+1 ) );
|
||
|
szRep[0] = '\'';
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
char lc = pCheck->szWord[ pCheck->iWordLen-1 ];
|
||
|
if ( pCheck->iWordLen > 3 )
|
||
|
{
|
||
|
char slc = pCheck->szWord[ pCheck->iWordLen-2 ];
|
||
|
char lllc = pCheck->szWord[ pCheck->iWordLen-3 ];
|
||
|
|
||
|
// Randomly modify words ending in "ed", by replacing the "e" with an apostrophe
|
||
|
// i.e. "worked" -> "work'd", "waited" -> "wait'd"
|
||
|
if ( slc == 'e' && lc == 'd' && lllc != 'e' && RandomInt(1,4) == 1 )
|
||
|
{
|
||
|
Q_strncpy( szRep, pCheck->szWord, MIN( iRepSize, pCheck->iWordLen+1 ) );
|
||
|
szRep[ pCheck->iWordLen-2 ] = '\'';
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Randomly append "th" or "st" to any word ending in "ke"
|
||
|
// i.e. "take" -> "taketh", "broke" -> "brokest"
|
||
|
if ( slc == 'k' && lc == 'e' && RandomInt(1,3) == 1 )
|
||
|
{
|
||
|
Q_strncpy( szRep, pCheck->szWord, MIN( iRepSize, pCheck->iWordLen+1 ) );
|
||
|
if ( RandomInt(1,2) == 1 )
|
||
|
{
|
||
|
Q_strncat( szRep, "th", iRepSize );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncat( szRep, "st", iRepSize );
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( pCheck->iWordLen >= 3 )
|
||
|
{
|
||
|
char slc = pCheck->szWord[ pCheck->iWordLen-2 ];
|
||
|
|
||
|
// Randomly append "eth" to words with appropriate last letters.
|
||
|
if ( RandomInt( 1, 5 ) == 1 &&
|
||
|
(lc == 't' || lc == 'p' || lc == 'k' || lc == 'g' || lc == 'b' || lc == 'w') )
|
||
|
{
|
||
|
Q_strncpy( szRep, pCheck->szWord, MIN( iRepSize, pCheck->iWordLen+1 ) );
|
||
|
Q_strncat( szRep, "eth", iRepSize );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Randomly append "est" to any word ending in "ss"
|
||
|
// i.e. "pass" -> "passest", "class" -> "classest"
|
||
|
if ( lc == 's' && slc == 's' && RandomInt(1,5) == 1 )
|
||
|
{
|
||
|
Q_strncpy( szRep, pCheck->szWord, MIN( iRepSize, pCheck->iWordLen+1 ) );
|
||
|
Q_strncat( szRep, "est", iRepSize );
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( pCheck->iWordLen > 4 )
|
||
|
{
|
||
|
// Randomly prepend "a-" to words ending in "ing", and randomly replace the trailing g with an apostrophe
|
||
|
// i.e. "coming" -> "a-comin'", "dancing" -> "a-dancing"
|
||
|
char slc = pCheck->szWord[ pCheck->iWordLen-2 ];
|
||
|
char lllc = pCheck->szWord[ pCheck->iWordLen-3 ];
|
||
|
if ( lllc == 'i' && slc == 'n' && lc == 'g' )
|
||
|
{
|
||
|
char sc = pCheck->szWord[2];
|
||
|
if ( sc != '-' )
|
||
|
{
|
||
|
Q_strncpy( szRep, "a-", iRepSize );
|
||
|
|
||
|
if ( RandomInt(1,2) == 1 )
|
||
|
{
|
||
|
Q_strncat( szRep, pCheck->szWord, iRepSize, pCheck->iWordLen );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncat( szRep, pCheck->szWord, iRepSize, pCheck->iWordLen-1 );
|
||
|
Q_strncat( szRep, "'", iRepSize );
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CTFAutoRP::PerformReplacement( const char *pszReplacement, replacementcheck_t *pRepCheck, char *szStoredWord, int iStoredWordSize, char *pszOutText, int iOutLen )
|
||
|
{
|
||
|
if ( pszReplacement && pszReplacement[0] )
|
||
|
{
|
||
|
// Check to see if the previous word should be modified
|
||
|
char fc = tolower( *pszReplacement );
|
||
|
if ( !_strnicmp( pRepCheck->szPrevWord, "an", MAX(pRepCheck->iPrevLen,2) ) )
|
||
|
{
|
||
|
if ( fc != 'a' && fc != 'e' && fc != 'i' && fc != 'o' && fc != 'u' )
|
||
|
{
|
||
|
// Remove the trailing n
|
||
|
int iLen = (int)strlen( szStoredWord );
|
||
|
szStoredWord[iLen-1] = '\0'; // Move back 3. 1 for null, 1 for space, 1 for n.
|
||
|
}
|
||
|
}
|
||
|
else if ( *pRepCheck->szPrevWord == 'a' && pRepCheck->iPrevLen == 1 )
|
||
|
{
|
||
|
if ( fc == 'a' || fc == 'e' || fc == 'i' || fc == 'o' || fc == 'u' )
|
||
|
{
|
||
|
// Add a trailing n
|
||
|
Q_strncat( szStoredWord, "n", iStoredWordSize );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Only append the previous word if we didn't use it in our replacement
|
||
|
if ( !pRepCheck->bUsedPrevWord )
|
||
|
{
|
||
|
// Append the previous word
|
||
|
Q_strncat( pszOutText, szStoredWord, iOutLen );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFAutoRP::ModifySpeech( const char *pszInText, char *pszOutText, int iOutLen, bool bGeneratePreAndPost, bool bInPrePost )
|
||
|
{
|
||
|
if ( bGeneratePreAndPost )
|
||
|
{
|
||
|
// See if we generate a pre. If we do, modify it as well so we can perform replacements on it.
|
||
|
const char *pszPre = GetRandomPre();
|
||
|
if ( pszPre && pszPre[0] )
|
||
|
{
|
||
|
ModifySpeech( pszPre, pszOutText, iOutLen, false, true );
|
||
|
Q_strncat( pszOutText, " ", iOutLen );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Iterate through all the words and test them vs our replacement list
|
||
|
const char *pszPrevWord = pszInText;
|
||
|
const char *pszCurWord = pszInText;
|
||
|
const char *pszCh = pszInText;
|
||
|
char szStoredWord[128];
|
||
|
szStoredWord[0] = '\0';
|
||
|
char szCurrentWord[128];
|
||
|
szCurrentWord[0] = '\0';
|
||
|
|
||
|
replacementcheck_t repCheck;
|
||
|
|
||
|
while ( 1 )
|
||
|
{
|
||
|
if ( (*pszCh >= 'A' && *pszCh <= 'Z') || (*pszCh >= 'a' && *pszCh <= 'z') || *pszCh == '&' )
|
||
|
{
|
||
|
pszCh++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Hit the end of a word/string.
|
||
|
int iCurLen = (int)(pszCh - pszCurWord);
|
||
|
int iPrevLen = MAX( 0, (int)(pszCurWord - pszPrevWord) - 1 ); // -1 for the space
|
||
|
|
||
|
bool bModifyWord = true;
|
||
|
bool bSkipOneLetter = false;
|
||
|
// Pre/Post pend blocks only modify words that start with an '&'
|
||
|
if ( bInPrePost )
|
||
|
{
|
||
|
bModifyWord = ( pszCurWord[0] == '&' );
|
||
|
bSkipOneLetter = bModifyWord;
|
||
|
}
|
||
|
|
||
|
if ( bSkipOneLetter )
|
||
|
{
|
||
|
Q_strncpy( repCheck.szWord, pszCurWord+1, iCurLen );
|
||
|
repCheck.iWordLen = iCurLen-1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncpy( repCheck.szWord, pszCurWord, iCurLen+1 );
|
||
|
repCheck.iWordLen = iCurLen;
|
||
|
}
|
||
|
|
||
|
Q_strncpy( repCheck.szPrevWord, pszPrevWord, iPrevLen+1 );
|
||
|
repCheck.iPrevLen = iPrevLen;
|
||
|
repCheck.bUsedPrevWord = false;
|
||
|
|
||
|
if ( iCurLen > 0 )
|
||
|
{
|
||
|
bool bChanged = bModifyWord ? ReplaceWord( &repCheck, szCurrentWord, sizeof(szCurrentWord), false, bInPrePost ) : false;
|
||
|
|
||
|
// If the character that broke the last two words apart was an apostrophe, see if we can replace the whole word
|
||
|
if ( !bChanged && bModifyWord )
|
||
|
{
|
||
|
if ( szStoredWord[0] )
|
||
|
{
|
||
|
int iLen = Q_strlen(szStoredWord);
|
||
|
if ( szStoredWord[iLen-1] == '\'' )
|
||
|
{
|
||
|
Q_strncpy( repCheck.szWord, szStoredWord, MIN( sizeof(repCheck.szWord),iLen+1 ) );
|
||
|
Q_strncat( repCheck.szWord, pszCurWord, sizeof(repCheck.szWord), iCurLen );
|
||
|
repCheck.iWordLen = iLen + iCurLen;
|
||
|
repCheck.szPrevWord[0] = '\0';
|
||
|
repCheck.iPrevLen = 0;
|
||
|
|
||
|
bChanged = ReplaceWord( &repCheck, szCurrentWord, sizeof(szCurrentWord), false, bInPrePost );
|
||
|
if ( bChanged )
|
||
|
{
|
||
|
repCheck.bUsedPrevWord = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( szStoredWord[0] != '\0' )
|
||
|
{
|
||
|
if ( PerformReplacement( szCurrentWord, &repCheck, szStoredWord, sizeof(szStoredWord), pszOutText, iOutLen ) )
|
||
|
{
|
||
|
// Append a space, but not if the last character is an apostrophe
|
||
|
int iLen = Q_strlen(szStoredWord);
|
||
|
if ( szStoredWord[iLen-1] != '\'' )
|
||
|
{
|
||
|
Q_strncat( pszOutText, " ", iOutLen );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( bChanged )
|
||
|
{
|
||
|
Q_strncpy( szStoredWord, szCurrentWord, sizeof(szStoredWord) );
|
||
|
|
||
|
// Match case of the first letter in the word we're replacing
|
||
|
if ( pszCurWord[0] >= 'A' && pszCurWord[0] <= 'Z' )
|
||
|
{
|
||
|
szStoredWord[0] = toupper( szStoredWord[0] );
|
||
|
}
|
||
|
else if ( pszCurWord[0] >= 'a' && pszCurWord[0] <= 'a' )
|
||
|
{
|
||
|
szStoredWord[0] = tolower( szStoredWord[0] );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncpy( szStoredWord, pszCurWord, MIN( (int)sizeof(szStoredWord), (int)(pszCh - pszCurWord)+1 ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Finished?
|
||
|
if ( *pszCh == '\0' )
|
||
|
{
|
||
|
repCheck.bUsedPrevWord = false;
|
||
|
if ( szStoredWord[0] != '\0' )
|
||
|
{
|
||
|
PerformReplacement( NULL, &repCheck, szStoredWord, sizeof(szStoredWord), pszOutText, iOutLen );
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// If it wasn't a space that ended this word, try checking it for a symbol
|
||
|
if ( *pszCh != ' ' )
|
||
|
{
|
||
|
Q_strncpy( repCheck.szWord, pszCh, 2 );
|
||
|
repCheck.iWordLen = 1;
|
||
|
repCheck.iPrevLen = 0;
|
||
|
repCheck.bUsedPrevWord = false;
|
||
|
|
||
|
char szSymbolRep[128];
|
||
|
szSymbolRep[0] = '\0';
|
||
|
if ( ReplaceWord( &repCheck, szSymbolRep, sizeof(szSymbolRep), true, true ) )
|
||
|
{
|
||
|
Q_strncat( szStoredWord, szSymbolRep, (int)sizeof(szStoredWord) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncat( szStoredWord, pszCh, (int)sizeof(szStoredWord), 1 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Move on
|
||
|
pszCh++;
|
||
|
pszPrevWord = pszCurWord;
|
||
|
pszCurWord = pszCh;
|
||
|
}
|
||
|
|
||
|
if ( bGeneratePreAndPost )
|
||
|
{
|
||
|
int iLen = (int)strlen( pszOutText );
|
||
|
char pszLC = pszOutText[iLen-1];
|
||
|
if ( pszLC != '?' && pszLC != '!' )
|
||
|
{
|
||
|
// See if we generate a post. If we do, modify it as well so we can perform replacements on it.
|
||
|
const char *pszPost = GetRandomPost();
|
||
|
if ( pszPost && pszPost[0] )
|
||
|
{
|
||
|
if ( pszLC != '.' )
|
||
|
{
|
||
|
Q_strncat( pszOutText, ". ", iOutLen );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncat( pszOutText, " ", iOutLen );
|
||
|
}
|
||
|
|
||
|
ModifySpeech( pszPost, pszOutText, iOutLen, false, true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|