mirror of
https://github.com/mapbase-source/source-sdk-2013.git
synced 2025-03-03 17:25:27 +03:00
Merge pull request #317 from Blixibon/mapbase/feature/choreosentences
Choreography-driven sentence system
This commit is contained in:
commit
fa6d7e49c8
@ -18,6 +18,7 @@
|
||||
#include "sceneentity.h"
|
||||
#include "ai_speechqueue.h"
|
||||
#ifdef MAPBASE
|
||||
#include "mapbase/choreosentence.h"
|
||||
#include "ai_squad.h"
|
||||
#endif
|
||||
|
||||
@ -846,6 +847,9 @@ bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response *res
|
||||
case ResponseRules::RESPONSE_NONE:
|
||||
break;
|
||||
|
||||
#ifdef MAPBASE
|
||||
case ResponseRules::RESPONSE_CHOREOSENTENCE:
|
||||
#endif
|
||||
case ResponseRules::RESPONSE_SPEAK:
|
||||
{
|
||||
if ( !result->ShouldntUseScene() )
|
||||
@ -1194,6 +1198,15 @@ float CAI_Expresser::GetResponseDuration( AI_Response *result )
|
||||
case ResponseRules::RESPONSE_NONE:
|
||||
case ResponseRules::RESPONSE_ENTITYIO:
|
||||
return 0.0f;
|
||||
#ifdef MAPBASE
|
||||
case ResponseRules::RESPONSE_CHOREOSENTENCE:
|
||||
{
|
||||
const ChoreoSentence_t *pSentence = LookupChoreoSentence( GetOuter(), response );
|
||||
if (pSentence)
|
||||
return GetChoreoSentenceDuration( GetOuter(), *pSentence );
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
|
@ -2093,8 +2093,16 @@ float CBaseFlex::PlayScene( const char *pszScene, float flDelay, AI_Response *re
|
||||
#ifdef MAPBASE
|
||||
float CBaseFlex::PlayAutoGeneratedSoundScene( const char *soundname, float flDelay, AI_Response *response, IRecipientFilter *filter )
|
||||
{
|
||||
if (response && response->GetType() == ResponseRules::RESPONSE_CHOREOSENTENCE)
|
||||
return InstancedChoreoSentenceScene( this, soundname, NULL, flDelay, false, response, false, filter );
|
||||
|
||||
return InstancedAutoGeneratedSoundScene( this, soundname, NULL, flDelay, false, response, false, filter );
|
||||
}
|
||||
|
||||
float CBaseFlex::PlayChoreoSentenceScene( const char *pszSentence, float flDelay, AI_Response *response, IRecipientFilter *filter )
|
||||
{
|
||||
return InstancedChoreoSentenceScene( this, pszSentence, NULL, flDelay, false, response, false, filter );
|
||||
}
|
||||
#else
|
||||
float CBaseFlex::PlayAutoGeneratedSoundScene( const char *soundname )
|
||||
{
|
||||
|
@ -129,6 +129,7 @@ public:
|
||||
|
||||
virtual float PlayScene( const char *pszScene, float flDelay = 0.0f, AI_Response *response = NULL, IRecipientFilter *filter = NULL );
|
||||
#ifdef MAPBASE
|
||||
virtual float PlayChoreoSentenceScene( const char *pszSentenceName, float flDelay = 0.0f, AI_Response *response = NULL, IRecipientFilter *filter = NULL );
|
||||
virtual float PlayAutoGeneratedSoundScene( const char *soundname, float flDelay = 0.0f, AI_Response *response = NULL, IRecipientFilter *filter = NULL );
|
||||
#else
|
||||
virtual float PlayAutoGeneratedSoundScene( const char *soundname );
|
||||
|
@ -297,6 +297,19 @@ void CSpeaker::DispatchResponse( const char *conceptName )
|
||||
CAI_Expresser::RunScriptResponse( pTarget, response, &set, true );
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef MAPBASE
|
||||
case ResponseRules::RESPONSE_CHOREOSENTENCE:
|
||||
{
|
||||
CBaseFlex *pFlex = NULL;
|
||||
if (pTarget != this)
|
||||
{
|
||||
// Attempt to get flex on the target
|
||||
pFlex = dynamic_cast<CBaseFlex*>(pTarget);
|
||||
}
|
||||
InstancedAutoGeneratedSoundScene(pFlex, response);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
|
985
sp/src/game/server/mapbase/choreosentence.cpp
Normal file
985
sp/src/game/server/mapbase/choreosentence.cpp
Normal file
@ -0,0 +1,985 @@
|
||||
//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============//
|
||||
//
|
||||
// Purpose: VCD-based sentences.
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#include "cbase.h"
|
||||
#include "utlhashtable.h"
|
||||
#include "engine/IEngineSound.h"
|
||||
#include "soundchars.h"
|
||||
#include "choreosentence.h"
|
||||
#include "mapbase/matchers.h"
|
||||
#include "ai_speech_new.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#define IsVirtualWord(pszWord) (pszWord[0] == 'V' && pszWord[1] == '_')
|
||||
#define RemoveVirtualPrefix(pszWord) IsVirtualWord(pszWord) ? pszWord + 2 : pszWord
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
class CChoreoSentenceSystem : public CAutoGameSystem
|
||||
{
|
||||
public:
|
||||
|
||||
struct VirtualWord_t;
|
||||
struct VirtualWordDefinition_t;
|
||||
struct VirtualWordDefinitionList_t;
|
||||
struct SentenceContextPrerequisite_t;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
CChoreoSentenceSystem() : m_Strings( 256, 0, &StringLessThan ) { }
|
||||
~CChoreoSentenceSystem() { PurgeSentences(); }
|
||||
|
||||
bool Init()
|
||||
{
|
||||
// Load the default choreo sentences files
|
||||
LoadChoreoSentenceManifest( "scripts/choreosentences_manifest.txt" );
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
PurgeSentences();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void LoadChoreoSentenceManifest( const char *pszFile )
|
||||
{
|
||||
KeyValues *pManifest = new KeyValues( "ChoreoSentencesManifest" );
|
||||
if (pManifest->LoadFromFile( filesystem, pszFile ))
|
||||
{
|
||||
FOR_EACH_SUBKEY( pManifest, pSubKey )
|
||||
{
|
||||
LoadChoreoSentenceFile( pSubKey->GetString() );
|
||||
}
|
||||
}
|
||||
pManifest->deleteThis();
|
||||
|
||||
//m_Strings.Compact( true );
|
||||
}
|
||||
|
||||
void LoadChoreoSentenceFile( const char *pszFile )
|
||||
{
|
||||
KeyValues *pManifest = new KeyValues( "ChoreoSentences" );
|
||||
if (pManifest->LoadFromFile( filesystem, pszFile ))
|
||||
{
|
||||
//----------------------------------------------------------------------------
|
||||
// Context Prerequisites
|
||||
//----------------------------------------------------------------------------
|
||||
KeyValues *pSentenceContextPrerequisites = pManifest->FindKey( "SentenceContextPrerequisites" );
|
||||
FOR_EACH_SUBKEY( pSentenceContextPrerequisites, pSubKey )
|
||||
{
|
||||
KeyValues *pCondition = pSubKey->GetFirstSubKey();
|
||||
if (pCondition)
|
||||
{
|
||||
//
|
||||
// Only add if there is a subkey for condition
|
||||
//
|
||||
int i = m_ContextPrerequisites.AddToTail();
|
||||
m_ContextPrerequisites[i].nConditionType = DeduceCondition( pCondition->GetName(), V_strlen( pCondition->GetName() ) );
|
||||
|
||||
m_ContextPrerequisites[i].pszContextName = AllocateString( pSubKey->GetName() );
|
||||
m_ContextPrerequisites[i].pszCondition = AllocateString( pCondition->GetString() );
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Virtual Words
|
||||
//----------------------------------------------------------------------------
|
||||
KeyValues *pVirtualWords = pManifest->FindKey( "VirtualWords" );
|
||||
FOR_EACH_SUBKEY( pVirtualWords, pSubKey )
|
||||
{
|
||||
//
|
||||
// Determine if this has nested virtual words or not
|
||||
//
|
||||
KeyValues *pMatches = pSubKey->FindKey( "matches" );
|
||||
if (pMatches)
|
||||
{
|
||||
//
|
||||
// It has nested virtual words
|
||||
//
|
||||
int i = m_VirtualWordLists.Insert( RemoveVirtualPrefix( pSubKey->GetName() ) );
|
||||
m_VirtualWordLists[i].pszCriterion = AllocateString( pSubKey->GetString( "criterion" ) );
|
||||
|
||||
bool bFirst = true;
|
||||
FOR_EACH_SUBKEY( pMatches, pWordList )
|
||||
{
|
||||
if (bFirst)
|
||||
{
|
||||
// First one is always default
|
||||
if (LoadVirtualWordDefinitionFromKV( pWordList, m_VirtualWordLists[i].m_DefaultWordDefinition, pSubKey->GetName() ))
|
||||
bFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
int i2 = m_VirtualWordLists[i].m_WordDefinitions.Insert( pWordList->GetName() );
|
||||
if (!LoadVirtualWordDefinitionFromKV( pWordList, m_VirtualWordLists[i].m_WordDefinitions[i2], pSubKey->GetName() ))
|
||||
m_VirtualWordLists[i].m_WordDefinitions.RemoveAt( i2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// It is a virtual word
|
||||
//
|
||||
int i = m_VirtualWords.Insert( RemoveVirtualPrefix( pSubKey->GetName() ) );
|
||||
if (!LoadVirtualWordDefinitionFromKV( pSubKey, m_VirtualWords[i], pSubKey->GetName() ))
|
||||
m_VirtualWords.RemoveAt( i );
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Sentences
|
||||
//----------------------------------------------------------------------------
|
||||
KeyValues *pSentences = pManifest->FindKey( "Sentences" );
|
||||
FOR_EACH_SUBKEY( pSentences, pSetKey )
|
||||
{
|
||||
FOR_EACH_SUBKEY( pSetKey, pSubKey )
|
||||
{
|
||||
if (pSubKey->GetName() && *pSubKey->GetString())
|
||||
{
|
||||
int i = m_Sentences.Insert( pSubKey->GetName() );
|
||||
|
||||
char szPathName[MAX_PATH];
|
||||
V_strncpy( szPathName, pSetKey->GetName(), sizeof( szPathName ) );
|
||||
V_FixSlashes( szPathName );
|
||||
m_Sentences[i].pszPrefix = AllocateString( szPathName );
|
||||
|
||||
//m_Sentences[i].pszPrefix = AllocateString( pSetKey->GetName() );
|
||||
|
||||
ParseChoreoSentence( NULL, pSubKey->GetString(), m_Sentences[i] );
|
||||
m_Sentences[i].pszName = m_Sentences.GetElementName( i );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pManifest->deleteThis();
|
||||
}
|
||||
|
||||
bool LoadVirtualWordDefinitionFromKV( KeyValues *pWordList, VirtualWordDefinition_t &wordDefinition, const char *pszWordName )
|
||||
{
|
||||
//
|
||||
// Get the condition for this virtual word
|
||||
//
|
||||
KeyValues *pCondition = pWordList->FindKey( "condition" );
|
||||
if (pCondition)
|
||||
{
|
||||
int nStrLen = V_strlen( pCondition->GetString() );
|
||||
const char *pColon = V_strnchr( pCondition->GetString(), ':', nStrLen );
|
||||
if (pColon)
|
||||
{
|
||||
wordDefinition.pszCondition = AllocateString( pColon + 1 );
|
||||
wordDefinition.nConditionType = DeduceCondition( pCondition->GetString(), pColon - pCondition->GetString() );
|
||||
}
|
||||
else
|
||||
{
|
||||
wordDefinition.nConditionType = DeduceCondition( pCondition->GetString(), nStrLen );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wordDefinition.nConditionType = 0;
|
||||
wordDefinition.pszCondition = "";
|
||||
}
|
||||
|
||||
//
|
||||
// Get the words themselves
|
||||
//
|
||||
KeyValues *pWords = pWordList->FindKey( "words" );
|
||||
if (pWords)
|
||||
{
|
||||
FOR_EACH_SUBKEY( pWords, pWord )
|
||||
{
|
||||
int i = wordDefinition.m_Words.AddToTail();
|
||||
V_strncpy( wordDefinition.m_Words[i].szWord, pWord->GetString(), sizeof( wordDefinition.m_Words[i].szWord ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pWords = pWordList->FindKey( "words_from" );
|
||||
if (pWords)
|
||||
{
|
||||
const char *pszWordsTarget = RemoveVirtualPrefix( pWords->GetString() );
|
||||
|
||||
//
|
||||
// Get from another virtual word
|
||||
//
|
||||
FOR_EACH_DICT_FAST( m_VirtualWords, i )
|
||||
{
|
||||
if (FStrEq( m_VirtualWords.GetElementName( i ), pszWordsTarget ))
|
||||
{
|
||||
wordDefinition.m_Words.AddVectorToTail( m_VirtualWords[i].m_Words );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wordDefinition.m_Words.Count() <= 0)
|
||||
{
|
||||
Warning( "WARNING: No words found on virtual word %s\n", pszWordName );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void PurgeSentences()
|
||||
{
|
||||
m_ContextPrerequisites.RemoveAll();
|
||||
m_VirtualWordLists.RemoveAll();
|
||||
m_VirtualWords.RemoveAll();
|
||||
m_Sentences.RemoveAll();
|
||||
|
||||
for (unsigned int i = 0; i < m_Strings.Count(); i++)
|
||||
{
|
||||
delete m_Strings[i];
|
||||
}
|
||||
m_Strings.Purge();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void PrintEverything()
|
||||
{
|
||||
Msg( "CONTEXT PREREQUISITES\n\n" );
|
||||
|
||||
FOR_EACH_VEC( m_ContextPrerequisites, i )
|
||||
{
|
||||
Msg( "\t\"%s\"\n\t\tCondition: %i (\"%s\")\n\n", m_ContextPrerequisites[i].pszContextName, m_ContextPrerequisites[i].nConditionType, m_ContextPrerequisites[i].pszCondition );
|
||||
}
|
||||
|
||||
Msg( "VIRTUAL WORDS\n\n" );
|
||||
|
||||
FOR_EACH_DICT_FAST( m_VirtualWords, i )
|
||||
{
|
||||
char szWords[256] = { 0 };
|
||||
FOR_EACH_VEC( m_VirtualWords[i].m_Words, nWord )
|
||||
{
|
||||
V_strncat( szWords, "\t\t\t\"", sizeof( szWords ) );
|
||||
V_strncat( szWords, m_VirtualWords[i].m_Words[nWord].szWord, sizeof( szWords ) );
|
||||
V_strncat( szWords, "\"\n", sizeof( szWords ) );
|
||||
}
|
||||
|
||||
Msg( "\t\"%s\"\n\t\tCondition: %i (\"%s\")\n\t\tWords:\n%s\n\n", m_VirtualWords.GetElementName( i ), m_VirtualWords[i].nConditionType, m_VirtualWords[i].pszCondition, szWords );
|
||||
}
|
||||
|
||||
Msg( "SENTENCES\n\n" );
|
||||
|
||||
FOR_EACH_DICT_FAST( m_Sentences, i )
|
||||
{
|
||||
char szWords[128] = { 0 };
|
||||
FOR_EACH_VEC( m_Sentences[i].m_Words, nWord )
|
||||
{
|
||||
if (m_Sentences[i].m_Words[nWord].nPitch != 100)
|
||||
{
|
||||
V_snprintf( szWords, sizeof( szWords ), "%s(p%i) ", szWords, m_Sentences[i].m_Words[nWord].nPitch );
|
||||
}
|
||||
|
||||
if (m_Sentences[i].m_Words[nWord].nVol != 100)
|
||||
{
|
||||
V_snprintf( szWords, sizeof( szWords ), "%s(v%i) ", szWords, m_Sentences[i].m_Words[nWord].nVol );
|
||||
}
|
||||
|
||||
V_strncat( szWords, "\"", sizeof( szWords ) );
|
||||
V_strncat( szWords, m_Sentences[i].m_Words[nWord].pszWord, sizeof( szWords ) );
|
||||
V_strncat( szWords, "\" ", sizeof( szWords ) );
|
||||
}
|
||||
|
||||
Msg( "\t\"%s\"\n\t\tPrefix: \"%s\"\n\t\tCaption: \"%s\"\n\t\tWords: %s\n\n", m_Sentences.GetElementName( i ), m_Sentences[i].pszPrefix, m_Sentences[i].pszCaption ? m_Sentences[i].pszCaption : "N/A", szWords);
|
||||
}
|
||||
}
|
||||
|
||||
void PrintStrings()
|
||||
{
|
||||
CUtlVector<const char*> strings( 0, m_Strings.Count() );
|
||||
for ( UtlHashHandle_t i = m_Strings.FirstInorder(); i != m_Strings.InvalidIndex(); i = m_Strings.NextInorder(i) )
|
||||
{
|
||||
strings.AddToTail( m_Strings[i] );
|
||||
}
|
||||
|
||||
struct _Local {
|
||||
static int __cdecl F(const char * const *a, const char * const *b) { return strcmp(*a, *b); }
|
||||
};
|
||||
strings.Sort( _Local::F );
|
||||
|
||||
for ( int i = 0; i < strings.Count(); ++i )
|
||||
{
|
||||
Msg( " %d (0x%p) : %s\n", i, strings[i], strings[i] );
|
||||
}
|
||||
Msg( "\n" );
|
||||
Msg( "Size: %d items\n", strings.Count() );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
const ChoreoSentence_t *LookupChoreoSentence( CBaseEntity *pSpeaker, const char *pszSentenceName )
|
||||
{
|
||||
FOR_EACH_DICT_FAST( m_Sentences, i )
|
||||
{
|
||||
if (FStrEq( m_Sentences.GetElementName( i ), pszSentenceName ))
|
||||
{
|
||||
return &m_Sentences[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void PrecacheVirtualWord( const char *pszWord, const char *pszPrefix, const char *pszSuffix )
|
||||
{
|
||||
pszWord = RemoveVirtualPrefix( pszWord );
|
||||
|
||||
FOR_EACH_DICT_FAST( m_VirtualWords, i )
|
||||
{
|
||||
if (FStrEq( m_VirtualWords.GetElementName( i ), pszWord ))
|
||||
{
|
||||
// Precache all words
|
||||
FOR_EACH_VEC( m_VirtualWords[i].m_Words, i2 )
|
||||
{
|
||||
CBaseEntity::PrecacheScriptSound( UTIL_VarArgs( "%s%s%s", pszPrefix, m_VirtualWords[i].m_Words[i2].szWord, pszSuffix ) );
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FOR_EACH_DICT_FAST( m_VirtualWordLists, i )
|
||||
{
|
||||
if (FStrEq( m_VirtualWordLists.GetElementName( i ), pszWord ))
|
||||
{
|
||||
// Precache all words in default definition
|
||||
FOR_EACH_VEC( m_VirtualWordLists[i].m_DefaultWordDefinition.m_Words, i2 )
|
||||
{
|
||||
CBaseEntity::PrecacheScriptSound( UTIL_VarArgs( "%s%s%s", pszPrefix, m_VirtualWordLists[i].m_DefaultWordDefinition.m_Words[i2].szWord, pszSuffix));
|
||||
}
|
||||
|
||||
// Precache all words in nested definitions
|
||||
FOR_EACH_DICT( m_VirtualWordLists[i].m_WordDefinitions, i2 )
|
||||
{
|
||||
FOR_EACH_VEC( m_VirtualWordLists[i].m_WordDefinitions[i2].m_Words, i3 )
|
||||
{
|
||||
CBaseEntity::PrecacheScriptSound( UTIL_VarArgs( "%s%s%s", pszPrefix, m_VirtualWordLists[i].m_WordDefinitions[i2].m_Words[i3].szWord, pszSuffix ) );
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline const VirtualWord_t &ResolveVirtualWordDefinition( CBaseEntity *pSpeaker, const VirtualWordDefinition_t &wordDefinition )
|
||||
{
|
||||
// Resolve this condition
|
||||
int nIndex = ResolveVirtualCondition( pSpeaker, wordDefinition.nConditionType, wordDefinition.pszCondition, wordDefinition.m_Words.Count() );
|
||||
Assert( nIndex >= 0 && nIndex < wordDefinition.m_Words.Count() );
|
||||
|
||||
return wordDefinition.m_Words[nIndex];
|
||||
}
|
||||
|
||||
const char *ResolveVirtualWord( CBaseEntity *pSpeaker, const char *pszWord )
|
||||
{
|
||||
pszWord = RemoveVirtualPrefix( pszWord );
|
||||
|
||||
FOR_EACH_DICT_FAST( m_VirtualWords, i )
|
||||
{
|
||||
if (FStrEq( m_VirtualWords.GetElementName( i ), pszWord ))
|
||||
{
|
||||
return ResolveVirtualWordDefinition( pSpeaker, m_VirtualWords[i] ).szWord;
|
||||
}
|
||||
}
|
||||
|
||||
FOR_EACH_DICT_FAST( m_VirtualWordLists, i )
|
||||
{
|
||||
if (FStrEq( m_VirtualWordLists.GetElementName( i ), pszWord ))
|
||||
{
|
||||
AI_CriteriaSet set;
|
||||
if (pSpeaker)
|
||||
pSpeaker->ModifyOrAppendCriteria( set );
|
||||
|
||||
int nCriterion = set.FindCriterionIndex( m_VirtualWordLists[i].pszCriterion );
|
||||
if (set.IsValidIndex( nCriterion ))
|
||||
{
|
||||
const char *pszValue = set.GetValue( nCriterion );
|
||||
|
||||
// Find the set of virtual words that matches the criterion
|
||||
FOR_EACH_DICT( m_VirtualWordLists[i].m_WordDefinitions, i2 )
|
||||
{
|
||||
if (Matcher_Match( m_VirtualWordLists[i].m_WordDefinitions.GetElementName(i2), pszValue ))
|
||||
{
|
||||
return ResolveVirtualWordDefinition( pSpeaker, m_VirtualWordLists[i].m_WordDefinitions[i2] ).szWord;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the default
|
||||
return ResolveVirtualWordDefinition( pSpeaker, m_VirtualWordLists[i].m_DefaultWordDefinition ).szWord;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CGWarning( 0, CON_GROUP_CHOREO, "Choreo sentence can't find virtual word \"%s\"\n", pszWord );
|
||||
return "";
|
||||
}
|
||||
|
||||
int ResolveVirtualCondition( CBaseEntity *pSpeaker, int nConditionType, const char *pszCondition, int nWordCount )
|
||||
{
|
||||
switch (nConditionType)
|
||||
{
|
||||
default:
|
||||
case ConditionType_None:
|
||||
// Randomize between each word
|
||||
return RandomInt( 0, nWordCount - 1 );
|
||||
break;
|
||||
|
||||
case ConditionType_Context:
|
||||
// Return context as integer
|
||||
if (pSpeaker)
|
||||
{
|
||||
if (pSpeaker->FindContextByName( pszCondition ) == -1)
|
||||
{
|
||||
// Check if this is a prerequisite, and if it is, then apply it
|
||||
FOR_EACH_VEC( m_ContextPrerequisites, i )
|
||||
{
|
||||
if (FStrEq( m_ContextPrerequisites[i].pszContextName, pszCondition ))
|
||||
{
|
||||
pSpeaker->AddContext( pszCondition, UTIL_VarArgs("%i", ResolveVirtualCondition(pSpeaker, m_ContextPrerequisites[i].nConditionType, m_ContextPrerequisites[i].pszCondition, nWordCount)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int nContext = clamp( atoi( pSpeaker->GetContextValue( pszCondition ) ), 0, nWordCount - 1 );
|
||||
return nContext;
|
||||
}
|
||||
break;
|
||||
|
||||
case ConditionType_VScript:
|
||||
{
|
||||
// Return VScript code result
|
||||
g_pScriptVM->SetValue( "_choreo_val", "" );
|
||||
|
||||
HSCRIPT hCreateChainScript = g_pScriptVM->CompileScript( UTIL_VarArgs( "_choreo_val = (%s)", pszCondition ) );
|
||||
g_pScriptVM->Run( hCreateChainScript, pSpeaker ? pSpeaker->GetOrCreatePrivateScriptScope() : NULL );
|
||||
|
||||
ScriptVariant_t scriptVar;
|
||||
g_pScriptVM->GetValue( "_choreo_val", &scriptVar );
|
||||
|
||||
g_pScriptVM->ClearValue( "_choreo_val" );
|
||||
|
||||
if (scriptVar.m_int < 0 || scriptVar.m_int >= nWordCount)
|
||||
{
|
||||
Warning("Choreo sentence script var %i from '%s' out of range\n", scriptVar.m_int, pszCondition );
|
||||
scriptVar.m_int = 0;
|
||||
}
|
||||
|
||||
return scriptVar.m_int;
|
||||
}
|
||||
break;
|
||||
|
||||
case ConditionType_DistTo:
|
||||
case ConditionType_DirTo:
|
||||
case ConditionType_GridX:
|
||||
case ConditionType_GridY:
|
||||
{
|
||||
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, pszCondition, pSpeaker );
|
||||
if (pSpeaker && pTarget)
|
||||
{
|
||||
if (nConditionType == ConditionType_DistTo)
|
||||
{
|
||||
// Convert to meters
|
||||
return (pSpeaker->GetAbsOrigin() - pTarget->GetAbsOrigin()).Length() / 52.49344;
|
||||
}
|
||||
else if (nConditionType == ConditionType_DirTo)
|
||||
{
|
||||
Vector vecDir = (pSpeaker->GetAbsOrigin() - pTarget->GetAbsOrigin());
|
||||
return vecDir.y;
|
||||
}
|
||||
else if (nConditionType == ConditionType_GridX)
|
||||
{
|
||||
return (int)(pTarget->GetAbsOrigin().x / 10.0f) % 20;
|
||||
}
|
||||
else if (nConditionType == ConditionType_GridY)
|
||||
{
|
||||
return (int)(pTarget->GetAbsOrigin().y / 10.0f) % 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
enum
|
||||
{
|
||||
ConditionType_None, // No condition (random)
|
||||
ConditionType_Context, // Word should match value from specified context
|
||||
ConditionType_VScript, // Word should match value returned from specified VScript code
|
||||
|
||||
// HL2 sentence parameters
|
||||
ConditionType_DistTo, // Word should match distance to specified target
|
||||
ConditionType_DirTo, // Word should match direction to specified target
|
||||
ConditionType_GridX, // Word should match world X axis grid of specified target
|
||||
ConditionType_GridY, // Word should match world Y axis grid of specified target
|
||||
};
|
||||
|
||||
int DeduceCondition( const char *pszCondition, int nNumChars )
|
||||
{
|
||||
if (V_strncmp( pszCondition, "context", nNumChars ) == 0)
|
||||
return ConditionType_Context;
|
||||
else if (V_strncmp( pszCondition, "vscript", nNumChars ) == 0)
|
||||
return ConditionType_VScript;
|
||||
else if (V_strncmp( pszCondition, "dist_to", nNumChars ) == 0)
|
||||
return ConditionType_DistTo;
|
||||
else if (V_strncmp( pszCondition, "dir_to", nNumChars ) == 0)
|
||||
return ConditionType_DirTo;
|
||||
else if (V_strncmp( pszCondition, "gridx", nNumChars ) == 0)
|
||||
return ConditionType_GridX;
|
||||
else if (V_strncmp( pszCondition, "gridy", nNumChars ) == 0)
|
||||
return ConditionType_GridY;
|
||||
|
||||
return ConditionType_None;
|
||||
}
|
||||
|
||||
struct VirtualWord_t
|
||||
{
|
||||
char szWord[MAX_CHOREO_SENTENCE_VIRTUAL_WORD_LEN];
|
||||
};
|
||||
|
||||
struct VirtualWordDefinition_t
|
||||
{
|
||||
VirtualWordDefinition_t() {}
|
||||
VirtualWordDefinition_t( const VirtualWordDefinition_t &src )
|
||||
{
|
||||
pszCondition = src.pszCondition;
|
||||
nConditionType = src.nConditionType;
|
||||
m_Words.RemoveAll();
|
||||
m_Words.AddVectorToTail( src.m_Words );
|
||||
}
|
||||
|
||||
const char *pszCondition;
|
||||
int nConditionType;
|
||||
|
||||
CUtlVector< VirtualWord_t > m_Words;
|
||||
};
|
||||
|
||||
struct VirtualWordDefinitionList_t
|
||||
{
|
||||
VirtualWordDefinitionList_t() {}
|
||||
VirtualWordDefinitionList_t( const VirtualWordDefinitionList_t &src )
|
||||
{
|
||||
pszCriterion = src.pszCriterion;
|
||||
m_DefaultWordDefinition = src.m_DefaultWordDefinition;
|
||||
|
||||
m_WordDefinitions.RemoveAll();
|
||||
m_WordDefinitions.EnsureCapacity( src.m_WordDefinitions.Count() );
|
||||
FOR_EACH_DICT_FAST( src.m_WordDefinitions, i )
|
||||
m_WordDefinitions.Insert( src.m_WordDefinitions.GetElementName( i ), src.m_WordDefinitions[i] );
|
||||
}
|
||||
|
||||
const char *pszCriterion;
|
||||
|
||||
VirtualWordDefinition_t m_DefaultWordDefinition;
|
||||
CUtlDict< VirtualWordDefinition_t, int > m_WordDefinitions;
|
||||
};
|
||||
|
||||
struct SentenceContextPrerequisite_t
|
||||
{
|
||||
SentenceContextPrerequisite_t() {}
|
||||
SentenceContextPrerequisite_t( const SentenceContextPrerequisite_t &src )
|
||||
{
|
||||
pszContextName = src.pszContextName;
|
||||
pszCondition = src.pszCondition;
|
||||
nConditionType = src.nConditionType;
|
||||
}
|
||||
|
||||
const char *pszContextName;
|
||||
const char *pszCondition;
|
||||
int nConditionType;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
const char *FindString( const char *string )
|
||||
{
|
||||
unsigned short i = m_Strings.Find( string );
|
||||
return i == m_Strings.InvalidIndex() ? NULL : m_Strings[i];
|
||||
}
|
||||
|
||||
const char *AllocateString( const char *string )
|
||||
{
|
||||
int i = m_Strings.Find( string );
|
||||
if ( i != m_Strings.InvalidIndex() )
|
||||
{
|
||||
return m_Strings[i];
|
||||
}
|
||||
|
||||
int len = Q_strlen( string );
|
||||
char *out = new char[ len + 1 ];
|
||||
Q_memcpy( out, string, len );
|
||||
out[ len ] = 0;
|
||||
|
||||
return m_Strings[ m_Strings.Insert( out ) ];
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
const CUtlDict< ChoreoSentence_t, int > &GetSentences() { return m_Sentences; }
|
||||
|
||||
private:
|
||||
|
||||
// Context prerequisites to be applied if not already applied
|
||||
CUtlVector< SentenceContextPrerequisite_t > m_ContextPrerequisites;
|
||||
|
||||
// Embedded lists of virtual words based on a criterion
|
||||
CUtlDict< VirtualWordDefinitionList_t, int > m_VirtualWordLists;
|
||||
|
||||
// Lists of virtual words (does not include nested ones)
|
||||
CUtlDict< VirtualWordDefinition_t, int > m_VirtualWords;
|
||||
|
||||
// Sentences themselves
|
||||
CUtlDict< ChoreoSentence_t, int > m_Sentences;
|
||||
|
||||
// Dedicated strings, copied from game string pool
|
||||
CUtlRBTree<const char *> m_Strings;
|
||||
|
||||
};
|
||||
|
||||
static CChoreoSentenceSystem g_ChoreoSentenceSystem;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
CON_COMMAND( choreosentence_reload, "Reloads choreo sentences" )
|
||||
{
|
||||
//int nStringCount = g_ChoreoSentenceSystem.GetStringCount();
|
||||
g_ChoreoSentenceSystem.PurgeSentences();
|
||||
|
||||
//g_ChoreoSentenceSystem.ReserveStrings( nStringCount );
|
||||
g_ChoreoSentenceSystem.LoadChoreoSentenceManifest( "scripts/choreosentences_manifest.txt" );
|
||||
}
|
||||
|
||||
CON_COMMAND( choreosentence_dump, "Prints all choreo sentence stuff" )
|
||||
{
|
||||
g_ChoreoSentenceSystem.PrintEverything();
|
||||
}
|
||||
|
||||
CON_COMMAND( choreosentence_dump_strings, "Prints all strings allocated by choreo sentences" )
|
||||
{
|
||||
g_ChoreoSentenceSystem.PrintStrings();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Purpose :
|
||||
//------------------------------------------------------------------------------
|
||||
static int AutoCompleteChoreoSentences(const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0)
|
||||
{
|
||||
FOR_EACH_DICT_FAST( g_ChoreoSentenceSystem.GetSentences(), i )
|
||||
{
|
||||
CUtlString sym = g_ChoreoSentenceSystem.GetSentences().GetElementName( i );
|
||||
|
||||
if (Q_strnicmp( sym, substring, checklen ) != 0)
|
||||
continue;
|
||||
|
||||
int idx = symbols.Find( sym );
|
||||
if (idx == symbols.InvalidIndex())
|
||||
{
|
||||
symbols.Insert( sym );
|
||||
}
|
||||
|
||||
// Too many
|
||||
if (symbols.Count() >= COMMAND_COMPLETION_MAXITEMS)
|
||||
break;
|
||||
}
|
||||
|
||||
// Now fill in the results
|
||||
for (int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder(i))
|
||||
{
|
||||
const char *name = symbols[i].String();
|
||||
|
||||
char buf[512];
|
||||
Q_snprintf( buf, sizeof( buf ), "%s %s", cmdname, name );
|
||||
Q_strlower( buf );
|
||||
|
||||
CUtlString command;
|
||||
command = buf;
|
||||
commands.AddToTail(command);
|
||||
}
|
||||
|
||||
return symbols.Count();
|
||||
}
|
||||
|
||||
class CChoreoSentenceAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback
|
||||
{
|
||||
public:
|
||||
virtual void CommandCallback( const CCommand &command )
|
||||
{
|
||||
if (command.ArgC() != 2)
|
||||
{
|
||||
Msg( "Format: choreosentence_play <sentence name>\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
||||
if (pPlayer)
|
||||
{
|
||||
const ChoreoSentence_t *pSentence = LookupChoreoSentence( pPlayer, command.Arg( 1 ) );
|
||||
if (pSentence)
|
||||
PrecacheChoreoSentence( *pSentence );
|
||||
|
||||
pPlayer->PlayChoreoSentenceScene( command.Arg( 1 ) );
|
||||
}
|
||||
}
|
||||
|
||||
virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands )
|
||||
{
|
||||
if ( !g_pGameRules )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *cmdname = "choreosentence_play";
|
||||
|
||||
char *substring = (char *)partial;
|
||||
if ( Q_strstr( partial, cmdname ) )
|
||||
{
|
||||
substring = (char *)partial + strlen( cmdname ) + 1;
|
||||
}
|
||||
|
||||
int checklen = Q_strlen( substring );
|
||||
|
||||
extern bool UtlStringLessFunc( const CUtlString & lhs, const CUtlString & rhs );
|
||||
CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc );
|
||||
|
||||
return AutoCompleteChoreoSentences(cmdname, commands, symbols, substring, checklen);
|
||||
}
|
||||
};
|
||||
|
||||
static CChoreoSentenceAutoCompletionFunctor g_ChoreoSentenceAutoComplete;
|
||||
static ConCommand choreosentence_play("choreosentence_play", &g_ChoreoSentenceAutoComplete, "Plays the specified choreo sentence on the player", FCVAR_CHEAT, &g_ChoreoSentenceAutoComplete );
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
const char *ChoreoSentence_t::GetWordString( CBaseEntity *pSpeaker, int i ) const
|
||||
{
|
||||
const char *pszExtension = "";
|
||||
if (V_strrchr( pszPrefix, CORRECT_PATH_SEPARATOR ))
|
||||
{
|
||||
// Use waves if our prefix is a path
|
||||
pszExtension = ".wav";
|
||||
}
|
||||
|
||||
// TODO: Something more elaborate than UTIL_VarArgs?
|
||||
if (m_Words[i].bVirtual)
|
||||
{
|
||||
const char *pszVirtualWord = g_ChoreoSentenceSystem.ResolveVirtualWord( pSpeaker, m_Words[i].pszWord );
|
||||
return UTIL_VarArgs( "%s%s%s", pszPrefix, pszVirtualWord, pszExtension );
|
||||
}
|
||||
|
||||
return UTIL_VarArgs( "%s%s%s", pszPrefix, m_Words[i].pszWord, pszExtension );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void LoadChoreoSentenceFile( const char *pszFile )
|
||||
{
|
||||
g_ChoreoSentenceSystem.LoadChoreoSentenceFile( pszFile );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
const ChoreoSentence_t *LookupChoreoSentence( CBaseEntity *pSpeaker, const char *pszSentenceName )
|
||||
{
|
||||
return g_ChoreoSentenceSystem.LookupChoreoSentence( pSpeaker, pszSentenceName );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void PrecacheChoreoSentence( const ChoreoSentence_t &sentence )
|
||||
{
|
||||
FOR_EACH_VEC( sentence.m_Words, i )
|
||||
{
|
||||
if (sentence.m_Words[i].bVirtual)
|
||||
{
|
||||
// Precache all virtual words
|
||||
const char *pszExtension = "";
|
||||
if (V_strrchr( sentence.pszPrefix, CORRECT_PATH_SEPARATOR ))
|
||||
pszExtension = ".wav";
|
||||
|
||||
g_ChoreoSentenceSystem.PrecacheVirtualWord( sentence.m_Words[i].pszWord, sentence.pszPrefix, pszExtension );
|
||||
}
|
||||
else
|
||||
{
|
||||
CBaseEntity::PrecacheScriptSound( sentence.GetWordString( NULL, i ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool ParseChoreoSentence( CBaseEntity *pSpeaker, const char *pszRawSentence, ChoreoSentence_t &sentence )
|
||||
{
|
||||
if (pszRawSentence == NULL || *pszRawSentence == NULL)
|
||||
return false;
|
||||
|
||||
char szSentence[256];
|
||||
|
||||
// First, try getting the prefix
|
||||
const char *pColon = V_strnchr( pszRawSentence, ':', V_strlen( pszRawSentence ) );
|
||||
if (pColon)
|
||||
{
|
||||
// Sentence is everything after colon
|
||||
Q_strncpy( szSentence, pColon + 1, sizeof( szSentence ) );
|
||||
|
||||
// Copy everything before colon for prefix
|
||||
char szPathName[MAX_PATH];
|
||||
V_strncpy( szPathName, pszRawSentence, pColon - pszRawSentence + 1 );
|
||||
V_FixSlashes( szPathName );
|
||||
sentence.pszPrefix = g_ChoreoSentenceSystem.AllocateString( szPathName );
|
||||
}
|
||||
else
|
||||
{
|
||||
// It's all one sentence
|
||||
Q_strncpy( szSentence, pszRawSentence, sizeof( szSentence ) );
|
||||
}
|
||||
|
||||
// Now get any parameters
|
||||
const char *pSemiColon = V_strnchr( szSentence, ';', sizeof( szSentence ) );
|
||||
if (pSemiColon)
|
||||
{
|
||||
// Caption is whatever's after the semicolon
|
||||
const char *pszCaption = pSemiColon+1;
|
||||
if (pszCaption[0] == ' ')
|
||||
pszCaption++;
|
||||
|
||||
sentence.pszCaption = g_ChoreoSentenceSystem.AllocateString( pszCaption );
|
||||
|
||||
// Replace semicolon with null terminator
|
||||
szSentence[pSemiColon - szSentence] = '\0';
|
||||
}
|
||||
|
||||
// Next, split up the sentence itself
|
||||
bool success = ParseChoreoSentenceContents( pSpeaker, szSentence, sentence );
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool ParseChoreoSentenceContents( CBaseEntity *pSpeaker, char *pszSentence, ChoreoSentence_t &sentence )
|
||||
{
|
||||
int nCurVol = 100, nCurPitch = 100;
|
||||
|
||||
char *pszToken = strtok( pszSentence, " " );
|
||||
for (; pszToken != NULL; pszToken = strtok( NULL, " " ))
|
||||
{
|
||||
if (!pszToken || !*pszToken)
|
||||
continue;
|
||||
|
||||
// Check if this is a command number
|
||||
if (pszToken[0] == '(')
|
||||
{
|
||||
pszToken++;
|
||||
|
||||
// Remove closing parentheses
|
||||
//int end = V_strlen( pszToken )-1;
|
||||
//if (pszToken[end] == ')')
|
||||
// pszToken[end] = '\0';
|
||||
|
||||
int nNum = atoi( pszToken + 1 );
|
||||
if (nNum > 0)
|
||||
{
|
||||
switch (pszToken[0])
|
||||
{
|
||||
// TODO: Recognize e, t, etc.?
|
||||
case 'v': nCurVol = nNum; break;
|
||||
case 'p': nCurPitch = nNum; break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
Msg( "Zero command number in %s\n", pszSentence );
|
||||
}
|
||||
}
|
||||
|
||||
int nWord = sentence.m_Words.AddToTail();
|
||||
|
||||
sentence.m_Words[nWord].nVol = nCurVol;
|
||||
sentence.m_Words[nWord].nPitch = nCurPitch;
|
||||
|
||||
// Check if this is virtual
|
||||
if (IsVirtualWord( pszToken ))
|
||||
sentence.m_Words[nWord].bVirtual = true;
|
||||
|
||||
// Check for periods or commas
|
||||
int end = V_strlen( pszToken )-1;
|
||||
if (pszToken[end] == ',')
|
||||
{
|
||||
int nWord2 = sentence.m_Words.AddToTail();
|
||||
sentence.m_Words[nWord2].pszWord = g_ChoreoSentenceSystem.AllocateString( "_comma" );
|
||||
sentence.m_Words[nWord2].nVol = nCurVol;
|
||||
sentence.m_Words[nWord2].nPitch = nCurPitch;
|
||||
pszToken[end] = '\0';
|
||||
}
|
||||
else if (pszToken[end] == '.')
|
||||
{
|
||||
int nWord2 = sentence.m_Words.AddToTail();
|
||||
sentence.m_Words[nWord2].pszWord = g_ChoreoSentenceSystem.AllocateString( "_period" );
|
||||
sentence.m_Words[nWord2].nVol = nCurVol;
|
||||
sentence.m_Words[nWord2].nPitch = nCurPitch;
|
||||
pszToken[end] = '\0';
|
||||
}
|
||||
|
||||
sentence.m_Words[nWord].pszWord = g_ChoreoSentenceSystem.AllocateString( pszToken );
|
||||
}
|
||||
|
||||
return sentence.m_Words.Count() > 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
float GetChoreoSentenceDuration( CBaseFlex *pSpeaker, const ChoreoSentence_t &sentence )
|
||||
{
|
||||
const char *actormodel = (pSpeaker ? STRING( pSpeaker->GetModelName() ) : NULL);
|
||||
float flLength = 0.0f;
|
||||
|
||||
FOR_EACH_VEC( sentence.m_Words, i )
|
||||
{
|
||||
//float duration = CBaseEntity::GetSoundDuration( sentence.GetWordString(pSpeaker, i), actormodel );
|
||||
|
||||
float duration;
|
||||
const char *pszWord = sentence.GetWordString( pSpeaker, i );
|
||||
|
||||
// For now, call the engine functions manually instead of using CBaseEntity::GetSoundDuration so that we could get around the WaveTrace warning
|
||||
if ( V_stristr( pszWord, ".wav" ) || V_stristr( pszWord, ".mp3" ) )
|
||||
{
|
||||
duration = enginesound->GetSoundDuration( PSkipSoundChars( pszWord ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
extern ISoundEmitterSystemBase *soundemitterbase;
|
||||
duration = enginesound->GetSoundDuration( PSkipSoundChars( soundemitterbase->GetWavFileForSound( pszWord, actormodel ) ) );
|
||||
}
|
||||
|
||||
flLength += duration;
|
||||
}
|
||||
|
||||
return flLength;
|
||||
}
|
67
sp/src/game/server/mapbase/choreosentence.h
Normal file
67
sp/src/game/server/mapbase/choreosentence.h
Normal file
@ -0,0 +1,67 @@
|
||||
//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============//
|
||||
//
|
||||
// Purpose: VCD-based sentences.
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#ifndef CHOREOSENTENCE_H
|
||||
#define CHOREOSENTENCE_H
|
||||
|
||||
#include "cbase.h"
|
||||
|
||||
|
||||
#define MAX_CHOREO_SENTENCE_PREFIX_LEN 64
|
||||
#define MAX_CHOREO_SENTENCE_WORD_LEN 32
|
||||
#define MAX_CHOREO_SENTENCE_CAPTION_LEN 64
|
||||
|
||||
#define MAX_CHOREO_SENTENCE_VIRTUAL_WORD_LEN 32
|
||||
|
||||
struct ChoreoSentenceWord_t
|
||||
{
|
||||
ChoreoSentenceWord_t() {}
|
||||
ChoreoSentenceWord_t( const ChoreoSentenceWord_t &src )
|
||||
{
|
||||
pszWord = src.pszWord;
|
||||
nPitch = src.nPitch; nVol = src.nVol;
|
||||
bVirtual = src.bVirtual;
|
||||
}
|
||||
|
||||
const char *pszWord;
|
||||
int nPitch, nVol = 100;
|
||||
bool bVirtual = false;
|
||||
};
|
||||
|
||||
struct ChoreoSentence_t
|
||||
{
|
||||
ChoreoSentence_t() {}
|
||||
ChoreoSentence_t( const ChoreoSentence_t &src )
|
||||
{
|
||||
pszName = src.pszName;
|
||||
pszPrefix = src.pszPrefix;
|
||||
pszCaption = src.pszCaption;
|
||||
m_Words.RemoveAll();
|
||||
m_Words.AddVectorToTail( src.m_Words );
|
||||
}
|
||||
|
||||
const char *GetWordString( CBaseEntity *pSpeaker, int i ) const;
|
||||
|
||||
CUtlVector< ChoreoSentenceWord_t > m_Words;
|
||||
const char *pszName;
|
||||
const char *pszPrefix;
|
||||
const char *pszCaption;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
extern void LoadChoreoSentenceFile( const char *pszFile );
|
||||
|
||||
extern const ChoreoSentence_t *LookupChoreoSentence( CBaseEntity *pSpeaker, const char *pszSentenceName );
|
||||
|
||||
extern void PrecacheChoreoSentence( const ChoreoSentence_t &sentence );
|
||||
|
||||
bool ParseChoreoSentence( CBaseEntity *pSpeaker, const char *pszRawSentence, ChoreoSentence_t &sentence );
|
||||
bool ParseChoreoSentenceContents( CBaseEntity *pSpeaker, char *pszSentence, ChoreoSentence_t &sentence );
|
||||
|
||||
extern float GetChoreoSentenceDuration( CBaseFlex *pSpeaker, const ChoreoSentence_t &sentence );
|
||||
|
||||
#endif // CHOREOSENTENCE_H
|
@ -41,6 +41,10 @@
|
||||
#include "npc_alyx_episodic.h"
|
||||
#endif // HL2_EPISODIC
|
||||
|
||||
#ifdef MAPBASE
|
||||
#include "mapbase/choreosentence.h"
|
||||
#endif
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
@ -482,6 +486,9 @@ public:
|
||||
bool InvolvesActor( CBaseEntity *pActor ); // NOTE: returns false if scene hasn't loaded yet
|
||||
|
||||
void GenerateSoundScene( CBaseFlex *pActor, const char *soundname );
|
||||
#ifdef MAPBASE
|
||||
void GenerateChoreoSentenceScene( CBaseFlex *pActor, const char *pszSentenceName );
|
||||
#endif
|
||||
|
||||
virtual float GetPostSpeakDelay() { return 1.0; }
|
||||
|
||||
@ -594,6 +601,9 @@ private:
|
||||
void PrecacheScene( CChoreoScene *scene );
|
||||
|
||||
CChoreoScene *GenerateSceneForSound( CBaseFlex *pFlexActor, const char *soundname );
|
||||
#ifdef MAPBASE
|
||||
CChoreoScene *GenerateSceneForSentenceName( CBaseFlex *pFlexActor, const char *pszSentenceName );
|
||||
#endif
|
||||
|
||||
bool CheckActors();
|
||||
|
||||
@ -643,6 +653,9 @@ private:
|
||||
bool m_bGenerated;
|
||||
string_t m_iszSoundName;
|
||||
CHandle< CBaseFlex > m_hActor;
|
||||
#ifdef MAPBASE
|
||||
bool m_bChoreoSentence;
|
||||
#endif
|
||||
|
||||
EHANDLE m_hActivator;
|
||||
|
||||
@ -736,6 +749,9 @@ BEGIN_DATADESC( CSceneEntity )
|
||||
DEFINE_FIELD( m_bGenerated, FIELD_BOOLEAN ),
|
||||
DEFINE_FIELD( m_iszSoundName, FIELD_STRING ),
|
||||
DEFINE_FIELD( m_hActor, FIELD_EHANDLE ),
|
||||
#ifdef MAPBASE
|
||||
DEFINE_FIELD( m_bChoreoSentence, FIELD_BOOLEAN ),
|
||||
#endif
|
||||
DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ),
|
||||
|
||||
// DEFINE_FIELD( m_bSceneMissing, FIELD_BOOLEAN ),
|
||||
@ -953,6 +969,146 @@ CChoreoScene *CSceneEntity::GenerateSceneForSound( CBaseFlex *pFlexActor, const
|
||||
return scene;
|
||||
}
|
||||
|
||||
#ifdef MAPBASE
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : *actor -
|
||||
// *pszSentenceName -
|
||||
// Output : CChoreoScene
|
||||
//-----------------------------------------------------------------------------
|
||||
CChoreoScene *CSceneEntity::GenerateSceneForSentenceName( CBaseFlex *pFlexActor, const char *pszSentenceName )
|
||||
{
|
||||
const ChoreoSentence_t *pSentence = LookupChoreoSentence( pFlexActor, pszSentenceName );
|
||||
if ( !pSentence )
|
||||
{
|
||||
Warning( "CSceneEntity::GenerateSceneForSentenceName: Couldn't find sentence '%s'\n", pszSentenceName );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// TODO: Raw sentence support?
|
||||
// ChoreoSentence_t sentence;
|
||||
// if ( !ParseChoreoSentence( pFlexActor, pszSentence, sentence ) )
|
||||
// {
|
||||
// Warning( "CSceneEntity::GenerateSceneForSentence: Couldn't parse sentence from '%s'\n", pszSentence );
|
||||
// return NULL;
|
||||
// }
|
||||
|
||||
CChoreoScene *scene = new CChoreoScene( this );
|
||||
if ( !scene )
|
||||
{
|
||||
Warning( "CSceneEntity::GenerateSceneForSentenceName: Failed to allocated new scene!!!\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
scene->SetPrintFunc( LocalScene_Printf );
|
||||
|
||||
CChoreoActor *actor = scene->AllocActor();
|
||||
CChoreoChannel *channel = scene->AllocChannel();
|
||||
|
||||
Assert( actor );
|
||||
Assert( channel );
|
||||
|
||||
if ( !actor || !channel )
|
||||
{
|
||||
Warning( "CSceneEntity::GenerateSceneForSentenceName: Alloc of actor or channel failed!!!\n" );
|
||||
delete scene;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set us up the actorz
|
||||
actor->SetName( "!self" ); // Could be pFlexActor->GetName()?
|
||||
actor->SetActive( true );
|
||||
|
||||
// Set us up the channelz
|
||||
channel->SetName( STRING( m_iszSceneFile ) );
|
||||
channel->SetActor( actor );
|
||||
|
||||
// Add to actor
|
||||
actor->AddChannel( channel );
|
||||
|
||||
// Set us up the eventz
|
||||
const char *actormodel = (pFlexActor ? STRING( pFlexActor->GetModelName() ) : NULL);
|
||||
float flCurTime = 0.0f;
|
||||
FOR_EACH_VEC( pSentence->m_Words, i )
|
||||
{
|
||||
const char *pszWord = pSentence->GetWordString( pFlexActor, i );
|
||||
|
||||
float duration = CBaseEntity::GetSoundDuration( pszWord, actormodel );
|
||||
if (duration <= 0.0f)
|
||||
{
|
||||
Warning( "CSceneEntity::GenerateSceneForSentenceName: Couldn't determine duration of %s\n", pszWord );
|
||||
}
|
||||
|
||||
CChoreoEvent *event = scene->AllocEvent();
|
||||
Assert( event );
|
||||
|
||||
if ( !event )
|
||||
{
|
||||
Warning( "CSceneEntity::GenerateSceneForSentenceName: Alloc of event failed!!!\n" );
|
||||
delete scene;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pSentence->pszCaption)
|
||||
{
|
||||
// First word gets the caption, others fall back to it
|
||||
if (i == 0)
|
||||
{
|
||||
event->SetCloseCaptionType( CChoreoEvent::CC_MASTER );
|
||||
event->SetCloseCaptionToken( pSentence->pszCaption );
|
||||
}
|
||||
else
|
||||
{
|
||||
event->SetCloseCaptionType( CChoreoEvent::CC_SLAVE );
|
||||
}
|
||||
}
|
||||
//else if (pSentence->pszName)
|
||||
//{
|
||||
// // TODO: Caption from name?
|
||||
//}
|
||||
|
||||
if (pSentence->m_Words[i].nVol != 100)
|
||||
{
|
||||
event->SetYaw( pSentence->m_Words[i].nVol );
|
||||
}
|
||||
|
||||
if (pSentence->m_Words[i].nPitch != 100)
|
||||
{
|
||||
duration *= (100.0f / ((float)pSentence->m_Words[i].nPitch));
|
||||
event->SetPitch( pSentence->m_Words[i].nPitch );
|
||||
}
|
||||
|
||||
// HACKHACK: Need to be spaced away from repeated sound to avoid changing the previous sound's pitch instead
|
||||
if (i+1 < pSentence->m_Words.Count() && pSentence->m_Words[i+1].pszWord == pSentence->m_Words[i].pszWord
|
||||
&& pSentence->m_Words[i + 1].nPitch != 100)
|
||||
duration += 0.1f;
|
||||
|
||||
event->SetType( CChoreoEvent::SPEAK );
|
||||
event->SetName( pszWord );
|
||||
event->SetParameters( pszWord );
|
||||
event->SetStartTime( flCurTime );
|
||||
event->SetUsingRelativeTag( false );
|
||||
event->SetEndTime( flCurTime + duration );
|
||||
event->SnapTimes();
|
||||
|
||||
//Msg( "%i %s: %f -> %f (%f)\n", i, pszWord, flCurTime, flCurTime + duration, duration );
|
||||
|
||||
// Add to channel
|
||||
channel->AddEvent( event );
|
||||
|
||||
// Point back to our owners
|
||||
event->SetChannel( channel );
|
||||
event->SetActor( actor );
|
||||
|
||||
flCurTime += duration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return scene;
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -960,6 +1116,13 @@ void CSceneEntity::Activate()
|
||||
{
|
||||
if ( m_bGenerated && !m_pScene )
|
||||
{
|
||||
#ifdef MAPBASE
|
||||
if (m_bChoreoSentence)
|
||||
{
|
||||
m_pScene = GenerateSceneForSentenceName( m_hActor, STRING( m_iszSoundName ) );
|
||||
}
|
||||
else
|
||||
#endif
|
||||
m_pScene = GenerateSceneForSound( m_hActor, STRING( m_iszSoundName ) );
|
||||
}
|
||||
|
||||
@ -1138,6 +1301,22 @@ void CSceneEntity::GenerateSoundScene( CBaseFlex *pActor, const char *soundname
|
||||
m_hActor = pActor;
|
||||
}
|
||||
|
||||
#ifdef MAPBASE
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : *pActor -
|
||||
// *pszSentenceName -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CSceneEntity::GenerateChoreoSentenceScene( CBaseFlex *pActor, const char *pszSentenceName )
|
||||
{
|
||||
m_bGenerated = true;
|
||||
m_iszSoundName = MAKE_STRING( pszSentenceName );
|
||||
m_hActor = pActor;
|
||||
|
||||
m_bChoreoSentence = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Output : Returns true on success, false on failure.
|
||||
@ -1930,6 +2109,26 @@ void CSceneEntity::DispatchStartSpeak( CChoreoScene *scene, CBaseFlex *actor, CC
|
||||
|
||||
// Warning( "Speak %s\n", soundname );
|
||||
|
||||
#ifdef MAPBASE
|
||||
if ( m_fPitch != 1.0f || event->GetPitch() != 0 )
|
||||
{
|
||||
if ( es.m_nPitch && es.m_nPitch != 100 )
|
||||
es.m_nPitch = static_cast<float>( es.m_nPitch ) * m_fPitch;
|
||||
else
|
||||
{
|
||||
float flPitch = (event->GetPitch() != 0 ? event->GetPitch() : 100.0f);
|
||||
es.m_nPitch = flPitch * m_fPitch;
|
||||
}
|
||||
|
||||
es.m_nFlags |= SND_CHANGE_PITCH;
|
||||
}
|
||||
|
||||
if ( event->GetYaw() != 0 )
|
||||
{
|
||||
es.m_flVolume = (((float)event->GetYaw()) / 100.0f);
|
||||
es.m_nFlags |= SND_CHANGE_VOL;
|
||||
}
|
||||
#else
|
||||
if ( m_fPitch != 1.0f )
|
||||
{
|
||||
if ( es.m_nPitch )
|
||||
@ -1939,6 +2138,7 @@ void CSceneEntity::DispatchStartSpeak( CChoreoScene *scene, CBaseFlex *actor, CC
|
||||
|
||||
es.m_nFlags |= SND_CHANGE_PITCH;
|
||||
}
|
||||
#endif
|
||||
|
||||
EmitSound( filter2, actor->entindex(), es );
|
||||
actor->AddSceneEvent( scene, event );
|
||||
@ -5667,6 +5867,62 @@ void CInstancedSceneEntity::OnLoaded()
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef MAPBASE
|
||||
float InstancedChoreoSentenceScene( CBaseFlex *pActor, const char *pszSentence, EHANDLE *phSceneEnt,
|
||||
float flPostDelay, bool bIsBackground, AI_Response *response,
|
||||
bool bMultiplayer, IRecipientFilter *filter /* = NULL */ )
|
||||
{
|
||||
if ( !pActor )
|
||||
{
|
||||
Warning( "InstancedChoreoSentenceScene: Expecting non-NULL pActor for sound %s\n", pszSentence );
|
||||
return 0;
|
||||
}
|
||||
|
||||
CInstancedSceneEntity *pScene = (CInstancedSceneEntity *)CBaseEntity::CreateNoSpawn( "instanced_scripted_scene", vec3_origin, vec3_angle );
|
||||
|
||||
Q_strncpy( pScene->m_szInstanceFilename, UTIL_VarArgs( "AutoGenerated(%s)", pszSentence ), sizeof( pScene->m_szInstanceFilename ) );
|
||||
pScene->m_iszSceneFile = MAKE_STRING( pScene->m_szInstanceFilename );
|
||||
|
||||
pScene->m_hOwner = pActor;
|
||||
pScene->m_bHadOwner = pActor != NULL;
|
||||
|
||||
pScene->GenerateChoreoSentenceScene( pActor, pszSentence );
|
||||
|
||||
pScene->m_bMultiplayer = bMultiplayer;
|
||||
pScene->SetPostSpeakDelay( flPostDelay );
|
||||
DispatchSpawn( pScene );
|
||||
pScene->Activate();
|
||||
pScene->m_bIsBackground = bIsBackground;
|
||||
|
||||
pScene->SetBackground( bIsBackground );
|
||||
pScene->SetRecipientFilter( filter );
|
||||
|
||||
if ( response )
|
||||
{
|
||||
float flPreDelay = response->GetPreDelay();
|
||||
if ( flPreDelay )
|
||||
{
|
||||
pScene->SetPreDelay( flPreDelay );
|
||||
}
|
||||
}
|
||||
|
||||
pScene->StartPlayback();
|
||||
|
||||
if ( response )
|
||||
{
|
||||
// If the response wants us to abort on NPC state switch, remember that
|
||||
pScene->SetBreakOnNonIdle( response->ShouldBreakOnNonIdle() );
|
||||
}
|
||||
|
||||
if ( phSceneEnt )
|
||||
{
|
||||
*phSceneEnt = pScene;
|
||||
}
|
||||
|
||||
return pScene->EstimateLength();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool g_bClientFlex = true;
|
||||
|
||||
LINK_ENTITY_TO_CLASS( scene_manager, CSceneManager );
|
||||
|
@ -28,6 +28,7 @@ int GetRecentNPCSpeech( recentNPCSpeech_t speech[ SPEECH_LIST_MAX_SOUNDS ] );
|
||||
float InstancedScriptedScene( CBaseFlex *pActor, const char *pszScene, EHANDLE *phSceneEnt = NULL, float flPostDelay = 0.0f, bool bIsBackground = false, AI_Response *response = NULL, bool bMultiplayer = false, IRecipientFilter *filter = NULL );
|
||||
#ifdef MAPBASE
|
||||
float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, char const *soundname, EHANDLE *phSceneEnt = NULL, float flPostDelay = 0.0f, bool bIsBackground = false, AI_Response *response = NULL, bool bMultiplayer = false, IRecipientFilter *filter = NULL );
|
||||
float InstancedChoreoSentenceScene( CBaseFlex *pActor, char const *pszSentence, EHANDLE *phSceneEnt = NULL, float flPostDelay = 0.0f, bool bIsBackground = false, AI_Response *response = NULL, bool bMultiplayer = false, IRecipientFilter *filter = NULL );
|
||||
#else
|
||||
float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, char const *soundname, EHANDLE *phSceneEnt = NULL );
|
||||
#endif
|
||||
|
@ -63,6 +63,8 @@ $Project
|
||||
$File "mapbase\ai_grenade.h"
|
||||
$File "mapbase\ai_monitor.cpp"
|
||||
$File "mapbase\ai_weaponmodifier.cpp"
|
||||
$File "mapbase\choreosentence.cpp"
|
||||
$File "mapbase\choreosentence.h"
|
||||
$File "mapbase\custom_weapon_factory.cpp"
|
||||
$File "mapbase\custom_weapon_factory.h"
|
||||
$File "mapbase\closecaption_entity.cpp"
|
||||
|
@ -31,6 +31,9 @@
|
||||
|
||||
#ifdef GAME_DLL
|
||||
#include "sceneentity.h"
|
||||
#ifdef MAPBASE
|
||||
#include "mapbase/choreosentence.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "networkstringtabledefs.h"
|
||||
@ -492,6 +495,17 @@ void CGameResponseSystem::Precache()
|
||||
CBaseEntity::PrecacheScriptSound( response.value );
|
||||
}
|
||||
break;
|
||||
#ifdef MAPBASE
|
||||
case RESPONSE_CHOREOSENTENCE:
|
||||
{
|
||||
const ChoreoSentence_t *pSentence = LookupChoreoSentence( NULL, response.value );
|
||||
if (pSentence)
|
||||
PrecacheChoreoSentence( *pSentence );
|
||||
else
|
||||
Msg( "Choreo scene '%s' not found\n", response.value );
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "AI_ResponseSystem.h"
|
||||
#include "mapbase/SystemConvarMod.h"
|
||||
#include "gameinterface.h"
|
||||
#include "mapbase/choreosentence.h"
|
||||
#endif
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
@ -107,7 +108,7 @@ enum
|
||||
MANIFEST_HUDLAYOUT,
|
||||
#else
|
||||
MANIFEST_TALKER,
|
||||
//MANIFEST_SENTENCES,
|
||||
MANIFEST_CHOREOSENTENCES,
|
||||
MANIFEST_ACTBUSY,
|
||||
#endif
|
||||
#ifdef MAPBASE_VSCRIPT
|
||||
@ -147,7 +148,7 @@ static const ManifestType_t gm_szManifestFileStrings[MANIFEST_NUM_TYPES] = {
|
||||
{ "hudlayout", "mapbase_load_hudlayout", "Should we load map-specific HUD layout overrides? e.g. \"maps/<mapname>_hudlayout.res\"" },
|
||||
#else
|
||||
{ "talker", "mapbase_load_talker", "Should we load map-specific talker files? e.g. \"maps/<mapname>_talker.txt\"" },
|
||||
//{ "sentences", "mapbase_load_sentences", "Should we load map-specific sentences? e.g. \"maps/<mapname>_sentences.txt\"" },
|
||||
{ "choreosentences", "mapbase_load_choreosentences", "Should we load map-specific choreo sentences? e.g. \"maps/<mapname>_choreosentences.txt\"" },
|
||||
{ "actbusy", "mapbase_load_actbusy", "Should we load map-specific actbusy files? e.g. \"maps/<mapname>_actbusy.txt\"" },
|
||||
#endif
|
||||
#ifdef MAPBASE_VSCRIPT
|
||||
@ -465,7 +466,7 @@ public:
|
||||
LoadResponseSystemFile(value); //PrecacheCustomResponseSystem( value );
|
||||
} break;
|
||||
//case MANIFEST_SOUNDSCAPES: { g_SoundscapeSystem.AddSoundscapeFile(value); } break;
|
||||
//case MANIFEST_SENTENCES: { engine->PrecacheSentenceFile(value); } break;
|
||||
case MANIFEST_CHOREOSENTENCES: { LoadChoreoSentenceFile(value); } break;
|
||||
case MANIFEST_ACTBUSY: { ParseCustomActbusyFile(value); } break;
|
||||
#endif
|
||||
#ifdef MAPBASE_VSCRIPT
|
||||
@ -610,6 +611,7 @@ public:
|
||||
#else
|
||||
void LoadCustomTalkerFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_TALKER, false ); }
|
||||
void LoadCustomActbusyFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_ACTBUSY, false ); }
|
||||
void LoadCustomChoreoSentenceFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_CHOREOSENTENCES, false ); }
|
||||
#endif
|
||||
|
||||
const char *GetModName() { return g_iszGameName; }
|
||||
@ -658,6 +660,7 @@ BEGIN_SCRIPTDESC_ROOT( CMapbaseSystem, SCRIPT_SINGLETON "All-purpose Mapbase sys
|
||||
#else
|
||||
DEFINE_SCRIPTFUNC( LoadCustomTalkerFile, "Loads a custom talker file." )
|
||||
DEFINE_SCRIPTFUNC( LoadCustomActbusyFile, "Loads a custom actbusy file." )
|
||||
DEFINE_SCRIPTFUNC( LoadCustomChoreoSentenceFile, "Loads a custom choreo sentence file." )
|
||||
#endif
|
||||
|
||||
DEFINE_SCRIPTFUNC( GetModName, "Gets the name of the mod. This is the name which shows up on Steam, RPC, etc." )
|
||||
|
@ -99,6 +99,7 @@ namespace ResponseRules
|
||||
#ifdef MAPBASE
|
||||
RESPONSE_VSCRIPT, // Run VScript code
|
||||
RESPONSE_VSCRIPT_FILE, // Run a VScript file (bypasses ugliness and character limits when just using IncludeScript() with RESPONSE_VSCRIPT)
|
||||
RESPONSE_CHOREOSENTENCE, // Sentences put together as VCDs, see choreosentence.h for more information
|
||||
#endif
|
||||
|
||||
NUM_RESPONSES,
|
||||
|
@ -1655,6 +1655,8 @@ inline ResponseType_t ComputeResponseType( const char *s )
|
||||
return RESPONSE_VSCRIPT_FILE;
|
||||
else
|
||||
return RESPONSE_VSCRIPT;
|
||||
case 'c':
|
||||
return RESPONSE_CHOREOSENTENCE;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -260,6 +260,8 @@ const char *CRR_Response::DescribeResponse( ResponseType_t type )
|
||||
return "RESPONSE_VSCRIPT";
|
||||
case ResponseRules::RESPONSE_VSCRIPT_FILE:
|
||||
return "RESPONSE_VSCRIPT_FILE";
|
||||
case ResponseRules::RESPONSE_CHOREOSENTENCE:
|
||||
return "RESPONSE_CHOREOSENTENCE";
|
||||
#endif
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user